diff --git a/.github/workflows/verify.yaml b/.github/workflows/verify.yaml index 220b0f9..fb4c04f 100644 --- a/.github/workflows/verify.yaml +++ b/.github/workflows/verify.yaml @@ -15,7 +15,7 @@ jobs: steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: setup deno uses: denoland/setup-deno@v2 @@ -33,3 +33,27 @@ jobs: - name: test run: deno task test + + diagnostics: + runs-on: ubuntu-latest + + steps: + - name: checkout + uses: actions/checkout@v7 + + - name: setup node + uses: actions/setup-node@v6 + with: + node-version: 24.x + + - name: install node diagnostics + run: npm install + working-directory: diagnostics/node-ts5 + + - name: check node diagnostics + run: npm run check + working-directory: diagnostics/node-ts5 + + - name: run node diagnostics + run: npm run diagnostics:all + working-directory: diagnostics/node-ts5 diff --git a/.gitignore b/.gitignore index a7c3533..1bfeedc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ /build/ /.agent-shell/ +/diagnostics/node-ts5/node_modules/ +/diagnostics/node-ts5/package-lock.json +/diagnostics/node-ts5/.diagnostic.tsconfig.json +/diagnostics/node-ts5/fixtures/generated/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e3efc9d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "typescript.tsdk": "diagnostics/node-ts5/node_modules/typescript/lib", + "js/ts.tsdk.path": "diagnostics/node-ts5/node_modules/typescript/lib" +} diff --git a/diagnostics/node-ts5/diagnose.ts b/diagnostics/node-ts5/diagnose.ts new file mode 100644 index 0000000..f76917f --- /dev/null +++ b/diagnostics/node-ts5/diagnose.ts @@ -0,0 +1,84 @@ +import { readdir, unlink, writeFile } from "node:fs/promises"; +import { relative } from "node:path"; +import { fileURLToPath } from "node:url"; +import { spawnSync } from "node:child_process"; + +const dir = fileURLToPath(new URL(".", import.meta.url)); +const tsc = fileURLToPath( + new URL("./node_modules/typescript/bin/tsc", import.meta.url), +); +const config = fileURLToPath( + new URL("./.diagnostic.tsconfig.json", import.meta.url), +); +const examples = fileURLToPath(new URL("../../examples/", import.meta.url)); +const generated = fileURLToPath( + new URL("./fixtures/generated/", import.meta.url), +); + +let failed = false; +const files = [ + ...(await filesIn(examples)), + ...(await filesIn(generated, true)), +]; + +try { + for (const file of files) { + const path = relative(dir, file); + await writeFile( + config, + JSON.stringify({ + extends: "./tsconfig.json", + files: [path], + }), + ); + + const result = spawnSync(process.execPath, [ + tsc, + "--extendedDiagnostics", + "--project", + config, + "--pretty", + "false", + ], { cwd: dir, encoding: "utf8" }); + + const output = `${result.stdout}${result.stderr}`; + const instantiations = match(output, /^Instantiations:\s+(.+)$/m); + const checkTime = match(output, /^Check time:\s+(.+)$/m); + const error = output.match(/^.*error TS\d+:.*$/m)?.[0]; + const status = result.status === 0 ? "PASS" : "FAIL"; + + console.log( + `${path.padEnd(34)} ${status} ` + + `instantiations=${instantiations ?? "n/a"} ` + + `check=${checkTime ?? "n/a"}`, + ); + if (error) console.log(` ${error}`); + failed ||= result.status !== 0; + } +} finally { + await unlink(config).catch(() => {}); +} + +if (failed) process.exitCode = 1; + +function match(value: string, pattern: RegExp) { + return value.match(pattern)?.[1]?.trim(); +} + +async function filesIn(dir: string, optional = false): Promise { + try { + return (await readdir(dir, { withFileTypes: true })) + .filter((entry) => entry.isFile() && entry.name.endsWith(".ts")) + .map((entry) => `${dir}${entry.name}`) + .sort(); + } catch (error) { + if ( + optional && + typeof error === "object" && + error !== null && + "code" in error && + error.code === "ENOENT" + ) return []; + throw error; + } +} diff --git a/diagnostics/node-ts5/generate.ts b/diagnostics/node-ts5/generate.ts new file mode 100644 index 0000000..93e493e --- /dev/null +++ b/diagnostics/node-ts5/generate.ts @@ -0,0 +1,48 @@ +import { mkdir, readdir, unlink, writeFile } from "node:fs/promises"; +import { fileURLToPath } from "node:url"; + +const output = fileURLToPath(new URL("./fixtures/generated/", import.meta.url)); +const sizes = [5, 20, 30, 50, 75, 100, 101]; +const modes = ["static", "checkpoint"] as const; + +await mkdir(output, { recursive: true }); + +for (const entry of await readdir(output)) { + if (entry.endsWith(".ts")) await unlink(`${output}${entry}`); +} + +for (const size of sizes) { + for (const mode of modes) { + const prefix = mode === "static" ? "" : `${mode}-`; + const file = `${output}${prefix}options-${ + String(size).padStart(3, "0") + }.ts`; + await writeFile(file, source(size, mode)); + console.log(`generated ${file}`); + } +} + +function source(size: number, mode: (typeof modes)[number]) { + const split = Math.floor(size / 2); + const lines = Array.from({ length: size }, (_, index) => { + const field = String(index + 1).padStart(3, "0"); + return ` option(name("p${field}"), description("p${field}"), cli(["--p${field}"]), schema(z.number())),`; + }); + const fields = mode === "checkpoint" + ? [...lines.slice(0, split), " checkpoint(),", ...lines.slice(split)].join( + "\n", + ) + : lines.join("\n"); + const imports = mode === "checkpoint" ? "checkpoint, " : ""; + const prefix = mode === "static" ? "" : `${mode}-`; + + return `import { ${imports}cli, command, description, name, option } from "../../../../mod.ts"; +import { schema } from "../../../../lib/param.ts"; +import { z } from "zod"; + +export const app = command( + name("${prefix}options-${size}"), +${fields} +); +`; +} diff --git a/diagnostics/node-ts5/package.json b/diagnostics/node-ts5/package.json new file mode 100644 index 0000000..acdef7d --- /dev/null +++ b/diagnostics/node-ts5/package.json @@ -0,0 +1,17 @@ +{ + "name": "configliere-node-ts5-diagnostics", + "private": true, + "type": "module", + "scripts": { + "check": "tsc --noEmit --project tsconfig.json", + "generate": "node generate.ts", + "diagnostics": "node diagnose.ts", + "diagnostics:all": "npm run generate && npm run diagnostics" + }, + "devDependencies": { + "@standard-schema/spec": "1.1.0", + "@types/node": "^26.5.0", + "typescript": "5.5.4", + "zod": "4.4.3" + } +} diff --git a/diagnostics/node-ts5/tsconfig.json b/diagnostics/node-ts5/tsconfig.json new file mode 100644 index 0000000..553da8e --- /dev/null +++ b/diagnostics/node-ts5/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "types": ["node"] + }, + "files": [ + "../../examples/options.ts", + "../../examples/checkpoint-options.ts", + "../../examples/routes.ts", + "../../examples/routes-checkpoint.ts", + "diagnose.ts", + "generate.ts" + ] +} diff --git a/docs/insights.md b/docs/insights.md index 55059ad..54291e0 100644 --- a/docs/insights.md +++ b/docs/insights.md @@ -17,6 +17,12 @@ The detailed phase-binding design is recorded in [Binding](./binding.md). - Definitions are immutable function-composition pipelines. - Any valid `A → B` transformation may participate in a pipeline. - `extend()` packages context-free composition; `extend()` is identity. +- Static built-in route elements carry a hidden operation type and fold in + bounded chunks; their number is not limited by an overload tower. +- Macros composed with `extend()` inherit that operation type without an + explicit registration step. Concrete unbranded `A → B` functions compose + directly; bespoke generic atoms use an explicit type-lambda marker because + TypeScript cannot otherwise apply their input-dependent output type. - Static definitions contain enough metadata to render help and schemas without parsing input. - Standard Schema validates values; it does not define source syntax or token diff --git a/examples/checkpoint-options.ts b/examples/checkpoint-options.ts new file mode 100644 index 0000000..687c03d --- /dev/null +++ b/examples/checkpoint-options.ts @@ -0,0 +1,86 @@ +import { checkpoint, cli, command, description, name, option } from "../mod.ts"; +import { schema } from "../lib/param.ts"; +import type { ModelOf } from "../lib/types.ts"; +import { z } from "zod"; + +export const app = command( + name("auth0"), + option( + name("port"), + description("server port"), + cli(["--port", "-p"]), + schema(z.number()), + ), + option( + name("domain"), + description("server domain"), + cli(["--domain"]), + schema(z.string()), + ), + option( + name("audience"), + description("Auth0 audience"), + cli(["--audience"]), + schema(z.string()), + ), + option( + name("clientID"), + description("Auth0 client ID"), + cli(["--client-id"]), + schema(z.string()), + ), + option( + name("protocol"), + description("server protocol"), + cli(["--protocol"]), + schema(z.enum(["http", "https"])), + ), + checkpoint(), + option( + name("clientSecret"), + description("client secret"), + cli(["--client-secret"]), + schema(z.string()), + ), + option( + name("scope"), + description("OAuth scope"), + cli(["--scope"]), + schema(z.string()), + ), + option( + name("rulesDirectory"), + description("rules directory"), + cli(["--rules-directory"]), + schema(z.string()), + ), + option( + name("connection"), + description("Auth0 connection"), + cli(["--connection"]), + schema(z.string()), + ), + option( + name("config"), + description("JSON config path"), + cli(["--config", "-c"]), + schema(z.string()), + ), +); + +export type Configuration = ModelOf; + +type Equal = (() => T extends A ? 1 : 2) extends + (() => T extends B ? 1 : 2) + ? (() => T extends B ? 1 : 2) extends (() => T extends A ? 1 : 2) ? true + : false + : false; +type Assert = T; +type ConfigIsPresent = Assert< + Equal<"config" extends keyof Configuration ? true : false, true> +>; +type ConnectionIsPresent = Assert< + Equal<"connection" extends keyof Configuration ? true : false, true> +>; +type PortIsNumber = Assert>; +type ConfigIsString = Assert>; diff --git a/examples/options.ts b/examples/options.ts new file mode 100644 index 0000000..a075830 --- /dev/null +++ b/examples/options.ts @@ -0,0 +1,87 @@ +import { cli, command, description, name, option } from "../mod.ts"; +import { schema } from "../lib/param.ts"; +import type { ModelOf } from "../lib/types.ts"; +import { z } from "zod"; + +export const app = command( + name("auth0"), + option( + name("port"), + description("server port"), + cli(["--port", "-p"]), + schema(z.number()), + ), + option( + name("domain"), + description("server domain"), + cli(["--domain"]), + schema(z.string()), + ), + option( + name("audience"), + description("Auth0 audience"), + cli(["--audience"]), + schema(z.string()), + ), + option( + name("clientID"), + description("Auth0 client ID"), + cli(["--client-id"]), + schema(z.string()), + ), + option( + name("protocol"), + description("server protocol"), + cli(["--protocol"]), + schema(z.enum(["http", "https"])), + ), + option( + name("clientSecret"), + description("client secret"), + cli(["--client-secret"]), + schema(z.string()), + ), + option( + name("scope"), + description("OAuth scope"), + cli(["--scope"]), + schema(z.string()), + ), + option( + name("rulesDirectory"), + description("rules directory"), + cli(["--rules-directory"]), + schema(z.string()), + ), + option( + name("connection"), + description("Auth0 connection"), + cli(["--connection"]), + schema(z.string()), + ), + option( + name("config"), + description("JSON config path"), + cli(["--config", "-c"]), + schema(z.string()), + ), +); + +// Production-use type: application code can use this inferred configuration shape. +export type Configuration = ModelOf; + +// Diagnostic-only assertions: these force TypeScript to materialize representative keys. +type Equal = (() => T extends A ? 1 : 2) extends + (() => T extends B ? 1 : 2) + ? (() => T extends B ? 1 : 2) extends (() => T extends A ? 1 : 2) ? true + : false + : false; +type Assert = T; +type ConfigIsPresent = Assert< + Equal<"config" extends keyof Configuration ? true : false, true> +>; +type ConnectionIsPresent = Assert< + Equal<"connection" extends keyof Configuration ? true : false, true> +>; +type PortIsNumber = Assert>; +type ConfigIsString = Assert>; diff --git a/examples/routes-checkpoint.ts b/examples/routes-checkpoint.ts new file mode 100644 index 0000000..10eea97 --- /dev/null +++ b/examples/routes-checkpoint.ts @@ -0,0 +1,71 @@ +import { + checkpoint, + command, + description, + name, + option, + route, + routes, + schema, + toggle, + version, +} from "../mod.ts"; +import type { ChildrenOf, ModelOf } from "../lib/types.ts"; +import { z } from "zod"; + +export const app = command( + name("simulacrum"), + description("Run and manage local service simulators."), + version("1.0.0"), + toggle(name("verbose")), + routes( + command( + name("serve"), + option(name("port"), description("server port"), schema(z.number())), + checkpoint(), + option(name("host"), description("server host"), schema(z.string())), + option( + name("protocol"), + description("server protocol"), + schema(z.enum(["http", "https"])), + ), + ), + route( + name("database"), + routes( + command( + name("clean"), + toggle(name("dryRun")), + option( + name("output"), + description("output path"), + schema(z.string()), + ), + ), + ), + ), + ), +); + +type Equal = (() => T extends A ? 1 : 2) extends + (() => T extends B ? 1 : 2) + ? (() => T extends B ? 1 : 2) extends (() => T extends A ? 1 : 2) ? true + : false + : false; +type Assert = T; +type RootModelIsExact = Assert< + Equal, { verbose: boolean }> +>; +type ServeModelIsExact = Assert< + Equal[0]>, { + port: number; + host: string; + protocol: "http" | "https"; + }> +>; +type CleanModelIsExact = Assert< + Equal[1]>[0]>, { + dryRun: boolean; + output: string; + }> +>; diff --git a/examples/routes.ts b/examples/routes.ts new file mode 100644 index 0000000..dfa6eac --- /dev/null +++ b/examples/routes.ts @@ -0,0 +1,69 @@ +import { + command, + description, + name, + option, + route, + routes, + schema, + toggle, + version, +} from "../mod.ts"; +import type { ChildrenOf, ModelOf } from "../lib/types.ts"; +import { z } from "zod"; + +export const app = command( + name("simulacrum"), + description("Run and manage local service simulators."), + version("1.0.0"), + toggle(name("verbose")), + routes( + command( + name("serve"), + option(name("port"), description("server port"), schema(z.number())), + option(name("host"), description("server host"), schema(z.string())), + option( + name("protocol"), + description("server protocol"), + schema(z.enum(["http", "https"])), + ), + ), + route( + name("database"), + routes( + command( + name("clean"), + toggle(name("dryRun")), + option( + name("output"), + description("output path"), + schema(z.string()), + ), + ), + ), + ), + ), +); + +type Equal = (() => T extends A ? 1 : 2) extends + (() => T extends B ? 1 : 2) + ? (() => T extends B ? 1 : 2) extends (() => T extends A ? 1 : 2) ? true + : false + : false; +type Assert = T; +type RootModelIsExact = Assert< + Equal, { verbose: boolean }> +>; +type ServeModelIsExact = Assert< + Equal[0]>, { + port: number; + host: string; + protocol: "http" | "https"; + }> +>; +type CleanModelIsExact = Assert< + Equal[1]>[0]>, { + dryRun: boolean; + output: string; + }> +>; diff --git a/lib/checkpoint.ts b/lib/checkpoint.ts index 49e3b38..a5175ec 100644 --- a/lib/checkpoint.ts +++ b/lib/checkpoint.ts @@ -1,14 +1,10 @@ -import { dynamic, type Seed } from "./dynamic.ts"; -import type { AnyRoute } from "./types.ts"; +import { dynamic } from "./dynamic.ts"; +import type { DynamicElement } from "./pipeline.ts"; import { type ValueSource, withValues } from "./values.ts"; -export function checkpoint(): ( - route: Before, -) => ReturnType< - ReturnType>> +export function checkpoint(): DynamicElement< + ValueSource[], + ReturnType > { - return (route: Before) => - dynamic>( - (values) => withValues(values), - )(route); + return dynamic((values: ValueSource[]) => withValues(values)); } diff --git a/lib/command.ts b/lib/command.ts index bf96e6e..520e595 100644 --- a/lib/command.ts +++ b/lib/command.ts @@ -1,6 +1,5 @@ // deno-lint-ignore-file ban-types -import { extend } from "./extend.ts"; -import { executable, route } from "./route.ts"; +import type { Check, Fold, Materialize, Unary } from "./pipeline.ts"; import type { Definition, Done, Route } from "./types.ts"; export type CommandZero = Route< @@ -11,1090 +10,26 @@ export type CommandZero = Route< [Done<{}, []>] >; -export function command( - start: Definition, -): CommandZero; - -export function command( - start: Definition, - na: (value: CommandZero) => A, -): A; - -export function command( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, -): B; - -export function command( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, -): C; - -export function command( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, -): D; - -export function command( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, -): E; - -export function command( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, -): F; - export function command< const N extends string, - A, - B, - C, - D, - E, - F, - G, ->( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, -): G; - -export function command< - const N extends string, - A, - B, - C, - D, - E, - F, - G, - H, ->( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, -): H; - -export function command< - const N extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, ->( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, -): I; - -export function command< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, ->( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, -): J; - -export function command< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, ->( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, -): K; - -export function command< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, ->( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, -): L; - -export function command< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, ->( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, -): M; - -export function command< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, ->( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, -): N; - -export function command< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, ->( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, -): O; - -export function command< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, ->( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, -): P; - -export function command< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, ->( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, -): Q; - -export function command< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, ->( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, -): R; - -export function command< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, ->( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, -): S; - -export function command< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, ->( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, -): T; - -export function command< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, ->( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, -): U; - -export function command< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, ->( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, -): V; - -export function command< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, ->( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, -): W; - -export function command< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, ->( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, -): X; - -export function command< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, ->( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, -): Y; - -export function command< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, ->( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, -): Z; - -export function command< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, ->( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, -): AA; - -export function command< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, ->( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, -): AB; - -export function command< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, ->( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, -): AC; - -export function command< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, - AD, ->( - start: Definition, - na: (value: CommandZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, - acad: (value: AC) => AD, -): AD; - -export function command( - start: Definition, - ...elements: readonly ((value: never) => unknown)[] -): unknown { - return extend(elements)(route(start, executable())); + const E extends readonly Unary[], +>( + start: Definition, + ...elements: E & Check, E> +): Materialize, E>> { + let zero: CommandZero = { + ...start, + methods: ["help", "execute"], + phases: [{ + params: {}, + routes: [], + values: [], + envs: [], + }], + }; + + return elements.reduce( + (value, element) => element(value as never), + zero, + ) as Materialize, E>>; } diff --git a/lib/definition.ts b/lib/definition.ts index b781db3..d353551 100644 --- a/lib/definition.ts +++ b/lib/definition.ts @@ -1,3 +1,4 @@ +import { brand, type IdentityElement } from "./pipeline.ts"; import type { Definition } from "./types.ts"; export function name(name: N): Definition { @@ -6,9 +7,11 @@ export function name(name: N): Definition { export function description( description: string, -): >(definition: D) => D { - return (definition) => ({ +): IdentityElement> { + return brand>>(( + definition: Definition, + ) => ({ ...definition, description, - }); + })); } diff --git a/lib/dynamic.ts b/lib/dynamic.ts index 37b37b9..88f09d6 100644 --- a/lib/dynamic.ts +++ b/lib/dynamic.ts @@ -1,113 +1,36 @@ -import type { - AnyPhase, - AnyPhases, - AnyRoute, - ChildrenOf, - MethodsOf, - ModelOf, - Phase, - Route, -} from "./types.ts"; - -export function dynamic< - Before extends AnyRoute, - Requirement, - After extends AnyRoute, ->( - extension: (requires: Requirement) => (input: Seed) => After, -): (input: Before) => Conjoin { - return (route) => { - let phases = [...route.phases]; - let phase = phases.pop()!; - - phases.push({ - ...phase, - resolver: extension, - }); - phases.push({ - params: {}, - routes: [], - values: [], - envs: [], - }); - - return { ...route, phases } as unknown as Conjoin< - Before, - After, - Requirement - >; - }; -} +import type { AnyRoute } from "./types.ts"; +import { type AnyElement, brand, type DynamicElement } from "./pipeline.ts"; +export type { ConjoinPhases, Seed } from "./pipeline.ts"; export type PhaseOf = R["phases"][0]; export type PhasesOf = R["phases"]; -export type Seed = Route< - R["name"], - MethodsOf, - ModelOf, - ChildrenOf, - readonly [Phase<{}, [], never>] ->; - -type Conjoin< - A extends AnyRoute, - B extends AnyRoute, - Requirement, -> = Route< - B["name"], - MethodsOf, - ModelOf, - ChildrenOf, - ConjoinPhases ->; - -export type ConjoinPhases< - A extends AnyRoute, - B extends AnyRoute, - Requirement, -> = ConcatPhases< - ContinueLast, Requirement>, - PhasesOf ->; - -type ContinueLast< - P extends AnyPhases, - Requirement, -> = P extends readonly [infer Only extends AnyPhase] - ? readonly [WithRequirement] - : P extends readonly [ - infer Head extends AnyPhase, - ...infer Middle extends readonly AnyPhase[], - infer Last extends AnyPhase, - ] ? readonly [ - Head, - ...Middle, - WithRequirement, - ] - : never; - -type ConcatPhases< - A extends AnyPhases, - B extends AnyPhases, -> = A extends readonly [ - infer AHead extends AnyPhase, - ...infer ATail extends AnyPhase[], -] ? B extends readonly [ - infer BHead extends AnyPhase, - ...infer BTail extends AnyPhase[], - ] ? readonly [ - AHead, - ...ATail, - BHead, - ...BTail, - ] - : never - : never; - -type WithRequirement< - P extends AnyPhase, +export function dynamic< Requirement, -> = P extends Phase - ? Phase - : never; + E, +>( + extension: (requires: Requirement) => E, + ..._valid: E extends AnyElement ? [] : [never] +): DynamicElement> { + return brand>>( + (route: AnyRoute) => { + let phases = [...route.phases]; + let phase = phases.pop()!; + + phases.push({ + ...phase, + resolver: extension as unknown as ( + requirement: never, + ) => (input: never) => AnyRoute, + }); + phases.push({ + params: {}, + routes: [], + values: [], + envs: [], + }); + + return { ...route, phases }; + }, + ); +} diff --git a/lib/env.ts b/lib/env.ts index f5277f8..3ccf786 100644 --- a/lib/env.ts +++ b/lib/env.ts @@ -1,5 +1,6 @@ import type { Maybe } from "./maybe.ts"; import type { Param } from "./param.ts"; +import { brand, type IdentityElement } from "./pipeline.ts"; import type { AnyRoute, RoutePath } from "./types.ts"; export interface EnvSource { @@ -33,19 +34,21 @@ export function env( export function withEnvs( envs: readonly EnvSource[], -): (route: R) => R { - return (route) => { - let phases = [...route.phases]; - let phase = phases.pop()!; - phases.push({ - ...phase, - envs: phase.envs.concat(envs), - }); - return { - ...route, - phases, - }; - }; +): IdentityElement { + return brand>( + (route: AnyRoute) => { + let phases = [...route.phases]; + let phase = phases.pop()!; + phases.push({ + ...phase, + envs: phase.envs.concat(envs), + }); + return { + ...route, + phases, + }; + }, + ); } export class Envs { diff --git a/lib/extend.ts b/lib/extend.ts index aa18ec6..0aeb43e 100644 --- a/lib/extend.ts +++ b/lib/extend.ts @@ -1,2613 +1,12 @@ -export function extend(): (value: T) => T; +import { brand, type Extension, type Unary } from "./pipeline.ts"; -export function extend( - ab: (value: A) => B, -): (value: A) => B; - -export function extend( - ab: (value: A) => B, - bc: (value: B) => C, -): (value: A) => C; - -export function extend( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, -): (value: A) => D; - -export function extend( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, -): (value: A) => E; - -export function extend( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, -): (value: A) => F; - -export function extend( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, -): (value: A) => G; - -export function extend( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, -): (value: A) => H; - -export function extend( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, -): (value: A) => I; - -export function extend( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, -): (value: A) => J; - -export function extend( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, -): (value: A) => K; - -export function extend( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, -): (value: A) => L; - -export function extend( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, -): (value: A) => M; - -export function extend( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, -): (value: A) => N; - -export function extend( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, -): (value: A) => O; - -export function extend( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, -): (value: A) => P; - -export function extend( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, -): (value: A) => Q; - -export function extend( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, -): (value: A) => R; - -export function extend( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, -): (value: A) => S; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, -): (value: A) => T; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, -): (value: A) => U; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, -): (value: A) => V; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, -): (value: A) => W; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, -): (value: A) => X; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, -): (value: A) => Y; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, -): (value: A) => Z; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, -): (value: A) => AA; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, -): (value: A) => AB; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, -): (value: A) => AC; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, - AD, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, - acad: (value: AC) => AD, -): (value: A) => AD; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, - AD, - AE, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, - acad: (value: AC) => AD, - adae: (value: AD) => AE, -): (value: A) => AE; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, - AD, - AE, - AF, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, - acad: (value: AC) => AD, - adae: (value: AD) => AE, - aeaf: (value: AE) => AF, -): (value: A) => AF; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, - AD, - AE, - AF, - AG, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, - acad: (value: AC) => AD, - adae: (value: AD) => AE, - aeaf: (value: AE) => AF, - afag: (value: AF) => AG, -): (value: A) => AG; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, - AD, - AE, - AF, - AG, - AH, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, - acad: (value: AC) => AD, - adae: (value: AD) => AE, - aeaf: (value: AE) => AF, - afag: (value: AF) => AG, - agah: (value: AG) => AH, -): (value: A) => AH; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, - AD, - AE, - AF, - AG, - AH, - AI, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, - acad: (value: AC) => AD, - adae: (value: AD) => AE, - aeaf: (value: AE) => AF, - afag: (value: AF) => AG, - agah: (value: AG) => AH, - ahai: (value: AH) => AI, -): (value: A) => AI; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, - AD, - AE, - AF, - AG, - AH, - AI, - AJ, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, - acad: (value: AC) => AD, - adae: (value: AD) => AE, - aeaf: (value: AE) => AF, - afag: (value: AF) => AG, - agah: (value: AG) => AH, - ahai: (value: AH) => AI, - aiaj: (value: AI) => AJ, -): (value: A) => AJ; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, - AD, - AE, - AF, - AG, - AH, - AI, - AJ, - AK, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, - acad: (value: AC) => AD, - adae: (value: AD) => AE, - aeaf: (value: AE) => AF, - afag: (value: AF) => AG, - agah: (value: AG) => AH, - ahai: (value: AH) => AI, - aiaj: (value: AI) => AJ, - ajak: (value: AJ) => AK, -): (value: A) => AK; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, - AD, - AE, - AF, - AG, - AH, - AI, - AJ, - AK, - AL, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, - acad: (value: AC) => AD, - adae: (value: AD) => AE, - aeaf: (value: AE) => AF, - afag: (value: AF) => AG, - agah: (value: AG) => AH, - ahai: (value: AH) => AI, - aiaj: (value: AI) => AJ, - ajak: (value: AJ) => AK, - akal: (value: AK) => AL, -): (value: A) => AL; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, - AD, - AE, - AF, - AG, - AH, - AI, - AJ, - AK, - AL, - AM, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, - acad: (value: AC) => AD, - adae: (value: AD) => AE, - aeaf: (value: AE) => AF, - afag: (value: AF) => AG, - agah: (value: AG) => AH, - ahai: (value: AH) => AI, - aiaj: (value: AI) => AJ, - ajak: (value: AJ) => AK, - akal: (value: AK) => AL, - alam: (value: AL) => AM, -): (value: A) => AM; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, - AD, - AE, - AF, - AG, - AH, - AI, - AJ, - AK, - AL, - AM, - AN, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, - acad: (value: AC) => AD, - adae: (value: AD) => AE, - aeaf: (value: AE) => AF, - afag: (value: AF) => AG, - agah: (value: AG) => AH, - ahai: (value: AH) => AI, - aiaj: (value: AI) => AJ, - ajak: (value: AJ) => AK, - akal: (value: AK) => AL, - alam: (value: AL) => AM, - aman: (value: AM) => AN, -): (value: A) => AN; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, - AD, - AE, - AF, - AG, - AH, - AI, - AJ, - AK, - AL, - AM, - AN, - AO, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, - acad: (value: AC) => AD, - adae: (value: AD) => AE, - aeaf: (value: AE) => AF, - afag: (value: AF) => AG, - agah: (value: AG) => AH, - ahai: (value: AH) => AI, - aiaj: (value: AI) => AJ, - ajak: (value: AJ) => AK, - akal: (value: AK) => AL, - alam: (value: AL) => AM, - aman: (value: AM) => AN, - anao: (value: AN) => AO, -): (value: A) => AO; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, - AD, - AE, - AF, - AG, - AH, - AI, - AJ, - AK, - AL, - AM, - AN, - AO, - AP, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, - acad: (value: AC) => AD, - adae: (value: AD) => AE, - aeaf: (value: AE) => AF, - afag: (value: AF) => AG, - agah: (value: AG) => AH, - ahai: (value: AH) => AI, - aiaj: (value: AI) => AJ, - ajak: (value: AJ) => AK, - akal: (value: AK) => AL, - alam: (value: AL) => AM, - aman: (value: AM) => AN, - anao: (value: AN) => AO, - aoap: (value: AO) => AP, -): (value: A) => AP; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, - AD, - AE, - AF, - AG, - AH, - AI, - AJ, - AK, - AL, - AM, - AN, - AO, - AP, - AQ, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, - acad: (value: AC) => AD, - adae: (value: AD) => AE, - aeaf: (value: AE) => AF, - afag: (value: AF) => AG, - agah: (value: AG) => AH, - ahai: (value: AH) => AI, - aiaj: (value: AI) => AJ, - ajak: (value: AJ) => AK, - akal: (value: AK) => AL, - alam: (value: AL) => AM, - aman: (value: AM) => AN, - anao: (value: AN) => AO, - aoap: (value: AO) => AP, - apaq: (value: AP) => AQ, -): (value: A) => AQ; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, - AD, - AE, - AF, - AG, - AH, - AI, - AJ, - AK, - AL, - AM, - AN, - AO, - AP, - AQ, - AR, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, - acad: (value: AC) => AD, - adae: (value: AD) => AE, - aeaf: (value: AE) => AF, - afag: (value: AF) => AG, - agah: (value: AG) => AH, - ahai: (value: AH) => AI, - aiaj: (value: AI) => AJ, - ajak: (value: AJ) => AK, - akal: (value: AK) => AL, - alam: (value: AL) => AM, - aman: (value: AM) => AN, - anao: (value: AN) => AO, - aoap: (value: AO) => AP, - apaq: (value: AP) => AQ, - aqar: (value: AQ) => AR, -): (value: A) => AR; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, - AD, - AE, - AF, - AG, - AH, - AI, - AJ, - AK, - AL, - AM, - AN, - AO, - AP, - AQ, - AR, - AS, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, - acad: (value: AC) => AD, - adae: (value: AD) => AE, - aeaf: (value: AE) => AF, - afag: (value: AF) => AG, - agah: (value: AG) => AH, - ahai: (value: AH) => AI, - aiaj: (value: AI) => AJ, - ajak: (value: AJ) => AK, - akal: (value: AK) => AL, - alam: (value: AL) => AM, - aman: (value: AM) => AN, - anao: (value: AN) => AO, - aoap: (value: AO) => AP, - apaq: (value: AP) => AQ, - aqar: (value: AQ) => AR, - aras: (value: AR) => AS, -): (value: A) => AS; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, - AD, - AE, - AF, - AG, - AH, - AI, - AJ, - AK, - AL, - AM, - AN, - AO, - AP, - AQ, - AR, - AS, - AT, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, - acad: (value: AC) => AD, - adae: (value: AD) => AE, - aeaf: (value: AE) => AF, - afag: (value: AF) => AG, - agah: (value: AG) => AH, - ahai: (value: AH) => AI, - aiaj: (value: AI) => AJ, - ajak: (value: AJ) => AK, - akal: (value: AK) => AL, - alam: (value: AL) => AM, - aman: (value: AM) => AN, - anao: (value: AN) => AO, - aoap: (value: AO) => AP, - apaq: (value: AP) => AQ, - aqar: (value: AQ) => AR, - aras: (value: AR) => AS, - asat: (value: AS) => AT, -): (value: A) => AT; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, - AD, - AE, - AF, - AG, - AH, - AI, - AJ, - AK, - AL, - AM, - AN, - AO, - AP, - AQ, - AR, - AS, - AT, - AU, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, - acad: (value: AC) => AD, - adae: (value: AD) => AE, - aeaf: (value: AE) => AF, - afag: (value: AF) => AG, - agah: (value: AG) => AH, - ahai: (value: AH) => AI, - aiaj: (value: AI) => AJ, - ajak: (value: AJ) => AK, - akal: (value: AK) => AL, - alam: (value: AL) => AM, - aman: (value: AM) => AN, - anao: (value: AN) => AO, - aoap: (value: AO) => AP, - apaq: (value: AP) => AQ, - aqar: (value: AQ) => AR, - aras: (value: AR) => AS, - asat: (value: AS) => AT, - atau: (value: AT) => AU, -): (value: A) => AU; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, - AD, - AE, - AF, - AG, - AH, - AI, - AJ, - AK, - AL, - AM, - AN, - AO, - AP, - AQ, - AR, - AS, - AT, - AU, - AV, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, - acad: (value: AC) => AD, - adae: (value: AD) => AE, - aeaf: (value: AE) => AF, - afag: (value: AF) => AG, - agah: (value: AG) => AH, - ahai: (value: AH) => AI, - aiaj: (value: AI) => AJ, - ajak: (value: AJ) => AK, - akal: (value: AK) => AL, - alam: (value: AL) => AM, - aman: (value: AM) => AN, - anao: (value: AN) => AO, - aoap: (value: AO) => AP, - apaq: (value: AP) => AQ, - aqar: (value: AQ) => AR, - aras: (value: AR) => AS, - asat: (value: AS) => AT, - atau: (value: AT) => AU, - auav: (value: AU) => AV, -): (value: A) => AV; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, - AD, - AE, - AF, - AG, - AH, - AI, - AJ, - AK, - AL, - AM, - AN, - AO, - AP, - AQ, - AR, - AS, - AT, - AU, - AV, - AW, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, - acad: (value: AC) => AD, - adae: (value: AD) => AE, - aeaf: (value: AE) => AF, - afag: (value: AF) => AG, - agah: (value: AG) => AH, - ahai: (value: AH) => AI, - aiaj: (value: AI) => AJ, - ajak: (value: AJ) => AK, - akal: (value: AK) => AL, - alam: (value: AL) => AM, - aman: (value: AM) => AN, - anao: (value: AN) => AO, - aoap: (value: AO) => AP, - apaq: (value: AP) => AQ, - aqar: (value: AQ) => AR, - aras: (value: AR) => AS, - asat: (value: AS) => AT, - atau: (value: AT) => AU, - auav: (value: AU) => AV, - avaw: (value: AV) => AW, -): (value: A) => AW; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, - AD, - AE, - AF, - AG, - AH, - AI, - AJ, - AK, - AL, - AM, - AN, - AO, - AP, - AQ, - AR, - AS, - AT, - AU, - AV, - AW, - AX, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, - acad: (value: AC) => AD, - adae: (value: AD) => AE, - aeaf: (value: AE) => AF, - afag: (value: AF) => AG, - agah: (value: AG) => AH, - ahai: (value: AH) => AI, - aiaj: (value: AI) => AJ, - ajak: (value: AJ) => AK, - akal: (value: AK) => AL, - alam: (value: AL) => AM, - aman: (value: AM) => AN, - anao: (value: AN) => AO, - aoap: (value: AO) => AP, - apaq: (value: AP) => AQ, - aqar: (value: AQ) => AR, - aras: (value: AR) => AS, - asat: (value: AS) => AT, - atau: (value: AT) => AU, - auav: (value: AU) => AV, - avaw: (value: AV) => AW, - awax: (value: AW) => AX, -): (value: A) => AX; - -export function extend< - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, - AD, - AE, - AF, - AG, - AH, - AI, - AJ, - AK, - AL, - AM, - AN, - AO, - AP, - AQ, - AR, - AS, - AT, - AU, - AV, - AW, - AX, - AY, ->( - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, - acad: (value: AC) => AD, - adae: (value: AD) => AE, - aeaf: (value: AE) => AF, - afag: (value: AF) => AG, - agah: (value: AG) => AH, - ahai: (value: AH) => AI, - aiaj: (value: AI) => AJ, - ajak: (value: AJ) => AK, - akal: (value: AK) => AL, - alam: (value: AL) => AM, - aman: (value: AM) => AN, - anao: (value: AN) => AO, - aoap: (value: AO) => AP, - apaq: (value: AP) => AQ, - aqar: (value: AQ) => AR, - aras: (value: AR) => AS, - asat: (value: AS) => AT, - atau: (value: AT) => AU, - auav: (value: AU) => AV, - avaw: (value: AV) => AW, - awax: (value: AW) => AX, - axay: (value: AX) => AY, -): (value: A) => AY; - -export function extend( - elements: readonly ((value: never) => unknown)[], -): (value: unknown) => unknown; - -export function extend( - ...input: - | readonly ((value: never) => unknown)[] - | readonly [readonly ((value: never) => unknown)[]] -): unknown { - let elements = input.length === 1 && Array.isArray(input[0]) - ? input[0] as readonly ((value: never) => unknown)[] - : input as readonly ((value: never) => unknown)[]; - return (start: unknown): unknown => +export function extend( + ...elements: E +): Extension { + return brand>((start: unknown): unknown => elements.reduce( (value, element) => element(value as never), start, - ); + ) + ); } diff --git a/lib/option.ts b/lib/option.ts index 0be5730..d96d396 100644 --- a/lib/option.ts +++ b/lib/option.ts @@ -1,37 +1,30 @@ -import { extend } from "./extend.ts"; import { type Param, param } from "./param.ts"; import { dasherize } from "./dasherize.ts"; +import { brand, type ParamElement } from "./pipeline.ts"; import { cli } from "./read.ts"; -import type { - AddParamToLast, - AnyPhases, - AnyRoute, - Definition, - Method, - Route, -} from "./types.ts"; +import type { AnyRoute, Definition } from "./types.ts"; export function option( named: Definition, -): Option; +): ParamElement; export function option( named: Definition, nt: (value: Param) => Param, -): Option; +): ParamElement; export function option( named: Definition, na: (value: Param) => A, at: (value: A) => Param, -): Option; +): ParamElement; export function option( named: Definition, na: (value: Param) => A, ab: (value: A) => B, bt: (value: B) => Param, -): Option; +): ParamElement; export function option( named: Definition, @@ -39,7 +32,7 @@ export function option( ab: (value: A) => B, bc: (value: B) => C, ct: (value: C) => Param, -): Option; +): ParamElement; export function option( named: Definition, @@ -48,7 +41,7 @@ export function option( bc: (value: B) => C, cd: (value: C) => D, dt: (value: D) => Param, -): Option; +): ParamElement; export function option( named: Definition, @@ -58,7 +51,7 @@ export function option( cd: (value: C) => D, de: (value: D) => E, et: (value: E) => Param, -): Option; +): ParamElement; export function option( named: Definition, @@ -69,7 +62,7 @@ export function option( de: (value: D) => E, ef: (value: E) => F, ft: (value: F) => Param, -): Option; +): ParamElement; export function option< const N extends string, @@ -91,7 +84,7 @@ export function option< ef: (value: E) => F, fg: (value: F) => G, gt: (value: G) => Param, -): Option; +): ParamElement; export function option< const N extends string, @@ -115,17 +108,18 @@ export function option< fg: (value: F) => G, gh: (value: G) => H, ht: (value: H) => Param, -): Option; +): ParamElement; export function option( named: Definition, ...elements: readonly ((value: never) => unknown)[] ): unknown { - const added = extend(elements)( + const added = elements.reduce( + (value, element) => element(value as never), param(named, cli([`--${dasherize(named.name)}`])), ) as Param; - return (route: AnyRoute) => { + return brand>((route: AnyRoute) => { let phases = [...route.phases]; let phase = phases.pop()!; phases.push({ @@ -139,25 +133,5 @@ export function option( ...route, phases: phases, }; - }; + }); } - -type Option = < - const N extends string, - const M extends Method, - const T extends object, - const C extends readonly AnyRoute[], - const P extends AnyPhases, ->( - route: Route, -) => Route< - N, - M, - { - [P in keyof ({ [Q in K]: V } & T)]: ( - { [Q in K]: V } & T - )[P]; - }, - C, - AddParamToLast ->; diff --git a/lib/param.ts b/lib/param.ts index 0897d19..b14bdd3 100644 --- a/lib/param.ts +++ b/lib/param.ts @@ -1,5 +1,4 @@ import { type Decoder, scalar } from "./decode.ts"; -import { extend } from "./extend.ts"; import type { ReadCLI } from "./read.ts"; import type { AnyToken } from "./tokenize.ts"; import type { TokenInput } from "./tokenizer.ts"; @@ -147,7 +146,10 @@ export function param( }), decode: scalar, }; - return extend(elements)(zero); + return elements.reduce( + (value, element) => element(value as never), + zero, + ); } export function schema( diff --git a/lib/pipeline.ts b/lib/pipeline.ts new file mode 100644 index 0000000..cf45dcd --- /dev/null +++ b/lib/pipeline.ts @@ -0,0 +1,533 @@ +import type { + AddField, + AddParamToLast, + AddRoutesToLast, + AnyPhase, + AnyPhases, + AnyRoute, + ChildrenOf, + Done, + Method, + MethodsOf, + ModelOf, + Next, + Phase, + Route, +} from "./types.ts"; + +export type Element = { + readonly [operation]: D; +}; + +export type AnyElement = Element; + +export type Unary = (value: never) => unknown; + +export type AnyPipelineElement = Unary; + +export interface Transform { + readonly input: unknown; + readonly output: unknown; +} + +export type ApplyTransform = ( + F & { readonly input: S } +)["output"]; + +export interface TransformElement { + readonly [operation]: Custom; + + (value: S): ApplyTransform; +} + +export interface IdentityElement { + readonly [operation]: Identity; + + (value: T): T; +} + +export interface MethodElement { + readonly [operation]: AddMethod; + + < + const N extends string, + const M extends Method, + const T extends object, + const C extends readonly AnyRoute[], + const P extends AnyPhases, + >(route: Route): Route; +} + +export interface ParamElement { + readonly [operation]: AddParam; + + < + const N extends string, + const M extends Method, + const T extends object, + const C extends readonly AnyRoute[], + const P extends AnyPhases, + >(route: Route): Route< + N, + M, + AddField, + C, + AddParamToLast + >; +} + +export interface RoutesElement { + readonly [operation]: AddRoutes; + + < + const N extends string, + const M extends Method, + const T extends object, + const C extends readonly AnyRoute[], + const P extends AnyPhases, + >(route: Route): Route< + N, + M, + T, + readonly [...C, ...Added], + AddRoutesToLast + >; +} + +export type Extension = + & Element> + & (E extends readonly AnyElement[] ? GenericExtension + : ConcreteExtension); + +interface GenericExtension { + readonly [operation]: Batch; + + >(value: S): Fold; +} + +interface ConcreteExtension { + ( + value: [Fold, E>] extends [never] ? never + : InputOfPipeline, + ): Fold, E>; +} + +export interface DynamicElement { + readonly [operation]: Dynamic; + + (route: R): Apply>; +} + +export type Seed = Route< + R["name"], + MethodsOf, + ModelOf, + ChildrenOf, + readonly [Phase<{}, [], never>] +>; + +export type ConjoinPhases< + A extends AnyRoute, + B extends AnyRoute, + Requirement, +> = ConcatPhases< + ContinueLast, + B["phases"] +>; + +export type Fold< + S, + E extends readonly AnyPipelineElement[], +> = number extends E["length"] ? Conservative + : E extends readonly [ + infer Head extends AnyPipelineElement, + ...infer Tail extends readonly AnyPipelineElement[], + ] ? true extends IsUnion ? Conservative + : Head extends AnyElement + ? S extends AnyRoute + ? DeltaOf extends StaticDelta ? TakeStatic extends readonly [ + infer Fields extends object, + infer Routes extends readonly AnyRoute[], + infer Methods extends Method, + infer Rest extends readonly AnyPipelineElement[], + ] ? Fold, Rest> + : never + : Fold>, Tail> + : Fold>, Tail> + : Head extends (value: S) => infer Output ? Fold + : never + : S; + +export type Materialize = AnyRoute extends S ? S + : S extends Route< + infer N, + infer M, + infer Model, + infer Children, + infer Phases + > ? WithExtras< + Route< + N, + M, + { [K in keyof Model]: Model[K] }, + Children, + { + [K in keyof Phases]: Phases[K] extends AnyPhase + ? MaterializePhase + : Phases[K]; + } + >, + S + > + : S; + +export type Check< + S, + E extends readonly AnyPipelineElement[], +> = number extends E["length"] ? unknown + : E extends readonly AnyElement[] + ? [Fold] extends [never] ? CheckMixed + : E + : CheckMixed; + +export function mark( + element: (value: never) => unknown, +): TransformElement { + return element as TransformElement; +} + +export function brand(element: unknown): E { + return element as E; +} + +declare const operation: unique symbol; + +type Delta = + | Identity + | AddMethod + | AddParam + | AddRoutes + | Batch + | Dynamic + | Custom; + +type StaticDelta = + | Identity + | AddMethod + | AddParam + | AddRoutes; + +interface Identity { + readonly type: "identity"; + readonly input: S; +} + +interface AddMethod { + readonly type: "method"; + readonly method: M; +} + +interface AddParam { + readonly type: "param"; + readonly key: K; + readonly value: V; +} + +interface AddRoutes { + readonly type: "routes"; + readonly children: C; +} + +interface Batch { + readonly type: "batch"; + readonly elements: E; +} + +interface Dynamic { + readonly type: "dynamic"; + readonly requirement: Requirement; + readonly element: E; +} + +interface Custom { + readonly type: "custom"; + readonly transform: F; +} + +type Apply = Delta extends D ? Conservative + : D extends Identity ? S extends Input ? S : never + : D extends AddMethod ? S extends AnyRoute ? Route< + S["name"], + MethodsOf | M, + ModelOf, + ChildrenOf, + S["phases"] + > + : never + : D extends AddParam + ? S extends AnyRoute ? WithParams + : never + : D extends AddRoutes ? S extends AnyRoute ? Route< + S["name"], + MethodsOf, + ModelOf, + readonly [...ChildrenOf, ...C], + AddRoutesToLast + > + : never + : D extends Batch ? Fold + : D extends Dynamic + ? S extends AnyRoute + ? Apply, DeltaOf> extends infer After extends AnyRoute ? Route< + After["name"], + MethodsOf, + ModelOf, + ChildrenOf, + ConjoinPhases + > + : never + : never + : D extends Custom ? S extends F["input"] ? ApplyTransform + : never + : never; + +type DeltaOf = E[typeof operation]; + +type IsUnion = T extends unknown ? [Whole] extends [T] ? false + : true + : never; + +type Conservative = S extends AnyRoute ? AnyRoute : unknown; + +type CheckMixed< + S, + E extends readonly AnyPipelineElement[], +> = E extends readonly [] ? E + : E extends readonly [ + infer Head extends AnyPipelineElement, + ...infer Tail extends readonly AnyPipelineElement[], + ] ? [Fold] extends [never] ? readonly [never, ...Tail] + : readonly [Head, ...CheckTail, Tail>] + : never; + +type CheckTail< + S, + E extends readonly AnyPipelineElement[], +> = number extends E["length"] ? E + : E extends readonly AnyElement[] ? E + : CheckMixed; + +type InputOf = E extends AnyElement + ? InputOfDelta> + : E extends (value: infer Input) => unknown ? Input + : never; + +type InputOfPipeline = E extends + readonly [ + infer Head extends AnyPipelineElement, + ...readonly AnyPipelineElement[], + ] ? InputOf + : unknown; + +type InputOfDelta = D extends Identity ? Input + : D extends + | AddMethod + | AddParam + | AddRoutes< + readonly AnyRoute[] + > + | Dynamic ? AnyRoute + : D extends Batch ? InputOfPipeline + : D extends Custom ? F["input"] + : never; + +type TakeStatic< + E extends readonly AnyPipelineElement[], + Fields extends readonly object[] = readonly [], + Routes extends readonly AnyRoute[] = readonly [], + Methods extends Method = never, + Count extends readonly unknown[] = readonly [], +> = Count["length"] extends 20 + ? readonly [MergeFields, Routes, Methods, E] + : E extends readonly [ + infer Head extends AnyElement, + ...infer Tail extends readonly AnyPipelineElement[], + ] ? DeltaOf extends Identity ? TakeStatic< + Tail, + Fields, + Routes, + Methods, + readonly [...Count, unknown] + > + : DeltaOf extends AddMethod ? TakeStatic< + Tail, + Fields, + Routes, + Methods | M, + readonly [...Count, unknown] + > + : DeltaOf extends AddParam ? TakeStatic< + Tail, + readonly [...Fields, { [P in K]: V }], + Routes, + Methods, + readonly [...Count, unknown] + > + : DeltaOf extends AddRoutes ? TakeStatic< + Tail, + Fields, + readonly [...Routes, ...C], + Methods, + readonly [...Count, unknown] + > + : readonly [MergeFields, Routes, Methods, E] + : readonly [MergeFields, Routes, Methods, E]; + +// Collect fields before applying them so each static chunk builds the model once. +type MergeFields = { + [K in FieldKeys]: FieldValue; +}; + +type FieldKeys = F extends readonly [ + infer Head extends object, + ...infer Tail extends readonly object[], +] ? keyof Head | FieldKeys + : never; + +type FieldValue< + F extends readonly object[], + K extends PropertyKey, +> = F extends readonly [ + ...infer Rest extends readonly object[], + infer Last extends object, +] ? K extends keyof Last ? Last[K] : FieldValue + : never; + +type WithStatic< + R extends AnyRoute, + Fields extends object, + Routes extends readonly AnyRoute[], + Methods extends Method, +> = Route< + R["name"], + MethodsOf | Methods, + keyof Fields extends never ? ModelOf : Merge, Fields>, + Routes extends readonly [] ? ChildrenOf + : readonly [...ChildrenOf, ...Routes], + AddStaticToLast +>; + +type AddStaticToLast< + P extends AnyPhases, + Fields extends object, + Routes extends readonly AnyRoute[], +> = keyof Fields extends never ? Routes extends readonly [] ? P + : AddRoutesToLast + : Routes extends readonly [] ? AddFieldsToLast + : AddRoutesToLast, Routes>; + +type WithParams = Route< + R["name"], + MethodsOf, + Merge, Fields>, + ChildrenOf, + AddFieldsToLast +>; + +type AddFieldsToLast< + P extends AnyPhases, + Fields extends object, +> = P extends readonly [infer Only extends AnyPhase] + ? readonly [AddFields] + : P extends readonly [ + infer First extends AnyPhase, + ...infer Middle extends AnyPhase[], + AnyPhase, + ] ? readonly [First, ...Middle, AddFields, Fields>] + : never; + +type AddFields

= P extends Next< + infer Model, + infer Routes, + infer Requirement +> ? Next, Routes, Requirement> + : P extends Done ? Done< + Merge, + Routes + > + : never; + +type Last

= P extends readonly [ + ...AnyPhase[], + infer Tail extends AnyPhase, +] ? Tail + : never; + +type ContinueLast< + P extends AnyPhases, + Requirement, +> = P extends readonly [infer Only extends AnyPhase] + ? readonly [WithRequirement] + : P extends readonly [ + infer Head extends AnyPhase, + ...infer Middle extends readonly AnyPhase[], + infer Tail extends AnyPhase, + ] ? readonly [ + Head, + ...Middle, + WithRequirement, + ] + : never; + +type ConcatPhases< + A extends AnyPhases, + B extends AnyPhases, +> = A extends readonly [ + infer AHead extends AnyPhase, + ...infer ATail extends AnyPhase[], +] ? B extends readonly [ + infer BHead extends AnyPhase, + ...infer BTail extends AnyPhase[], + ] ? readonly [AHead, ...ATail, BHead, ...BTail] + : never + : never; + +type WithRequirement< + P extends AnyPhase, + Requirement, +> = P extends Phase + ? Phase + : never; + +type Merge = Simplify< + Omit & B +>; + +type Simplify = { [K in keyof T]: T[K] }; + +type MaterializePhase

= P extends Next< + infer Model, + infer Routes, + infer Requirement +> ? Next< + { [K in keyof Model]: Model[K] }, + Routes, + Requirement + > + : P extends Done ? Done< + { [K in keyof Model]: Model[K] }, + Routes + > + : never; + +type RouteKeys = keyof Route< + string, + Method, + object, + readonly AnyRoute[], + AnyPhases +>; + +type WithExtras = keyof Omit extends never + ? R + : R & Omit; diff --git a/lib/route.ts b/lib/route.ts index fa73678..e6be542 100644 --- a/lib/route.ts +++ b/lib/route.ts @@ -1,17 +1,14 @@ // deno-lint-ignore-file ban-types -import { extend } from "./extend.ts"; -import type { - AddRoutesToLast, - AnyPhases, - AnyRoute, - ChildrenOf, - Definition, - Done, - Method, - MethodsOf, - ModelOf, - Route, -} from "./types.ts"; +import { + brand, + type Check, + type Fold, + type Materialize, + type MethodElement, + type RoutesElement, + type Unary, +} from "./pipeline.ts"; +import type { AnyRoute, Definition, Done, Route } from "./types.ts"; export type RouteZero = Route< N, @@ -21,1092 +18,14 @@ export type RouteZero = Route< [Done<{}, []>] >; -export function route( - start: Definition, -): RouteZero; - -export function route( - start: Definition, - na: (value: RouteZero) => A, -): A; - -export function route( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, -): B; - -export function route( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, -): C; - -export function route( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, -): D; - -export function route( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, -): E; - -export function route( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, -): F; - -export function route< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, ->( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, -): G; - -export function route< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, ->( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, -): H; - -export function route< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, ->( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, -): I; - -export function route< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, ->( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, -): J; - -export function route< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, ->( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, -): K; - -export function route< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, ->( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, -): L; - -export function route< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, ->( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, -): M; - -export function route< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, ->( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, -): N; - -export function route< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, ->( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, -): O; - -export function route< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, ->( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, -): P; - -export function route< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, ->( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, -): Q; - -export function route< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, ->( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, -): R; - -export function route< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, ->( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, -): S; - export function route< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, ->( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, -): T; - -export function route< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, ->( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, -): U; - -export function route< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, ->( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, -): V; - -export function route< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, ->( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, -): W; - -export function route< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, ->( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, -): X; - -export function route< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, ->( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, -): Y; - -export function route< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, ->( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, -): Z; - -export function route< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, ->( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, -): AA; - -export function route< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, ->( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, -): AB; - -export function route< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, ->( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, -): AC; - -export function route< - const Name extends string, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - AA, - AB, - AC, - AD, + const N extends string, + const E extends readonly Unary[], >( - start: Definition, - na: (value: RouteZero) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - fg: (value: F) => G, - gh: (value: G) => H, - hi: (value: H) => I, - ij: (value: I) => J, - jk: (value: J) => K, - kl: (value: K) => L, - lm: (value: L) => M, - mn: (value: M) => N, - no: (value: N) => O, - op: (value: O) => P, - pq: (value: P) => Q, - qr: (value: Q) => R, - rs: (value: R) => S, - st: (value: S) => T, - tu: (value: T) => U, - uv: (value: U) => V, - vw: (value: V) => W, - wx: (value: W) => X, - xy: (value: X) => Y, - yz: (value: Y) => Z, - zaa: (value: Z) => AA, - aaab: (value: AA) => AB, - abac: (value: AB) => AC, - acad: (value: AC) => AD, -): AD; - -export function route( - start: Definition, - ...elements: readonly ((value: never) => unknown)[] -): unknown { - let zero: RouteZero = { + start: Definition, + ...elements: E & Check, E> +): Materialize, E>> { + let zero: RouteZero = { ...start, methods: ["help"], phases: [{ @@ -1116,66 +35,42 @@ export function route( envs: [], }], }; - return extend(elements)(zero); + + return elements.reduce( + (value, element) => element(value as never), + zero, + ) as Materialize, E>>; } -export function version( - semver: string, -): < - const N extends string, - const M extends Method, - const T extends object, - const C extends readonly AnyRoute[], - const P extends AnyPhases, ->(route: Route) => Route { - return (route) => ({ +export function version(semver: string): MethodElement<"version"> { + return brand>((route: AnyRoute) => ({ ...route, methods: [...route.methods, "version"] as const, version: semver, - }); + })); } -export function executable(): < - const N extends string, - const M extends Method, - const T extends object, - const C extends readonly AnyRoute[], - const P extends AnyPhases, ->(route: Route) => Route { - return (route) => ({ +export function executable(): MethodElement<"execute"> { + return brand>((route: AnyRoute) => ({ ...route, methods: [...route.methods, "execute"] as const, - }); + })); } export function routes( ...children: C -): ( - route: R, -) => Route< - R["name"], - MethodsOf, - ModelOf, - readonly [...ChildrenOf, ...C], - AddRoutesToLast -> { - return (route: R) => { - type Output = Route< - R["name"], - MethodsOf, - ModelOf, - readonly [...ChildrenOf, ...C], - AddRoutesToLast - >; +): RoutesElement { + return brand>((route: AnyRoute) => { let phases = [...route.phases]; let phase = phases.pop()!; phases.push({ ...phase, routes: [...phase.routes, ...children], }); + return { ...route, - phases: phases as unknown as Output["phases"], + phases, }; - }; + }); } diff --git a/lib/toggle.ts b/lib/toggle.ts index 8faaf9f..0ae9e03 100644 --- a/lib/toggle.ts +++ b/lib/toggle.ts @@ -1,39 +1,32 @@ import { boolean as decode } from "./decode.ts"; import { dasherize } from "./dasherize.ts"; import { type Param, param, schema } from "./param.ts"; +import { brand, type ParamElement } from "./pipeline.ts"; import type { CLIRead, ReadCLI } from "./read.ts"; import type { Flag } from "./tokenize.ts"; -import type { - AddParamToLast, - AnyPhases, - AnyRoute, - Definition, - Method, - Route, - Schema, -} from "./types.ts"; +import type { AnyRoute, Definition, Schema } from "./types.ts"; export function toggle( named: Definition, -): Toggle; +): ParamElement; export function toggle( named: Definition, nt: (value: Param) => Param, -): Toggle; +): ParamElement; export function toggle( named: Definition, na: (value: Param) => A, at: (value: A) => Param, -): Toggle; +): ParamElement; export function toggle( named: Definition, na: (value: Param) => A, ab: (value: A) => B, bt: (value: B) => Param, -): Toggle; +): ParamElement; export function toggle( named: Definition, @@ -41,7 +34,7 @@ export function toggle( ab: (value: A) => B, bc: (value: B) => C, ct: (value: C) => Param, -): Toggle; +): ParamElement; export function toggle( named: Definition, @@ -50,7 +43,7 @@ export function toggle( bc: (value: B) => C, cd: (value: C) => D, dt: (value: D) => Param, -): Toggle; +): ParamElement; export function toggle( named: Definition, @@ -60,7 +53,7 @@ export function toggle( cd: (value: C) => D, de: (value: D) => E, et: (value: E) => Param, -): Toggle; +): ParamElement; export function toggle( named: Definition, @@ -71,7 +64,7 @@ export function toggle( de: (value: D) => E, ef: (value: E) => F, ft: (value: F) => Param, -): Toggle; +): ParamElement; export function toggle< const N extends string, @@ -93,7 +86,7 @@ export function toggle< ef: (value: E) => F, fg: (value: F) => G, gt: (value: G) => Param, -): Toggle; +): ParamElement; export function toggle< const N extends string, @@ -117,7 +110,7 @@ export function toggle< fg: (value: F) => G, gh: (value: G) => H, ht: (value: H) => Param, -): Toggle; +): ParamElement; export function toggle( named: Definition, @@ -131,7 +124,7 @@ export function toggle( }, ) as Param; - return (route: AnyRoute) => { + return brand>((route: AnyRoute) => { let phases = [...route.phases]; let phase = phases.pop()!; phases.push({ @@ -146,29 +139,9 @@ export function toggle( ...route, phases, }; - }; + }); } -type Toggle = < - const N extends string, - const M extends Method, - const T extends object, - const C extends readonly AnyRoute[], - const P extends AnyPhases, ->( - route: Route, -) => Route< - N, - M, - { - [P in keyof ({ [Q in K]: V } & T)]: ( - { [Q in K]: V } & T - )[P]; - }, - C, - AddParamToLast ->; - function binding( name: string, ):

>(param: P) => P { diff --git a/lib/types.ts b/lib/types.ts index 1076880..c58e8c1 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -308,20 +308,12 @@ type AddParam< infer Routes, infer Requirement > ? Next< - { - [Key in keyof ({ [Added in K]: V } & Model)]: ( - { [Added in K]: V } & Model - )[Key]; - }, + AddField, Routes, Requirement > : P extends Done ? Done< - { - [Key in keyof ({ [Added in K]: V } & Model)]: ( - { [Added in K]: V } & Model - )[Key]; - }, + AddField, Routes > : never; @@ -378,11 +370,11 @@ type AddRoutes< > : never; -export type AddField = { - [P in keyof ({ [Q in K]: V } & T)]: ( - { [Q in K]: V } & T - )[P]; -}; +export type AddField = Simplify< + Omit & { [P in K]: V } +>; + +type Simplify = { [P in keyof T]: T[P] }; type IntentsAt< R extends AnyRoute, diff --git a/lib/values.ts b/lib/values.ts index 6985722..4771f07 100644 --- a/lib/values.ts +++ b/lib/values.ts @@ -1,4 +1,5 @@ import type { Maybe } from "./maybe.ts"; +import { brand, type IdentityElement } from "./pipeline.ts"; import type { AnyRoute, RoutePath } from "./types.ts"; export type ValueSource = { @@ -8,19 +9,21 @@ export type ValueSource = { export function withValues( values: readonly ValueSource[], -): (route: R) => R { - return (route) => { - let phases = [...route.phases]; - let phase = phases.pop()!; - phases.push({ - ...phase, - values: phase.values.concat(values), - }); - return { - ...route, - phases, - }; - }; +): IdentityElement { + return brand>( + (route: AnyRoute) => { + let phases = [...route.phases]; + let phase = phases.pop()!; + phases.push({ + ...phase, + values: phase.values.concat(values), + }); + return { + ...route, + phases, + }; + }, + ); } export type ValueClaim = { diff --git a/mod.ts b/mod.ts index 70c8e76..e28aaf4 100644 --- a/mod.ts +++ b/mod.ts @@ -16,6 +16,9 @@ export type { Param } from "./lib/param.ts"; export { parse } from "./lib/parse.ts"; +export { mark } from "./lib/pipeline.ts"; +export type { Transform, TransformElement } from "./lib/pipeline.ts"; + export { printErrors, printHelp, printVersion } from "./lib/print.ts"; export type { Rest } from "./lib/rest.ts"; diff --git a/test/examples.test.ts b/test/examples.test.ts new file mode 100644 index 0000000..6944c01 --- /dev/null +++ b/test/examples.test.ts @@ -0,0 +1,15 @@ +import { expect } from "@std/expect"; +import { describe, it } from "@std/testing/bdd"; +import { app as ten } from "../examples/options.ts"; +import { app as checkpointTen } from "../examples/checkpoint-options.ts"; +import { app as routesTen } from "../examples/routes.ts"; +import { app as routesCheckpointTen } from "../examples/routes-checkpoint.ts"; + +describe("shared type examples", () => { + it("loads the same examples used by the Node diagnostics", () => { + expect(ten.name).toBe("auth0"); + expect(checkpointTen.name).toBe("auth0"); + expect(routesTen.name).toBe("simulacrum"); + expect(routesCheckpointTen.name).toBe("simulacrum"); + }); +}); diff --git a/test/extend.test.ts b/test/extend.test.ts index 262c0c2..cedcfcb 100644 --- a/test/extend.test.ts +++ b/test/extend.test.ts @@ -39,6 +39,16 @@ describe("extend() types", () => { >(true); }); + it("composes ordinary functions", () => { + let stringify = extend( + (value: number) => value + 1, + (value: number) => String(value), + ); + let result = stringify(1); + + expectType>(true); + }); + it("can apply the same extension to different route states", () => { let address = extend( option(name("domain"), schema(type("string"))), @@ -73,10 +83,11 @@ describe("extend() types", () => { it("rejects adjacent elements whose types do not align", () => { let count = (value: { label: string }): number => value.label.length; let label = (value: string): string => value.toUpperCase(); + let invalid = extend(count, label); check(() => { - // @ts-expect-error label cannot consume the number returned by count. - extend(count, label); + // @ts-expect-error the invalid composition cannot consume its input. + invalid({ label: "hello" }); }); }); diff --git a/test/fold.test.ts b/test/fold.test.ts new file mode 100644 index 0000000..6a17295 --- /dev/null +++ b/test/fold.test.ts @@ -0,0 +1,838 @@ +import { expect } from "@std/expect"; +import { describe, it } from "@std/testing/bdd"; +import { type } from "arktype"; +import { command } from "../lib/command.ts"; +import { checkpoint } from "../lib/checkpoint.ts"; +import { description, name } from "../lib/definition.ts"; +import { dynamic } from "../lib/dynamic.ts"; +import { withEnvs } from "../lib/env.ts"; +import { extend } from "../lib/extend.ts"; +import { option } from "../lib/option.ts"; +import { schema } from "../lib/param.ts"; +import { parse } from "../lib/parse.ts"; +import { mark, type Transform } from "../lib/pipeline.ts"; +import { route, routes, version } from "../lib/route.ts"; +import { toggle } from "../lib/toggle.ts"; +import type { + AnyRoute, + ChildrenOf, + ContinuationOf, + Done, + MethodsOf, + ModelOf, + RequirementOf, + Route, +} from "../lib/types.ts"; +import { type ValueSource, withValues } from "../lib/values.ts"; + +const staticApp = command( + name("simulacrum"), + description("run service simulators"), + withValues([]), + withEnvs([]), + version("1.2.0"), + toggle(name("verbose")), + routes( + command( + name("serve"), + option(name("port"), schema(type("number"))), + ), + ), +); + +const configApp = command( + name("server"), + option(name("config"), schema(type("string"))), + dynamic((sources: readonly ValueSource[]) => extend(withValues(sources))), + option(name("port"), schema(type("number"))), +); + +const checkpointApp = command( + name("checkpoint"), + option(name("config"), schema(type("string"))), + checkpoint(), + option(name("port"), schema(type("number"))), +); + +const options = [ + numberOption("p0"), + numberOption("p1"), + numberOption("p2"), + numberOption("p3"), + numberOption("p4"), + numberOption("p5"), + numberOption("p6"), + numberOption("p7"), + numberOption("p8"), + numberOption("p9"), + numberOption("p10"), + numberOption("p11"), + numberOption("p12"), + numberOption("p13"), + numberOption("p14"), + numberOption("p15"), + numberOption("p16"), + numberOption("p17"), + numberOption("p18"), + numberOption("p19"), + numberOption("p20"), + numberOption("p21"), + numberOption("p22"), + numberOption("p23"), + numberOption("p24"), + numberOption("p25"), + numberOption("p26"), + numberOption("p27"), + numberOption("p28"), + numberOption("p29"), + numberOption("p30"), + numberOption("p31"), + numberOption("p32"), + numberOption("p33"), + numberOption("p34"), + numberOption("p35"), + numberOption("p36"), + numberOption("p37"), + numberOption("p38"), + numberOption("p39"), + numberOption("p40"), + numberOption("p41"), + numberOption("p42"), + numberOption("p43"), + numberOption("p44"), + numberOption("p45"), + numberOption("p46"), + numberOption("p47"), + numberOption("p48"), + numberOption("p49"), + numberOption("p50"), + numberOption("p51"), + numberOption("p52"), + numberOption("p53"), + numberOption("p54"), + numberOption("p55"), + numberOption("p56"), + numberOption("p57"), + numberOption("p58"), + numberOption("p59"), + numberOption("p60"), + numberOption("p61"), + numberOption("p62"), + numberOption("p63"), + numberOption("p64"), + numberOption("p65"), + numberOption("p66"), + numberOption("p67"), + numberOption("p68"), + numberOption("p69"), + numberOption("p70"), + numberOption("p71"), + numberOption("p72"), + numberOption("p73"), + numberOption("p74"), + numberOption("p75"), + numberOption("p76"), + numberOption("p77"), + numberOption("p78"), + numberOption("p79"), + numberOption("p80"), + numberOption("p81"), + numberOption("p82"), + numberOption("p83"), + numberOption("p84"), + numberOption("p85"), + numberOption("p86"), + numberOption("p87"), + numberOption("p88"), + numberOption("p89"), + numberOption("p90"), + numberOption("p91"), + numberOption("p92"), + numberOption("p93"), + numberOption("p94"), + numberOption("p95"), + numberOption("p96"), + numberOption("p97"), + numberOption("p98"), + numberOption("p99"), +] as const; + +const children = [ + routes(command(name("c0"))), + routes(command(name("c1"))), + routes(command(name("c2"))), + routes(command(name("c3"))), + routes(command(name("c4"))), + routes(command(name("c5"))), + routes(command(name("c6"))), + routes(command(name("c7"))), + routes(command(name("c8"))), + routes(command(name("c9"))), + routes(command(name("c10"))), + routes(command(name("c11"))), + routes(command(name("c12"))), + routes(command(name("c13"))), + routes(command(name("c14"))), + routes(command(name("c15"))), + routes(command(name("c16"))), + routes(command(name("c17"))), + routes(command(name("c18"))), + routes(command(name("c19"))), + routes(command(name("c20"))), + routes(command(name("c21"))), + routes(command(name("c22"))), + routes(command(name("c23"))), + routes(command(name("c24"))), + routes(command(name("c25"))), + routes(command(name("c26"))), + routes(command(name("c27"))), + routes(command(name("c28"))), + routes(command(name("c29"))), + routes(command(name("c30"))), + routes(command(name("c31"))), + routes(command(name("c32"))), + routes(command(name("c33"))), + routes(command(name("c34"))), + routes(command(name("c35"))), + routes(command(name("c36"))), + routes(command(name("c37"))), + routes(command(name("c38"))), + routes(command(name("c39"))), + routes(command(name("c40"))), + routes(command(name("c41"))), + routes(command(name("c42"))), + routes(command(name("c43"))), + routes(command(name("c44"))), + routes(command(name("c45"))), + routes(command(name("c46"))), + routes(command(name("c47"))), + routes(command(name("c48"))), + routes(command(name("c49"))), + routes(command(name("c50"))), + routes(command(name("c51"))), + routes(command(name("c52"))), + routes(command(name("c53"))), + routes(command(name("c54"))), + routes(command(name("c55"))), + routes(command(name("c56"))), + routes(command(name("c57"))), + routes(command(name("c58"))), + routes(command(name("c59"))), + routes(command(name("c60"))), + routes(command(name("c61"))), + routes(command(name("c62"))), + routes(command(name("c63"))), + routes(command(name("c64"))), + routes(command(name("c65"))), + routes(command(name("c66"))), + routes(command(name("c67"))), + routes(command(name("c68"))), + routes(command(name("c69"))), + routes(command(name("c70"))), + routes(command(name("c71"))), + routes(command(name("c72"))), + routes(command(name("c73"))), + routes(command(name("c74"))), + routes(command(name("c75"))), + routes(command(name("c76"))), + routes(command(name("c77"))), + routes(command(name("c78"))), + routes(command(name("c79"))), + routes(command(name("c80"))), + routes(command(name("c81"))), + routes(command(name("c82"))), + routes(command(name("c83"))), + routes(command(name("c84"))), + routes(command(name("c85"))), + routes(command(name("c86"))), + routes(command(name("c87"))), + routes(command(name("c88"))), + routes(command(name("c89"))), + routes(command(name("c90"))), + routes(command(name("c91"))), + routes(command(name("c92"))), + routes(command(name("c93"))), + routes(command(name("c94"))), + routes(command(name("c95"))), + routes(command(name("c96"))), + routes(command(name("c97"))), + routes(command(name("c98"))), + routes(command(name("c99"))), +] as const; + +const hundredCommand = command(name("hundred"), ...options); +const hundredRoute = route(name("hundred"), ...options); +const hundredExtension = extend(...options); +const extendedRoute = route(name("extended"), hundredExtension); +const hundredChildren = route(name("children"), ...children); + +describe("pipeline fold", () => { + it("infers a concrete static route", () => { + expectType, { verbose: boolean }>>(true); + expectType< + Equal, "help" | "version" | "execute"> + >(true); + + type Serve = ChildrenOf[0]; + expectType>(true); + expectType, { port: number }>>(true); + }); + + it("preserves exact dynamic inference", () => { + type Continued = ContinuationOf; + + expectType< + Equal, readonly ValueSource[]> + >(true); + expectType< + Equal, { config: string; port: number }> + >(true); + expectType< + Equal, ValueSource[]> + >(true); + expectType< + Equal< + ModelOf>, + { config: string; port: number } + > + >(true); + }); + + it("accepts an atomic dynamic continuation", () => { + let app = command( + name("atomic"), + dynamic((_plugins: Plugins) => + option(name("dyno"), schema(type("number"))) + ), + ); + + expectType, Plugins>>(true); + expectType, { dyno: number }>>(true); + }); + + it("folds one hundred elements through commands, routes, and extensions", () => { + expectType["p0"], number | undefined>>( + true, + ); + expectType< + Equal["p99"], number | undefined> + >(true); + expectType< + Equal, ModelOf> + >( + true, + ); + expectType< + Equal, ModelOf> + >( + true, + ); + }); + + it("marks a bespoke input-dependent transformation", () => { + let result = route( + name("simulacrum"), + option(name("port"), schema(type("number"))), + mark((value: AnyRoute) => ({ + type: "bloop" as const, + route: value, + })), + ); + + expectType>(true); + expectType>(true); + expectType, { port: number }>>(true); + }); + + it("composes more than ten unbranded functions", () => { + let routed = route( + name("routed"), + begin, + one, + two, + three, + four, + five, + six, + seven, + eight, + nine, + ten, + eleven, + ); + let commanded = command( + name("commanded"), + begin, + one, + two, + three, + four, + five, + six, + seven, + eight, + nine, + ten, + eleven, + ); + let extension = extend( + begin, + one, + two, + three, + four, + five, + six, + seven, + eight, + nine, + ten, + eleven, + ); + let extended = extension(route(name("extended"))); + + expectType>(true); + expectType>(true); + expectType>(true); + }); + + it("composes an identity macro between built-ins", () => { + let identity = extend(); + let app = route( + name("identity"), + option(name("port"), schema(type("number"))), + identity, + option(name("domain"), schema(type("string"))), + ); + + expectType, { port: number; domain: string }>>( + true, + ); + }); + + it("composes a marked identity with method and child-route elements", () => { + let identity = mark((route: AnyRoute) => route); + let app = route( + name("mixed"), + description("mixed pipeline"), + withValues([]), + withEnvs([]), + version("1.0.0"), + routes(command(name("child"))), + identity, + option(name("port"), schema(type("number"))), + ); + + expectType, "help" | "version">>(true); + expectType[0]["name"], "child">>(true); + expectType, { port: number }>>(true); + }); + + it("preserves a type-enhancing custom terminal combinator", () => { + let annotate = mark((route: AnyRoute) => ({ + ...route, + tag: "custom" as const, + })); + let app = route( + name("annotated"), + option(name("port"), schema(type("number"))), + option(name("domain"), schema(type("string"))), + annotate, + ); + + expectType>(true); + expectType>(true); + expectType, { port: number; domain: string }>>( + true, + ); + }); + + it("preserves state through a reusable hybrid extension", () => { + let identity = extend(); + let address = extend( + option(name("domain"), schema(type("string"))), + identity, + option(name("port"), schema(type("number"))), + ); + let app = address(route(name("address"))); + + expectType>(true); + expectType, { domain: string; port: number }>>( + true, + ); + }); + + it("preserves a dynamic boundary through a reusable hybrid extension", () => { + let identity = extend(); + let phased = extend( + option(name("before"), schema(type("number"))), + identity, + dynamic((_plugins: Plugins) => + option(name("dynamic"), schema(type("string"))) + ), + option(name("after"), schema(type("boolean"))), + ); + let app = phased(command(name("hybrid"))); + + expectType, Plugins>>(true); + expectType< + Equal< + ModelOf, + { before: number; dynamic: string; after: boolean } + > + >(true); + }); + + it("preserves state through a reusable terminal transform", () => { + let bloop = mark((route: AnyRoute) => ({ + type: "bloop" as const, + route, + })); + let inspect = extend( + option(name("port"), schema(type("number"))), + bloop, + ); + let result = inspect(route(name("inspected"))); + + expectType>(true); + expectType>(true); + expectType, { port: number }>>(true); + }); + + it("lets user combinators compose elements without explicit branding", () => { + let address = () => + extend( + option(name("domain"), schema(type("string"))), + option(name("port"), schema(type("number"))), + ); + let app = command(name("server"), address()); + + expectType< + Equal, { domain: string; port: number }> + >(true); + }); + + it("keeps built-in identity macros foldable without explicit branding", () => { + let documented = extend( + description("first"), + description("second"), + ); + let app = route(name("documented"), documented, ...options); + + expectType["p99"], number | undefined>>(true); + expect(app.description).toBe("second"); + }); + + it("preserves the domain of identity-only batches", () => { + let documented = extend( + description("first"), + description("second"), + )(name("tool")); + let value = extend(extend(), extend())(42 as const); + + expectType>(true); + expectType>(true); + expect(documented.description).toBe("second"); + expect(value).toBe(42); + }); + + it("preserves existing model modifiers while folding new fields", () => { + type Base = Route< + "base", + "help", + { readonly existing?: number }, + [], + readonly [Done<{ readonly existing?: number }, []>] + >; + + let base = route(name("base")) as unknown as Base; + let decorate = extend( + withValues([]), + option(name("added"), schema(type("string"))), + ); + let result = decorate(base); + + expectType< + Equal< + ModelOf, + { readonly existing?: number; added: string } + > + >(true); + }); + + it("composes nested built-in batches through an identity macro", () => { + let identity = extend(); + let nested = extend( + routes(command(name("child"))), + withValues([]), + ); + let hybrid = extend( + identity, + nested, + option(name("port"), schema(type("number"))), + ); + let app = hybrid(command(name("nested"))); + + expectType[0]["name"], "child">>(true); + expectType, { port: number }>>(true); + }); + + it("composes nested dynamic batches through an identity macro", () => { + let identity = extend(); + let nested = extend( + dynamic((_plugins: Plugins) => + option(name("dynamic"), schema(type("number"))) + ), + withValues([]), + ); + let hybrid = extend( + identity, + nested, + option(name("static"), schema(type("string"))), + ); + let app = hybrid(command(name("nested-dynamic"))); + + expectType, Plugins>>(true); + expectType< + Equal, { dynamic: number; static: string }> + >(true); + }); + + it("folds one hundred child-route elements", () => { + expectType[0]["name"], "c0">>( + true, + ); + expectType[99]["name"], "c99">>( + true, + ); + }); + + it("uses the final declaration when a parameter name is repeated", () => { + let app = command( + name("duplicate"), + option(name("value"), schema(type("number"))), + option(name("value"), schema(type("string"))), + ); + + expectType, { value: string }>>(true); + }); + + it("uses the final declaration through a marked identity", () => { + let identity = mark((route: AnyRoute) => route); + let app = command( + name("duplicate"), + option(name("value"), schema(type("number"))), + identity, + option(name("value"), schema(type("string"))), + ); + + expectType, { value: string }>>(true); + }); + + it("types widened element arrays conservatively", () => { + let elements = [option(name("port"), schema(type("number")))]; + let app = route(name("widened"), ...elements); + + expectType>(true); + }); + + it("types open element tuples conservatively", () => { + let head = option(name("head"), schema(type("number"))); + let extra = textOption("tail"); + let tail: (typeof extra)[] = [extra]; + let elements: readonly [typeof head, ...typeof tail] = [head, ...tail]; + let app = route(name("open"), ...elements); + + expectType>(true); + }); + + it("types union-valued elements conservatively", () => { + let element = Math.random() > 0.5 + ? option(name("port"), schema(type("number"))) + : routes(route(name("child"))); + let app = route(name("union"), element); + + expectType>(true); + + let optionElement = Math.random() > 0.5 + ? option(name("count"), schema(type("number"))) + : option(name("label"), schema(type("string"))); + let optionApp = route(name("option-union"), optionElement); + + expectType>(true); + }); + + it("rejects invalid route elements", () => { + check(() => { + route( + name("broken"), + // @ts-expect-error schema() transforms a Param, not a Route. + schema(type("number")), + ); + }); + + check(() => { + command( + name("broken"), + // @ts-expect-error schema() transforms a Param, not a Route. + schema(type("number")), + ); + }); + + check(() => { + // @ts-expect-error a resolver must return a route extension. + dynamic( + (_plugins: Plugins) => command(name("auth0")), + ); + }); + }); + + it("completes a static child parse", () => { + let result = parse(staticApp, { + argv: ["--verbose", "serve", "--port", "4040"], + }); + + expect(result).toMatchObject({ + ok: true, + method: "execute", + route: "/serve", + model: { port: 4040 }, + models: { + "/": { verbose: true }, + "/serve": { port: 4040 }, + }, + }); + }); + + it("completes an incremental parse", () => { + let first = parse(checkpointApp, { + argv: ["--config", "server.json"], + }); + + expect(first).toMatchObject({ + ok: true, + route: "/", + model: { config: "server.json" }, + }); + if (!first.ok || !("resume" in first)) { + throw new Error("expected a parse increment"); + } + + let result = first.resume({ + ok: true, + value: [{ name: "server.json", value: { port: 9001 } }], + }); + + expect(result).toMatchObject({ + ok: true, + method: "execute", + route: "/", + model: { config: "server.json", port: 9001 }, + }); + }); + + it("executes the final element of a one-hundred-element route", () => { + let result = parse(hundredCommand, { argv: ["--p99", "99"] }); + + expect(result).toMatchObject({ + ok: true, + method: "execute", + route: "/", + model: { p99: 99 }, + }); + }); +}); + +interface Plugins { + readonly auth0: boolean; +} + +interface Identity extends Transform { + readonly input: AnyRoute; + readonly output: this["input"]; +} + +interface Annotate extends Transform { + readonly input: AnyRoute; + readonly output: this["input"] & { readonly tag: "custom" }; +} + +interface Bloop extends Transform { + readonly input: AnyRoute; + readonly output: BloopValue; +} + +interface BloopValue { + readonly type: "bloop"; + readonly route: R; +} + +type Equal = (() => T extends L ? 1 : 2) extends + (() => T extends R ? 1 : 2) + ? (() => T extends R ? 1 : 2) extends (() => T extends L ? 1 : 2) ? true + : false + : false; + +function expectType(_value: T): void { + // Compile-time assertion. +} + +function check(_body: () => void): void { + // Compile without executing. +} + +function numberOption(key: N) { + return option(name(key), schema(type("number | undefined"))); +} + +function textOption(key: N) { + return option(name(key), schema(type("string"))); +} + +function begin(_value: { readonly name: string }): 0 { + return 0; +} + +function one(_value: 0): 1 { + return 1; +} + +function two(_value: 1): 2 { + return 2; +} + +function three(_value: 2): 3 { + return 3; +} + +function four(_value: 3): 4 { + return 4; +} + +function five(_value: 4): 5 { + return 5; +} + +function six(_value: 5): 6 { + return 6; +} + +function seven(_value: 6): 7 { + return 7; +} + +function eight(_value: 7): 8 { + return 8; +} + +function nine(_value: 8): 9 { + return 9; +} + +function ten(_value: 9): 10 { + return 10; +} + +function eleven(_value: 10): 11 { + return 11; +} diff --git a/test/route.test.ts b/test/route.test.ts index 63a7d47..29e2fed 100644 --- a/test/route.test.ts +++ b/test/route.test.ts @@ -4,9 +4,10 @@ import { type } from "arktype"; import { description, name } from "../lib/definition.ts"; import { option } from "../lib/option.ts"; import { schema } from "../lib/param.ts"; +import { mark, type Transform } from "../lib/pipeline.ts"; import { route, routes, type RouteZero, version } from "../lib/route.ts"; import { toggle } from "../lib/toggle.ts"; -import type { ChildrenOf, Done, ModelOf } from "../lib/types.ts"; +import type { AnyRoute, ChildrenOf, Done, ModelOf } from "../lib/types.ts"; describe("route() types", () => { it("preserves the literal route name through each element", () => { @@ -140,16 +141,24 @@ describe("route() types", () => { }); it("allows an explicit final element to transform a Route into another type", () => { + interface Bloop extends Transform { + readonly input: AnyRoute; + readonly output: BloopValue; + } + let result = route( name("simulacrum"), - (value) => ({ type: "bloop" as const, route: value }), + mark((value: AnyRoute) => ({ + type: "bloop" as const, + route: value, + })), ); expectType>(true); expectType>(true); }); - it("preserves inference through the longest supported pipeline", () => { + it("preserves inference across thirty route elements", () => { let result = route( name("simulacrum"), option(name("one"), schema(type("string"))), @@ -240,3 +249,8 @@ function expectType(_value: T): void { function check(_body: () => void): void { // Compile the callback without executing it. } + +interface BloopValue { + readonly type: "bloop"; + readonly route: R; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..360d035 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "allowImportingTsExtensions": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true, + "noErrorTruncation": true, + "skipLibCheck": true, + "strict": true, + "target": "ES2022", + "types": [], + "baseUrl": ".", + "paths": { + "@standard-schema/spec": [ + "diagnostics/node-ts5/node_modules/@standard-schema/spec/dist/index.d.ts" + ], + "zod": ["diagnostics/node-ts5/node_modules/zod/index.d.ts"] + } + }, + "files": [ + "examples/options.ts", + "examples/checkpoint-options.ts", + "examples/routes.ts", + "examples/routes-checkpoint.ts" + ] +}