@bomb.sh/router 0.0.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +293 -0
  2. package/esm/_dnt.polyfills.d.ts +11 -0
  3. package/esm/_dnt.polyfills.js +15 -0
  4. package/esm/lib/argument.d.ts +7 -0
  5. package/esm/lib/argument.js +66 -0
  6. package/esm/lib/bind.d.ts +41 -0
  7. package/esm/lib/bind.js +285 -0
  8. package/esm/lib/checkpoint.d.ts +3 -0
  9. package/esm/lib/checkpoint.js +5 -0
  10. package/esm/lib/command.d.ts +6 -0
  11. package/esm/lib/command.js +16 -0
  12. package/esm/lib/dasherize.d.ts +1 -0
  13. package/esm/lib/dasherize.js +7 -0
  14. package/esm/lib/decode.d.ts +6 -0
  15. package/esm/lib/decode.js +46 -0
  16. package/esm/lib/definition.d.ts +4 -0
  17. package/esm/lib/definition.js +10 -0
  18. package/esm/lib/dynamic.d.ts +6 -0
  19. package/esm/lib/dynamic.js +21 -0
  20. package/esm/lib/env.d.ts +34 -0
  21. package/esm/lib/env.js +83 -0
  22. package/esm/lib/extend.d.ts +2 -0
  23. package/esm/lib/extend.js +4 -0
  24. package/esm/lib/maybe.d.ts +9 -0
  25. package/esm/lib/maybe.js +12 -0
  26. package/esm/lib/multiple.d.ts +8 -0
  27. package/esm/lib/multiple.js +9 -0
  28. package/esm/lib/option.d.ts +7 -0
  29. package/esm/lib/option.js +31 -0
  30. package/esm/lib/param.d.ts +26 -0
  31. package/esm/lib/param.js +37 -0
  32. package/esm/lib/parse.d.ts +2 -0
  33. package/esm/lib/parse.js +338 -0
  34. package/esm/lib/pipeline.d.ts +182 -0
  35. package/esm/lib/pipeline.js +6 -0
  36. package/esm/lib/print.d.ts +7 -0
  37. package/esm/lib/print.js +156 -0
  38. package/esm/lib/read.d.ts +27 -0
  39. package/esm/lib/read.js +95 -0
  40. package/esm/lib/rest.d.ts +9 -0
  41. package/esm/lib/rest.js +1 -0
  42. package/esm/lib/result.d.ts +9 -0
  43. package/esm/lib/result.js +1 -0
  44. package/esm/lib/route.d.ts +9 -0
  45. package/esm/lib/route.js +45 -0
  46. package/esm/lib/toggle.d.ts +7 -0
  47. package/esm/lib/toggle.js +91 -0
  48. package/esm/lib/tokenize.d.ts +20 -0
  49. package/esm/lib/tokenize.js +41 -0
  50. package/esm/lib/tokenizer.d.ts +34 -0
  51. package/esm/lib/tokenizer.js +130 -0
  52. package/esm/lib/transform.d.ts +8 -0
  53. package/esm/lib/transform.js +34 -0
  54. package/esm/lib/types.d.ts +196 -0
  55. package/esm/lib/types.js +1 -0
  56. package/esm/lib/values.d.ts +29 -0
  57. package/esm/lib/values.js +83 -0
  58. package/esm/mod.d.ts +29 -0
  59. package/esm/mod.js +18 -0
  60. package/esm/package.json +3 -0
  61. package/package.json +27 -9
  62. package/src/_dnt.polyfills.ts +27 -0
  63. package/src/lib/argument.ts +100 -0
  64. package/src/lib/bind.ts +421 -0
  65. package/src/lib/checkpoint.ts +10 -0
  66. package/src/lib/command.ts +36 -0
  67. package/src/lib/dasherize.ts +7 -0
  68. package/src/lib/decode.ts +55 -0
  69. package/src/lib/definition.ts +17 -0
  70. package/src/lib/dynamic.ts +39 -0
  71. package/src/lib/env.ts +142 -0
  72. package/src/lib/extend.ts +12 -0
  73. package/src/lib/maybe.ts +22 -0
  74. package/src/lib/multiple.ts +23 -0
  75. package/src/lib/option.ts +57 -0
  76. package/src/lib/param.ts +96 -0
  77. package/src/lib/parse.ts +478 -0
  78. package/src/lib/pipeline.ts +549 -0
  79. package/src/lib/print.ts +216 -0
  80. package/src/lib/read.ts +133 -0
  81. package/src/lib/rest.ts +10 -0
  82. package/src/lib/result.ts +10 -0
  83. package/src/lib/route.ts +77 -0
  84. package/src/lib/toggle.ts +132 -0
  85. package/src/lib/tokenize.ts +74 -0
  86. package/src/lib/tokenizer.ts +191 -0
  87. package/src/lib/transform.ts +49 -0
  88. package/src/lib/types.ts +460 -0
  89. package/src/lib/values.ts +131 -0
  90. package/src/mod.ts +74 -0
@@ -0,0 +1,285 @@
1
+ import { Just } from "./maybe.js";
2
+ export function fromCLI(options) {
3
+ let { param, view, rest } = options;
4
+ return fromRead(param, param.cli.read(view), rest);
5
+ }
6
+ export function fromValues(options) {
7
+ let { param, route, rest } = options;
8
+ let claim = rest.values.claim({
9
+ route,
10
+ address: [param.name],
11
+ });
12
+ if (!claim.result.exists) {
13
+ return { exists: false };
14
+ }
15
+ return {
16
+ exists: true,
17
+ value: {
18
+ rest: {
19
+ ...rest,
20
+ values: claim.rest,
21
+ },
22
+ result: validate(param, claim.result.value.value, [param.name]),
23
+ },
24
+ };
25
+ }
26
+ export function fromEnv(options) {
27
+ let { param, route, rest } = options;
28
+ let claim = rest.envs.claim({
29
+ route,
30
+ address: [param.name],
31
+ key: param.env,
32
+ });
33
+ if (!claim.result.exists) {
34
+ return { exists: false };
35
+ }
36
+ let value = claim.result.value.value;
37
+ let candidates = param.cardinality === "one"
38
+ ? param.decode(value)
39
+ : param.decode([value]);
40
+ return {
41
+ exists: true,
42
+ value: {
43
+ rest: {
44
+ ...rest,
45
+ envs: claim.rest,
46
+ },
47
+ result: decode(param, value, candidates, [param.name]),
48
+ },
49
+ };
50
+ }
51
+ export function bindPhase(options) {
52
+ let { phase, segment } = options;
53
+ let rest = options.rest;
54
+ let params = Object.values(phase.model.params);
55
+ let pending = new Map(params.map((param) => [param.name, param]));
56
+ let results = new Map();
57
+ function settle(param, binding) {
58
+ rest = binding.rest;
59
+ results.set(param.name, binding.result);
60
+ pending.delete(param.name);
61
+ }
62
+ function accept(param, attempt) {
63
+ if (!attempt.exists) {
64
+ return;
65
+ }
66
+ settle(param, attempt.value);
67
+ }
68
+ let captures = new Map();
69
+ // Every pending reader proposes a claim against the same immutable view.
70
+ // Commit the proposal beginning earliest in argv, then recompute the view.
71
+ // This lets `--port 9000` outrank a positional claim on `9000` without
72
+ // attaching binding-policy metadata to either reader.
73
+ while (true) {
74
+ let horizon = first(rest.tokens, segment.range);
75
+ let offer;
76
+ for (let param of pending.values()) {
77
+ let view = rest.tokens.view({
78
+ range: segment.range,
79
+ through: horizon?.index,
80
+ });
81
+ let read = param.cli.read(view);
82
+ if (read.result.ok && !read.result.value.exists) {
83
+ continue;
84
+ }
85
+ let index = earliest(read.claim.tokens);
86
+ if (!offer || index < offer.index) {
87
+ offer = { param, read, index };
88
+ }
89
+ }
90
+ if (!offer) {
91
+ break;
92
+ }
93
+ if (offer.param.cardinality === "many") {
94
+ rest = {
95
+ ...rest,
96
+ tokens: offer.read.claim.rest,
97
+ };
98
+ let current = captures.get(offer.param.name) ?? {
99
+ param: offer.param,
100
+ values: [],
101
+ issues: [],
102
+ failed: false,
103
+ };
104
+ current.issues.push(...offer.read.result.issues ?? []);
105
+ if (offer.read.result.ok && offer.read.result.value.exists) {
106
+ current.values.push(offer.read.result.value.value);
107
+ }
108
+ else {
109
+ current.failed = true;
110
+ }
111
+ captures.set(offer.param.name, current);
112
+ continue;
113
+ }
114
+ accept(offer.param, fromRead(offer.param, offer.read, rest));
115
+ }
116
+ // deal wil all multiple
117
+ for (let capture of captures.values()) {
118
+ if (capture.failed) {
119
+ settle(capture.param, {
120
+ rest,
121
+ result: { ok: false, issues: capture.issues },
122
+ });
123
+ }
124
+ else {
125
+ let values = capture.values;
126
+ let candidates = values.every((value) => typeof value === "string")
127
+ ? capture.param.decode(values)
128
+ : [Just(values)];
129
+ let result = merge(decode(capture.param, values, candidates, [capture.param.name]), capture.issues);
130
+ settle(capture.param, { result, rest });
131
+ }
132
+ }
133
+ // Address sources have stable visibility once the route is known. They are
134
+ // tried only after CLI has reached its fixed point so a provisional CLI miss
135
+ // cannot let a lower-priority source settle the parameter too early.
136
+ for (let source of [fromEnv, fromValues]) {
137
+ for (let param of pending.values()) {
138
+ accept(param, source({
139
+ param,
140
+ route: segment.path,
141
+ rest,
142
+ }));
143
+ }
144
+ }
145
+ // Only total absence reaches the schema as undefined. This is where required,
146
+ // optional, and defaulting parameters diverge.
147
+ for (let param of pending.values()) {
148
+ results.set(param.name, validate(param, undefined, [param.name]));
149
+ }
150
+ let bindings = {};
151
+ let issues = [];
152
+ let valid = true;
153
+ // Collect in declaration order, independent of the source or sweep that
154
+ // settled each parameter.
155
+ for (let param of params) {
156
+ let result = results.get(param.name);
157
+ issues.push(...result.issues ?? []);
158
+ if (result.ok) {
159
+ bindings[param.name] = result.value;
160
+ }
161
+ else {
162
+ valid = false;
163
+ }
164
+ }
165
+ let model = {};
166
+ for (let step of phase.model.steps) {
167
+ let result = step(model, bindings);
168
+ if (!result.ok) {
169
+ valid = false;
170
+ issues.push(...result.issues);
171
+ break;
172
+ }
173
+ else {
174
+ model = result.value;
175
+ }
176
+ }
177
+ return { rest, model, issues, valid };
178
+ }
179
+ function fromRead(param, read, rest) {
180
+ let path = [param.name];
181
+ if (!read.result.ok) {
182
+ return {
183
+ exists: true,
184
+ value: {
185
+ rest: {
186
+ ...rest,
187
+ tokens: read.claim.rest,
188
+ },
189
+ result: read.result,
190
+ },
191
+ };
192
+ }
193
+ if (!read.result.value.exists) {
194
+ return { exists: false };
195
+ }
196
+ let value = read.result.value.value;
197
+ let candidates = typeof value === "string"
198
+ ? param.decode(value)
199
+ : [Just(value)];
200
+ let result = merge(decode(param, value, candidates, path), read.result.issues);
201
+ return {
202
+ exists: true,
203
+ value: {
204
+ rest: {
205
+ ...rest,
206
+ tokens: read.claim.rest,
207
+ },
208
+ result,
209
+ },
210
+ };
211
+ }
212
+ function decode(param, value, candidates, path) {
213
+ if (candidates.length === 0) {
214
+ return {
215
+ ok: false,
216
+ issues: [{
217
+ message: `unable to decode ${JSON.stringify(value)}`,
218
+ path,
219
+ }],
220
+ };
221
+ }
222
+ let issues;
223
+ let values = candidates.flatMap((candidate) => candidate.exists ? [candidate.value] : []);
224
+ for (let candidate of values) {
225
+ let result = validate(param, candidate, path);
226
+ if (result.ok) {
227
+ return result;
228
+ }
229
+ issues = issues ?? result.issues;
230
+ }
231
+ return {
232
+ ok: false,
233
+ issues: issues ?? [],
234
+ };
235
+ }
236
+ function validate(param, value, path) {
237
+ let validated = param.schema["~standard"].validate(value);
238
+ if (validated instanceof Promise) {
239
+ return {
240
+ ok: false,
241
+ issues: [{
242
+ message: `async schemas are not allowed`,
243
+ path,
244
+ }],
245
+ };
246
+ }
247
+ if (validated.issues) {
248
+ return {
249
+ ok: false,
250
+ issues: validated.issues.map((issue) => ({
251
+ ...issue,
252
+ message: issue.message,
253
+ path,
254
+ })),
255
+ };
256
+ }
257
+ return {
258
+ ok: true,
259
+ issues: [],
260
+ value: validated.value,
261
+ };
262
+ }
263
+ function merge(result, issues) {
264
+ if (!issues || issues.length === 0) {
265
+ return result;
266
+ }
267
+ return {
268
+ ...result,
269
+ issues: [...issues, ...(result.issues ?? [])],
270
+ };
271
+ }
272
+ function first(tokens, range) {
273
+ for (let token of tokens.view({ range })) {
274
+ if (token.type === "word") {
275
+ return token;
276
+ }
277
+ }
278
+ }
279
+ function earliest(tokens) {
280
+ let first = Infinity;
281
+ for (let token of tokens) {
282
+ first = Math.min(first, token.index);
283
+ }
284
+ return first;
285
+ }
@@ -0,0 +1,3 @@
1
+ import type { DynamicElement } from "./pipeline.js";
2
+ import { type ValueSource, withValues } from "./values.js";
3
+ export declare function checkpoint(): DynamicElement<ValueSource[], ReturnType<typeof withValues>>;
@@ -0,0 +1,5 @@
1
+ import { dynamic } from "./dynamic.js";
2
+ import { withValues } from "./values.js";
3
+ export function checkpoint() {
4
+ return dynamic((values) => withValues(values));
5
+ }
@@ -0,0 +1,6 @@
1
+ import type { Check, Fold, Materialize, Unary } from "./pipeline.js";
2
+ import type { Definition, Done, Route } from "./types.js";
3
+ export type CommandZero<N extends string = string> = Route<N, "help" | "execute", [
4
+ Done<{}, []>
5
+ ]>;
6
+ export declare function command<const N extends string, const E extends readonly Unary[]>(start: Definition<N>, ...elements: E & Check<CommandZero<N>, E>): Materialize<Fold<CommandZero<N>, E>>;
@@ -0,0 +1,16 @@
1
+ export function command(start, ...elements) {
2
+ let zero = {
3
+ ...start,
4
+ methods: ["help", "execute"],
5
+ phases: [{
6
+ model: {
7
+ params: {},
8
+ steps: [],
9
+ },
10
+ routes: [],
11
+ values: [],
12
+ envs: [],
13
+ }],
14
+ };
15
+ return elements.reduce((value, element) => element(value), zero);
16
+ }
@@ -0,0 +1 @@
1
+ export declare function dasherize(name: string): string;
@@ -0,0 +1,7 @@
1
+ export function dasherize(name) {
2
+ return name
3
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2")
4
+ .replace(/([a-z\d])([A-Z])/g, "$1-$2")
5
+ .replace(/[\s_]+/g, "-")
6
+ .toLowerCase();
7
+ }
@@ -0,0 +1,6 @@
1
+ import { type Maybe } from "./maybe.js";
2
+ export type Decoder<A, B> = (value: A) => Maybe<B>[];
3
+ export declare const number: Decoder<string, number>;
4
+ export declare const scalar: Decoder<string, string | number>;
5
+ export declare const boolean: Decoder<string, boolean>;
6
+ export declare function multiple<A, B>(decoder: Decoder<A, B>): Decoder<A[], B[]>;
@@ -0,0 +1,46 @@
1
+ import { Just, Nothing } from "./maybe.js";
2
+ export const number = (value) => {
3
+ if (!numeric.test(value)) {
4
+ return [Nothing()];
5
+ }
6
+ let decoded = Number(value);
7
+ return Number.isFinite(decoded) ? [Just(decoded)] : [Nothing()];
8
+ };
9
+ export const scalar = (value) => {
10
+ return [...number(value), Just(value)];
11
+ };
12
+ export const boolean = (value) => {
13
+ if (value === "true") {
14
+ return [Just(true)];
15
+ }
16
+ if (value === "false") {
17
+ return [Just(false)];
18
+ }
19
+ return [Nothing()];
20
+ };
21
+ export function multiple(decoder) {
22
+ return (input) => {
23
+ let decodings = input.map(decoder);
24
+ let result = [];
25
+ for (let i = 0; i < decodings.length; i++) {
26
+ let decoding = decodings[i];
27
+ for (let j = 0; j < decoding.length; j++) {
28
+ let row = result[j];
29
+ if (!row) {
30
+ row = result[j] = Just([]);
31
+ }
32
+ if (row.exists) {
33
+ let candidate = decoding[j];
34
+ if (candidate.exists) {
35
+ row.value[i] = candidate.value;
36
+ }
37
+ else {
38
+ result[j] = Nothing();
39
+ }
40
+ }
41
+ }
42
+ }
43
+ return result;
44
+ };
45
+ }
46
+ const numeric = /^-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?$/;
@@ -0,0 +1,4 @@
1
+ import { type IdentityElement } from "./pipeline.js";
2
+ import type { Definition } from "./types.js";
3
+ export declare function name<N extends string>(name: N): Definition<N>;
4
+ export declare function description(description: string): IdentityElement<Definition<string>>;
@@ -0,0 +1,10 @@
1
+ import { brand } from "./pipeline.js";
2
+ export function name(name) {
3
+ return { name };
4
+ }
5
+ export function description(description) {
6
+ return brand((definition) => ({
7
+ ...definition,
8
+ description,
9
+ }));
10
+ }
@@ -0,0 +1,6 @@
1
+ import type { AnyRoute } from "./types.js";
2
+ import { type AnyElement, type DynamicElement } from "./pipeline.js";
3
+ export type { ConjoinPhases, Seed } from "./pipeline.js";
4
+ export type PhaseOf<R extends AnyRoute> = R["phases"][0];
5
+ export type PhasesOf<R extends AnyRoute> = R["phases"];
6
+ export declare function dynamic<Requirement, E>(extension: (requires: Requirement) => E, ..._valid: E extends AnyElement ? [] : [never]): DynamicElement<Requirement, Extract<E, AnyElement>>;
@@ -0,0 +1,21 @@
1
+ import { brand } from "./pipeline.js";
2
+ export function dynamic(extension, ..._valid) {
3
+ return brand((route) => {
4
+ let phases = [...route.phases];
5
+ let phase = phases.pop();
6
+ phases.push({
7
+ ...phase,
8
+ resolver: extension,
9
+ });
10
+ phases.push({
11
+ model: {
12
+ params: {},
13
+ steps: [],
14
+ },
15
+ routes: [],
16
+ values: [],
17
+ envs: [],
18
+ });
19
+ return { ...route, phases };
20
+ });
21
+ }
@@ -0,0 +1,34 @@
1
+ import type { Maybe } from "./maybe.js";
2
+ import type { AnyParam } from "./param.js";
3
+ import { type IdentityElement } from "./pipeline.js";
4
+ import type { AnyRoute, RoutePath } from "./types.js";
5
+ export interface EnvSource {
6
+ readonly name: string;
7
+ readonly value: Environment;
8
+ }
9
+ export type Environment = Readonly<Record<string, string | undefined>>;
10
+ export interface EnvClaim {
11
+ readonly result: Maybe<{
12
+ readonly source: string;
13
+ readonly address: readonly string[];
14
+ readonly key: string;
15
+ readonly value: string;
16
+ }>;
17
+ readonly rest: Envs;
18
+ }
19
+ export interface EnvClaimOptions {
20
+ readonly route: readonly string[];
21
+ readonly address: readonly string[];
22
+ readonly key?: string;
23
+ }
24
+ export declare function env(key: string): IdentityElement<AnyParam>;
25
+ export declare function withEnvs(envs: readonly EnvSource[]): IdentityElement<AnyRoute>;
26
+ export declare class Envs {
27
+ mounts: Map<RoutePath, EnvSource[]>;
28
+ claims: Set<ClaimId>;
29
+ constructor(mounts?: Map<RoutePath, EnvSource[]>, claims?: Set<ClaimId>);
30
+ mount(path: readonly string[], sources: readonly EnvSource[]): Envs;
31
+ claim({ route, address, key }: EnvClaimOptions): EnvClaim;
32
+ }
33
+ type ClaimId = string;
34
+ export {};
package/esm/lib/env.js ADDED
@@ -0,0 +1,83 @@
1
+ import { brand } from "./pipeline.js";
2
+ export function env(key) {
3
+ return brand((param) => ({ ...param, env: key }));
4
+ }
5
+ export function withEnvs(envs) {
6
+ return brand((route) => {
7
+ let phases = [...route.phases];
8
+ let phase = phases.pop();
9
+ phases.push({
10
+ ...phase,
11
+ envs: phase.envs.concat(envs),
12
+ });
13
+ return {
14
+ ...route,
15
+ phases,
16
+ };
17
+ });
18
+ }
19
+ export class Envs {
20
+ mounts;
21
+ claims;
22
+ constructor(mounts = new Map(), claims = new Set()) {
23
+ this.mounts = mounts;
24
+ this.claims = claims;
25
+ }
26
+ mount(path, sources) {
27
+ if (sources.length === 0) {
28
+ return this;
29
+ }
30
+ let id = routeId(path);
31
+ return new Envs(new Map([...this.mounts.entries(), [
32
+ id,
33
+ (this.mounts.get(id) ?? []).concat(sources),
34
+ ]]), this.claims);
35
+ }
36
+ claim({ route, address, key = envKey([...route, ...address]) }) {
37
+ let id = claimId([...route, ...address]);
38
+ let nope = { result: { exists: false }, rest: this };
39
+ if (this.claims.has(id)) {
40
+ return nope;
41
+ }
42
+ for (let end = route.length; end >= 0; end--) {
43
+ let mount = routeId(route.slice(0, end));
44
+ let sources = this.mounts.get(mount) ?? [];
45
+ for (let { name, value } of sources) {
46
+ if (!Object.hasOwn(value, key) || typeof value[key] === "undefined") {
47
+ continue;
48
+ }
49
+ let rest = new Envs(this.mounts, new Set([...this.claims, id]));
50
+ return {
51
+ result: {
52
+ exists: true,
53
+ value: {
54
+ source: name,
55
+ address,
56
+ key,
57
+ value: value[key],
58
+ },
59
+ },
60
+ rest,
61
+ };
62
+ }
63
+ }
64
+ return nope;
65
+ }
66
+ }
67
+ function routeId(path) {
68
+ return `/${path.join("/")}`;
69
+ }
70
+ function claimId(address) {
71
+ return JSON.stringify(address);
72
+ }
73
+ function envKey(address) {
74
+ return address.map(normalize).filter(Boolean).join("_");
75
+ }
76
+ function normalize(value) {
77
+ return value
78
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2")
79
+ .replace(/([a-z\d])([A-Z])/g, "$1_$2")
80
+ .replace(/[^A-Za-z\d]+/g, "_")
81
+ .replace(/^_+|_+$/g, "")
82
+ .toUpperCase();
83
+ }
@@ -0,0 +1,2 @@
1
+ import { type Extension, type Unary } from "./pipeline.js";
2
+ export declare function extend<const E extends readonly Unary[]>(...elements: E): Extension<E>;
@@ -0,0 +1,4 @@
1
+ import { brand } from "./pipeline.js";
2
+ export function extend(...elements) {
3
+ return brand((start) => elements.reduce((value, element) => element(value), start));
4
+ }
@@ -0,0 +1,9 @@
1
+ export type Maybe<T> = {
2
+ readonly exists: false;
3
+ } | {
4
+ readonly exists: true;
5
+ readonly value: T;
6
+ };
7
+ export declare function Just(): Maybe<void>;
8
+ export declare function Just<T>(value: T): Maybe<T>;
9
+ export declare function Nothing<T = void>(): Maybe<T>;
@@ -0,0 +1,12 @@
1
+ export function Just(value) {
2
+ if (typeof value === "undefined") {
3
+ return { exists: true };
4
+ }
5
+ else {
6
+ return { exists: true, value };
7
+ }
8
+ }
9
+ export function Nothing() {
10
+ return nothing;
11
+ }
12
+ const nothing = { exists: false };
@@ -0,0 +1,8 @@
1
+ import type { Param } from "./param.js";
2
+ import { type Transform, type TransformElement } from "./pipeline.js";
3
+ export declare function multiple(): TransformElement<MultipleTransform>;
4
+ interface MultipleTransform extends Transform {
5
+ readonly input: Param<string, unknown, "one">;
6
+ readonly output: this["input"] extends Param<infer K, infer Model, "one"> ? Param<K, Model, "many"> : never;
7
+ }
8
+ export {};
@@ -0,0 +1,9 @@
1
+ import { multiple as decode } from "./decode.js";
2
+ import { mark } from "./pipeline.js";
3
+ export function multiple() {
4
+ return mark((param) => ({
5
+ ...param,
6
+ cardinality: "many",
7
+ decode: decode(param.decode),
8
+ }));
9
+ }
@@ -0,0 +1,7 @@
1
+ import { type Param, type ParamModel } from "./param.js";
2
+ import { type Check, type Fold, type ModelElement, type Unary } from "./pipeline.js";
3
+ import type { Definition } from "./types.js";
4
+ export declare function option<const N extends string, const E extends readonly Unary[]>(named: Definition<N>, ...elements: E & Check<Zero<N>, E>): ElementOf<N, Fold<Zero<N>, E>>;
5
+ type Zero<N extends string> = Param<N, unknown, "one">;
6
+ type ElementOf<N extends string, P> = P extends Param<N, infer Model, infer _Cardinality> ? ModelElement<ParamModel<N, Model>> : never;
7
+ export {};
@@ -0,0 +1,31 @@
1
+ import { param } from "./param.js";
2
+ import { dasherize } from "./dasherize.js";
3
+ import { brand, } from "./pipeline.js";
4
+ import { cli } from "./read.js";
5
+ export function option(named, ...elements) {
6
+ const added = elements.reduce((value, element) => element(value), param(named, cli([`--${dasherize(named.name)}`])));
7
+ return brand((route) => {
8
+ let phases = [...route.phases];
9
+ let phase = phases.pop();
10
+ phases.push({
11
+ ...phase,
12
+ model: {
13
+ params: {
14
+ ...phase.model.params,
15
+ [added.name]: added,
16
+ },
17
+ steps: phase.model.steps.concat((current, bindings) => ({
18
+ ok: true,
19
+ value: {
20
+ ...current,
21
+ [added.name]: bindings[added.name],
22
+ },
23
+ })),
24
+ },
25
+ });
26
+ return {
27
+ ...route,
28
+ phases,
29
+ };
30
+ });
31
+ }
@@ -0,0 +1,26 @@
1
+ import { type Decoder } from "./decode.js";
2
+ import { type Check, type Fold, type ModelPatch, type Transform, type TransformElement, type Unary } from "./pipeline.js";
3
+ import type { CLIBinding } from "./read.js";
4
+ import type { Definition, OutputOf, Schema } from "./types.js";
5
+ export interface Param<K extends string, T, C extends Cardinality> extends Definition<K> {
6
+ schema: Schema<T>;
7
+ cardinality: C;
8
+ cli: CLIBinding;
9
+ decode: Decoder<Representation<C>, Decoded<C>>;
10
+ env?: string;
11
+ }
12
+ export type Cardinality = "one" | "many";
13
+ export type AnyParam = Param<string, unknown, "one"> | Param<string, unknown, "many">;
14
+ export type ParamModel<K extends string, V> = ModelPatch<{
15
+ [P in K]: V;
16
+ }>;
17
+ export type Representation<C extends Cardinality> = C extends "many" ? string[] : string;
18
+ export declare function param<const K extends string, const E extends readonly Unary[]>(start: Definition<K>, ...elements: E & Check<ParamZero<K>, E>): Fold<ParamZero<K>, E>;
19
+ export declare function schema<S extends Schema>(schema: S): TransformElement<SchemaTransform<OutputOf<S>>>;
20
+ interface SchemaTransform<Output> extends Transform {
21
+ readonly input: AnyParam;
22
+ readonly output: this["input"] extends Param<infer N, unknown, infer C> ? Param<N, Output, C> : never;
23
+ }
24
+ type ParamZero<K extends string> = Param<K, unknown, "one">;
25
+ type Decoded<C extends Cardinality> = C extends "many" ? unknown[] : unknown;
26
+ export {};