Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
export type {
AltairEnvironment,
AltairUiOptions,
AltairUiOptionsInput,
} from "./options.js";
export {
altairUiOptionsSchema,
parseAltairUiOptions,
} from "./options.js";

View File

@@ -0,0 +1,74 @@
# `options` — `AltairUiOptions` schema + parser
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`options` owns the Zod-validated configuration surface every consumer of
the Altair UI plugin passes through. The schema enforces structural
invariants — every endpoint string must start with `/`, theme is one of
three literal values, top-level keys are strict so typos surface at the
boundary — while still defaulting every field so the most common call
site (`createAltairUi()`) is zero-config. Centralising defaulting in one
schema means the factory, the HTML renderer, and the asset map all
consume the same shape.
## Features
- Defaults: `uiEndpoint=/graphql/ui`, `graphqlEndpoint=/graphql`,
`subscriptionEndpoint=/graphql/ws`, `theme=system`, `initialQuery=""`,
empty `initialHeaders`, empty `initialEnvironments`.
- Strict mode at every nesting level — unknown keys are rejected.
- Rich per-environment block: `url`, `subscriptionsEndpoint`, `headers`,
`variables`.
- `parseAltairUiOptions(undefined)` is valid and produces the default
bundle.
## Usage: Basic
```ts
import { parseAltairUiOptions } from "@suaveplan/api-graphql-ui-altair/options";
const opts = parseAltairUiOptions();
// opts.uiEndpoint === "/graphql/ui"
```
## Usage: Advanced
```ts
import { parseAltairUiOptions } from "@suaveplan/api-graphql-ui-altair/options";
const opts = parseAltairUiOptions({
uiEndpoint: "/dev/ui",
graphqlEndpoint: "/api/graphql",
theme: "dark",
initialEnvironments: {
production: {
url: "https://api.example.com/graphql",
subscriptionsEndpoint: "wss://api.example.com/graphql",
headers: { "x-tenant": "acme" },
variables: { region: "us-east-1" },
},
},
});
```
## API Reference
- `altairUiOptionsSchema: z.ZodObject` — direct Zod handle (composable
via `.extend()` or `.partial()` for downstream wrappers).
- `parseAltairUiOptions(input?: AltairUiOptionsInput): AltairUiOptions`
— throws `ZodError` on shape violations.
- `AltairUiOptions` — output type (every field defined).
- `AltairUiOptionsInput` — input type (every field optional).
- `AltairEnvironment` — per-environment record shape.
## Implementation Notes
The Zod default record literals (`{}`) feed back into `z.input` as
optional, so the input type stays ergonomic at the caller boundary
while the output type guarantees every field is populated downstream.
The schema uses the Zod 4 default-import (`import z from "zod"`) per
the repo-wide Bun-safe convention.

View File

@@ -0,0 +1,94 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import { altairUiOptionsSchema, parseAltairUiOptions } from "./options.js";
describe("parseAltairUiOptions", () => {
it("applies all defaults when input is undefined", {
timeout: 5000,
}, () => {
const out = parseAltairUiOptions(undefined);
expect(out.uiEndpoint).toBe("/graphql/ui");
expect(out.graphqlEndpoint).toBe("/graphql");
expect(out.subscriptionEndpoint).toBe("/graphql/ws");
expect(out.theme).toBe("system");
expect(out.initialQuery).toBe("");
expect(out.initialHeaders).toEqual({});
expect(out.initialEnvironments).toEqual({});
});
it("applies all defaults when input is empty object", {
timeout: 5000,
}, () => {
const out = parseAltairUiOptions({});
expect(out.uiEndpoint).toBe("/graphql/ui");
});
it("accepts overrides for every field", { timeout: 5000 }, () => {
const out = parseAltairUiOptions({
uiEndpoint: "/dev/ui",
graphqlEndpoint: "/api/graphql",
subscriptionEndpoint: "/api/ws",
theme: "dark",
initialQuery: "{ ping }",
initialHeaders: { "x-tenant": "acme" },
initialEnvironments: {
production: {
url: "https://api.example.com/graphql",
headers: { "x-env": "prod" },
variables: { region: "us-east-1" },
subscriptionsEndpoint: "wss://api.example.com/graphql",
},
},
});
expect(out.uiEndpoint).toBe("/dev/ui");
expect(out.theme).toBe("dark");
expect(out.initialHeaders["x-tenant"]).toBe("acme");
expect(out.initialEnvironments["production"]?.url).toBe(
"https://api.example.com/graphql"
);
});
it("rejects uiEndpoint that doesn't start with /", {
timeout: 5000,
}, () => {
expect(() =>
parseAltairUiOptions({ uiEndpoint: "graphql/ui" })
).toThrow();
});
it("rejects graphqlEndpoint that doesn't start with /", {
timeout: 5000,
}, () => {
expect(() =>
parseAltairUiOptions({ graphqlEndpoint: "graphql" })
).toThrow();
});
it("rejects subscriptionEndpoint that doesn't start with /", {
timeout: 5000,
}, () => {
expect(() =>
parseAltairUiOptions({ subscriptionEndpoint: "ws" })
).toThrow();
});
it("rejects empty uiEndpoint", { timeout: 5000 }, () => {
expect(() => parseAltairUiOptions({ uiEndpoint: "" })).toThrow();
});
it("rejects unknown theme", { timeout: 5000 }, () => {
// @ts-expect-error testing runtime rejection
expect(() => parseAltairUiOptions({ theme: "midnight" })).toThrow();
});
it("rejects unknown top-level keys (strict)", { timeout: 5000 }, () => {
expect(() =>
// @ts-expect-error testing runtime rejection
parseAltairUiOptions({ rogue: true })
).toThrow();
});
it("schema is reusable directly via parse", { timeout: 5000 }, () => {
const out = altairUiOptionsSchema.parse({});
expect(out.theme).toBe("system");
});
});

View File

@@ -0,0 +1,72 @@
import z from "zod";
/** Per-environment configuration block — narrow projection of Altair's. */
export interface AltairEnvironment {
readonly url?: string;
readonly subscriptionsEndpoint?: string;
readonly headers?: Readonly<Record<string, string>>;
readonly variables?: Readonly<Record<string, string>>;
}
const environmentSchema = z
.object({
url: z.string().optional(),
subscriptionsEndpoint: z.string().optional(),
headers: z.record(z.string(), z.string()).optional(),
variables: z.record(z.string(), z.string()).optional(),
})
.strict();
/**
* Zod schema for {@link AltairUiOptions}. The schema is intentionally
* permissive on header/environment values — operators can plug arbitrary
* string values into the embedded shell — and strict on the four
* structural fields (endpoints + theme).
*/
export const altairUiOptionsSchema = z
.object({
uiEndpoint: z
.string()
.min(1)
.startsWith("/", {
message: "uiEndpoint must start with '/'",
})
.default("/graphql/ui"),
graphqlEndpoint: z
.string()
.min(1)
.startsWith("/", {
message: "graphqlEndpoint must start with '/'",
})
.default("/graphql"),
subscriptionEndpoint: z
.string()
.min(1)
.startsWith("/", {
message: "subscriptionEndpoint must start with '/'",
})
.default("/graphql/ws"),
theme: z.enum(["light", "dark", "system"]).default("system"),
initialQuery: z.string().default(""),
initialHeaders: z.record(z.string(), z.string()).default({}),
initialEnvironments: z
.record(z.string(), environmentSchema)
.default({}),
})
.strict();
/** Parsed, defaulted options consumed by `createAltairUi`. */
export type AltairUiOptions = z.infer<typeof altairUiOptionsSchema>;
/** Caller-facing input — every field optional, defaults applied by Zod. */
export type AltairUiOptionsInput = z.input<typeof altairUiOptionsSchema>;
/**
* Validate caller-supplied options. Throws on shape violations; returns
* a fully-defaulted, immutable `AltairUiOptions` on success.
*/
export function parseAltairUiOptions(
input: AltairUiOptionsInput | undefined
): AltairUiOptions {
return altairUiOptionsSchema.parse(input ?? {});
}