From cffe20b2334c73e8320126655aacd06f1a0fabe4 Mon Sep 17 00:00:00 2001 From: Jacob Bolda Date: Fri, 28 Aug 2026 13:34:53 -0500 Subject: [PATCH 1/2] remove overloads for adv type to 'fold' in all args --- lib/command.ts | 1095 +-------------------------------------- lib/elements.ts | 136 +++++ lib/option.ts | 159 +----- lib/route.ts | 1181 ++---------------------------------------- lib/toggle.ts | 159 +----- mod.ts | 8 + test/command.test.ts | 31 +- test/route.test.ts | 80 ++- 8 files changed, 328 insertions(+), 2521 deletions(-) create mode 100644 lib/elements.ts diff --git a/lib/command.ts b/lib/command.ts index cdcf915..8999618 100644 --- a/lib/command.ts +++ b/lib/command.ts @@ -1,6 +1,7 @@ // deno-lint-ignore-file ban-types import { executable, route } from "./route.ts"; -import type { Definition, Route } from "./types.ts"; +import type { AnyRoute, Definition, Route } from "./types.ts"; +import type { AnyRouteElement, Build } from "./elements.ts"; export type CommandZero = Route< N, @@ -9,1093 +10,19 @@ export type CommandZero = Route< [] >; -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, + const Ds extends readonly (AnyRouteElement)[], >( 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; + ...elements: Ds +): Build { + const zero = route(start, executable()); -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 elements.reduce( - (value, element) => element(value as never), - route(start, executable()), + const result = elements.reduce( + (value, element) => (element as (route: AnyRoute) => AnyRoute)(value), + zero, ); + + return result as Build; } diff --git a/lib/elements.ts b/lib/elements.ts new file mode 100644 index 0000000..40ab7b1 --- /dev/null +++ b/lib/elements.ts @@ -0,0 +1,136 @@ +// deno-lint-ignore-file ban-types +import type { AnyRoute, Definition, Method, Route } from "./types.ts"; + +// Merge: the params object after adding B's keys on top of A's. The +// mapped type keeps the result Equal to a plain object literal, which the +// tests require. A bare A & B intersection fails those checks. +type Merge = { + [K in keyof (A & B)]: (A & B)[K]; +}; + +// Element: takes any Route, returns a Route with M, P, C merged in. +// The callable carries a declaration-only `contribution` property so the +// fold can read M/P/C exactly. Inference from the callable alone falls back +// to the Method and AnyRoute[] constraints, because M hides inside the +// SM | M union and C trails a variadic. The fold would then read Method and +// AnyRoute[] where it needs never and readonly []. +export type Element< + M extends Method = never, + P extends object = {}, + C extends readonly AnyRoute[] = [], +> = + & (< + N extends string, + SM extends Method, + SP extends object, + SC extends readonly AnyRoute[], + >( + route: Route, + ) => Route, readonly [...SC, ...C]>) + & { + readonly contribution: readonly [M, P, C]; + }; + +export type AnyElement = Element; + +// DefinitionTransform: description()'s shape. It hands back the Definition +// it receives, which lets one function serve as both a no-op pipeline member +// and an option/toggle transform. +export type DefinitionTransform = { + >(definition: D): D; +}; + +// AnyRouteElement: what a pipeline accepts. Elements carry their +// contribution. description() is admitted through DefinitionTransform rather +// than being re-typed as an Element. +export type AnyRouteElement = AnyElement | DefinitionTransform; + +// Transformer: the shape a transformer must have to claim the +// contribution M, P, C. It takes any Route and returns that Route with the +// contribution merged in. withRoute applies this formal to transformers the +// compiler can check (version, executable, routes, bare no-op wrappers). +// Transformers the compiler cannot check -- option and toggle build a params +// key from runtime data, which no spread can prove -- cast straight to +// Element instead. Generic method unions stay comparable, so method-identity +// mistakes pass the formal even where it applies. +export type Transformer< + M extends Method = never, + P extends object = {}, + C extends readonly AnyRoute[] = readonly [], +> = < + N extends string, + SM extends Method, + SP extends object, + SC extends readonly AnyRoute[], +>( + route: Route, +) => Route< + N, + SM | M, + Merge, + // A transformer either passes the children through or appends its own. + // The union keeps identity returns assignable for any C, inferred or not. + SC | readonly [...SC, ...C] +>; + +// withRoute: types a transformer as the Element it declares. The +// function comes back unchanged. Only its type changes: it gains the +// contribution marker the fold reads. Give hand-written transformers like +// version and routes their shape here, or wrap a bare function to admit it +// as a no-op element. The parameter type stays plain so bare lambdas get a +// contextual type; the return must be a Route. Contributions always come +// from the explicit M/P/C arguments. +export function withRoute< + const M extends Method = never, + const P extends object = {}, + const C extends readonly AnyRoute[] = readonly [], +>( + f: Transformer, +): Element { + return f as unknown as Element; +} + +// Build: the Route produced by reducing Elements +// over a RouteZero that starts with Method methods +export type Build< + Name extends string, + M extends Method, + Ds extends readonly unknown[], +> = Finish, PartsOf>>; + +type State = readonly [N, M, P, C]; + +// PartsOf: each element's contribution [M, P, C], read from its +// declaration-only property. Definition-transformers and plain functions +// contribute nothing. +type PartsOf = { + [K in keyof Ds]: Ds[K] extends DefinitionTransform ? readonly [] + : Ds[K] extends { + readonly contribution: readonly [infer M, infer P, infer C]; + } ? readonly [M, P, C] + : readonly []; +}; + +// Fold: threads the accumulated [name, methods, params, children] +// through each element's contribution, mirroring the runtime reduce. +type Fold< + S extends State, + Ds extends readonly unknown[], +> = Ds extends readonly [infer Head, ...infer Rest] ? Head extends readonly [ + infer M extends Method, + infer P extends object, + infer C extends readonly AnyRoute[], + ] ? Fold< + State< + S[0], + S[1] | M, + Merge, + C extends readonly [] ? S[3] : readonly [...S[3], ...C] + >, + Rest + > + : Fold + : S; + +type Finish> = + Route; diff --git a/lib/option.ts b/lib/option.ts index 50e15b8..952bf7b 100644 --- a/lib/option.ts +++ b/lib/option.ts @@ -1,146 +1,27 @@ import { type Param, param } from "./param.ts"; import { dasherize } from "./dasherize.ts"; import { cli } from "./read.ts"; -import type { AnyRoute, Definition, Method, Route } from "./types.ts"; - -export function option( - named: Definition, -): Option; +import type { AnyRoute, Definition } from "./types.ts"; +import type { Element } from "./elements.ts"; export function option( named: Definition, - nt: (value: Param) => Param, -): Option; - -export function option( - named: Definition, - na: (value: Param) => A, - at: (value: A) => Param, -): Option; - -export function option( - named: Definition, - na: (value: Param) => A, - ab: (value: A) => B, - bt: (value: B) => Param, -): Option; - -export function option( - named: Definition, - na: (value: Param) => A, - ab: (value: A) => B, - bc: (value: B) => C, - ct: (value: C) => Param, -): Option; - -export function option( - named: Definition, - na: (value: Param) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - dt: (value: D) => Param, -): Option; - -export function option( - named: Definition, - na: (value: Param) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - et: (value: E) => Param, -): Option; - -export function option( - named: Definition, - na: (value: Param) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - ft: (value: F) => Param, -): Option; - -export function option< - const N extends string, - A, - B, - C, - D, - E, - F, - G, - T, ->( - named: Definition, - na: (value: Param) => 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, - gt: (value: G) => Param, -): Option; - -export function option< - const N extends string, - A, - B, - C, - D, - E, - F, - G, - H, - T, ->( - named: Definition, - na: (value: Param) => 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, - ht: (value: H) => Param, -): Option; - -export function option( - named: Definition, - ...elements: readonly ((value: never) => unknown)[] -): unknown { - const added = elements.reduce( - (value, element) => element(value as never), - param(named, cli([`--${dasherize(named.name)}`])), - ) as Param; - - return (route: AnyRoute) => ({ - ...route, - params: { - ...route.params, - [added.name]: added, - }, - }); + ...transforms: readonly ((value: Param) => Param)[] +): Element { + const transform = ((route: AnyRoute) => { + const added = transforms.reduce>( + (value, element) => element(value), + param(named, cli([`--${dasherize(named.name)}`])), + ); + + return { + ...route, + params: { + ...route.params, + [added.name]: added, + }, + }; + }) as unknown as Element; + + return transform; } - -type Option = < - const N extends string, - const M extends Method, - const T extends object, - const C extends readonly AnyRoute[], ->( - route: Route, -) => Route< - N, - M, - { - [P in keyof ({ [Q in K]: V } & T)]: ( - { [Q in K]: V } & T - )[P]; - }, - C ->; diff --git a/lib/route.ts b/lib/route.ts index a100bbb..7fa5633 100644 --- a/lib/route.ts +++ b/lib/route.ts @@ -1,1165 +1,78 @@ // deno-lint-ignore-file ban-types -import type { - AnyRoute, - Definition, - Method, - MethodsOf, - ModelOf, - Route, -} from "./types.ts"; +import type { AnyRoute, Definition, Method, Route } from "./types.ts"; +import type { AnyRouteElement, Build, Element } from "./elements.ts"; +import { withRoute } from "./elements.ts"; export type RouteZero = Route; -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, + const Ds extends readonly (AnyRouteElement)[], >( 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, ->( - 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 = { + ...elements: Ds +): Build { + const zero: RouteZero = { ...start, methods: ["help"], params: {}, children: [], }; - return elements.reduce( - (value, element) => element(value as never), + const result = elements.reduce( + (value, element) => (element as (route: AnyRoute) => AnyRoute)(value), zero, ); + + return result as Build; } export function version( semver: string, -): < - const N extends string, - const M extends Method, - const T extends object, - const C extends readonly AnyRoute[], ->(route: Route) => Route { - return (route) => ({ - ...route, - methods: [...route.methods, "version"] as const, - version: semver, - }); +): Element<"version", {}, readonly []> { + return withRoute<"version", {}, readonly []>( + < + const N extends string, + const M extends Method, + const T extends object, + const C extends readonly AnyRoute[], + >(route: Route) => ({ + ...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[], ->(route: Route) => Route { - return (route) => ({ - ...route, - methods: [...route.methods, "execute"] as const, - }); +export function executable(): Element<"execute", {}, readonly []> { + return withRoute<"execute", {}, readonly []>( + < + const N extends string, + const M extends Method, + const T extends object, + const C extends readonly AnyRoute[], + >(route: Route) => ({ + ...route, + methods: [...route.methods, "execute"] as const, + }), + ); } export function routes( ...children: C -): ( - route: R, -) => Route< - R["name"], - MethodsOf, - ModelOf, - readonly [...R["children"], ...C] -> { - return (route: R) => { - type Output = Route< - R["name"], - MethodsOf, - ModelOf, - readonly [...R["children"], ...C] - >; - - return { +): Element { + return withRoute( + < + const N extends string, + const M extends Method, + const T extends object, + const SC extends readonly AnyRoute[], + >(route: Route) => ({ ...route, children: [ ...route.children, ...children, - ] as unknown as Output["children"], - params: route.params as Output["params"], - }; - }; + ] as unknown as readonly [...SC, ...C], + }), + ); } diff --git a/lib/toggle.ts b/lib/toggle.ts index 621eec3..bf7deef 100644 --- a/lib/toggle.ts +++ b/lib/toggle.ts @@ -2,150 +2,31 @@ import { dasherize } from "./dasherize.ts"; import { type Param, param, schema } from "./param.ts"; import type { CLIRead, ReadCLI } from "./read.ts"; import type { Flag } from "./tokenize.ts"; -import type { AnyRoute, Definition, Method, Route, Schema } from "./types.ts"; - -export function toggle( - named: Definition, -): Toggle; +import type { AnyRoute, Definition, Schema } from "./types.ts"; +import type { Element } from "./elements.ts"; export function toggle( named: Definition, - nt: (value: Param) => Param, -): Toggle; - -export function toggle( - named: Definition, - na: (value: Param) => A, - at: (value: A) => Param, -): Toggle; - -export function toggle( - named: Definition, - na: (value: Param) => A, - ab: (value: A) => B, - bt: (value: B) => Param, -): Toggle; - -export function toggle( - named: Definition, - na: (value: Param) => A, - ab: (value: A) => B, - bc: (value: B) => C, - ct: (value: C) => Param, -): Toggle; - -export function toggle( - named: Definition, - na: (value: Param) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - dt: (value: D) => Param, -): Toggle; - -export function toggle( - named: Definition, - na: (value: Param) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - et: (value: E) => Param, -): Toggle; - -export function toggle( - named: Definition, - na: (value: Param) => A, - ab: (value: A) => B, - bc: (value: B) => C, - cd: (value: C) => D, - de: (value: D) => E, - ef: (value: E) => F, - ft: (value: F) => Param, -): Toggle; - -export function toggle< - const N extends string, - A, - B, - C, - D, - E, - F, - G, - T, ->( - named: Definition, - na: (value: Param) => 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, - gt: (value: G) => Param, -): Toggle; - -export function toggle< - const N extends string, - A, - B, - C, - D, - E, - F, - G, - H, - T, ->( - named: Definition, - na: (value: Param) => 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, - ht: (value: H) => Param, -): Toggle; - -export function toggle( - named: Definition, - ...elements: readonly ((value: never) => unknown)[] -): unknown { - const added = elements.reduce( - (value, element) => element(value as never), - param(named, binding(named.name), schema(bool)), - ) as Param; - - return (route: AnyRoute) => ({ - ...route, - params: { - ...route.params, - [added.name]: added, - }, - }); + ...transforms: readonly ((value: Param) => Param)[] +): Element { + const transform = ((route: AnyRoute) => { + const added = transforms.reduce>( + (value, element) => element(value), + param(named, binding(named.name), schema(bool)), + ); + + return { + ...route, + params: { + ...route.params, + [added.name]: added, + }, + }; + }) as unknown as Element; + + return transform; } -type Toggle = < - const N extends string, - const M extends Method, - const T extends object, - const C extends readonly AnyRoute[], ->( - route: Route, -) => Route< - N, - M, - { - [P in keyof ({ [Q in K]: V } & T)]: ( - { [Q in K]: V } & T - )[P]; - }, - C ->; - function binding( name: string, ):

>(param: P) => P { diff --git a/mod.ts b/mod.ts index 69984ee..35aae5c 100644 --- a/mod.ts +++ b/mod.ts @@ -3,6 +3,14 @@ export type { CommandZero } from "./lib/command.ts"; export { description, name } from "./lib/definition.ts"; +export type { + AnyElement, + AnyRouteElement, + Build, + Element, +} from "./lib/elements.ts"; +export { withRoute } from "./lib/elements.ts"; + export { option } from "./lib/option.ts"; export { param, schema } from "./lib/param.ts"; diff --git a/test/command.test.ts b/test/command.test.ts index 2c8cbed..4f3a657 100644 --- a/test/command.test.ts +++ b/test/command.test.ts @@ -3,6 +3,7 @@ import { describe, it } from "@std/testing/bdd"; import { type } from "arktype"; import { command, type CommandZero } from "../lib/command.ts"; import { name } from "../lib/definition.ts"; +import { withRoute } from "../lib/elements.ts"; import { option } from "../lib/option.ts"; import { schema } from "../lib/param.ts"; import { route, routes, version } from "../lib/route.ts"; @@ -18,16 +19,19 @@ describe("command()", () => { }); it("materializes a CommandZero before applying the first element", () => { - let result = command(name("simulacrum"), (zero) => { - expectType>>(true); - expect(zero).toEqual({ - name: "simulacrum", - methods: ["help", "execute"], - params: {}, - children: [], - }); - return zero; - }); + let result = command( + name("simulacrum"), + withRoute((zero) => { + expectRoute(zero); + expect(zero).toEqual({ + name: "simulacrum", + methods: ["help", "execute"], + params: {}, + children: [], + }); + return zero; + }), + ); expectType>>(true); }); @@ -147,3 +151,10 @@ type Methods = R["methods"][number]; function expectType(_value: T): void { // Compile-time assertion. } + +// Asserts the route a wrapped transformer receives: the formal's fully +// generic shape. The concrete accumulated type only exists at command()'s +// inference site, after the lambda is already checked. +function expectRoute( + _route: Route, +): void {} diff --git a/test/route.test.ts b/test/route.test.ts index 7a96870..964deb7 100644 --- a/test/route.test.ts +++ b/test/route.test.ts @@ -2,6 +2,7 @@ import { expect } from "@std/expect"; import { describe, it } from "@std/testing/bdd"; import { type } from "arktype"; import { description, name } from "../lib/definition.ts"; +import { withRoute } from "../lib/elements.ts"; import { option } from "../lib/option.ts"; import { schema } from "../lib/param.ts"; import { route, type RouteZero, version } from "../lib/route.ts"; @@ -25,16 +26,39 @@ describe("route() types", () => { }); it("materializes a RouteZero before applying the first element", () => { - let result = route(name("simulacrum"), (zero) => { - expectType>>(true); - expect(zero).toEqual({ - name: "simulacrum", - methods: ["help"], - params: {}, - children: [], - }); - return zero; - }); + let result = route( + name("simulacrum"), + withRoute((zero) => { + expectRoute(zero); + expect(zero).toEqual({ + name: "simulacrum", + methods: ["help"], + params: {}, + children: [], + }); + return zero; + }), + ); + + expectType>>(true); + }); + + it("materializes a RouteZero with a description", () => { + let result = route( + name("simulacrum"), + description("manage service simulators"), + withRoute((zero) => { + expectRoute(zero); + expect(zero).toEqual({ + name: "simulacrum", + description: "manage service simulators", + methods: ["help"], + params: {}, + children: [], + }); + return zero; + }), + ); expectType>>(true); }); @@ -46,7 +70,9 @@ describe("route() types", () => { ); expect(result.description).toBe("manage service simulators"); - expectType>>(true); + expectType, RouteZero<"simulacrum">>>( + true, + ); }); it("preserves supported methods while adding options", () => { @@ -58,6 +84,24 @@ describe("route() types", () => { expectType, "help">>(true); }); + it("preserves type while adding options", () => { + let result = route( + name("simulacrum"), + option(name("port"), schema(type("number"))), + withRoute((zero) => { + expectRoute(zero); + expect(zero.name).toBe("simulacrum"); + expect(zero.methods).toEqual(["help"]); + expect(Object.keys(zero.params)).toEqual(["port"]); + expect(zero.children).toEqual([]); + return zero; + }), + ); + + expectType, { port: number }>>(true); + expectType, "help">>(true); + }); + it("adds version to the supported methods", () => { let result = route( name("simulacrum"), @@ -122,14 +166,13 @@ describe("route() types", () => { }); }); - it("allows an explicit final element to transform a Route into another type", () => { + it("allows an explicit final element to receive the route and return it as-is", () => { let result = route( name("simulacrum"), - (value) => ({ type: "bloop" as const, route: value }), + withRoute((value) => value), ); - expectType>(true); - expectType>(true); + expectType>>(true); }); it("preserves inference through the longest supported pipeline", () => { @@ -223,6 +266,13 @@ function expectType(_value: T): void { // Compile-time assertion. } +// Asserts the route a wrapped transformer receives: the formal's fully +// generic shape. The concrete accumulated type only exists at route()'s +// inference site, after the lambda is already checked. +function expectRoute( + _route: Route, +): void {} + function check(_body: () => void): void { // Compile the callback without executing it. } From b2a841c0b91f0a0dbd4db237b077a104c9d08062 Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Tue, 8 Sep 2026 20:36:20 -0500 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=8E=A8=20Format=20fold=20type=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/command.test.ts | 7 ++++++- test/route.test.ts | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/test/command.test.ts b/test/command.test.ts index 4f3a657..40246be 100644 --- a/test/command.test.ts +++ b/test/command.test.ts @@ -155,6 +155,11 @@ function expectType(_value: T): void { // Asserts the route a wrapped transformer receives: the formal's fully // generic shape. The concrete accumulated type only exists at command()'s // inference site, after the lambda is already checked. -function expectRoute( +function expectRoute< + N extends string, + SM extends Method, + SP extends object, + SC extends readonly AnyRoute[], +>( _route: Route, ): void {} diff --git a/test/route.test.ts b/test/route.test.ts index 964deb7..c3132e9 100644 --- a/test/route.test.ts +++ b/test/route.test.ts @@ -269,7 +269,12 @@ function expectType(_value: T): void { // Asserts the route a wrapped transformer receives: the formal's fully // generic shape. The concrete accumulated type only exists at route()'s // inference site, after the lambda is already checked. -function expectRoute( +function expectRoute< + N extends string, + SM extends Method, + SP extends object, + SC extends readonly AnyRoute[], +>( _route: Route, ): void {}