@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
package/src/lib/env.ts ADDED
@@ -0,0 +1,142 @@
1
+ import type { Maybe } from "./maybe.js";
2
+ import type { AnyParam } from "./param.js";
3
+ import { brand, type IdentityElement } from "./pipeline.js";
4
+ import type { AnyRoute, RoutePath } from "./types.js";
5
+
6
+ export interface EnvSource {
7
+ readonly name: string;
8
+ readonly value: Environment;
9
+ }
10
+
11
+ export type Environment = Readonly<Record<string, string | undefined>>;
12
+
13
+ export interface EnvClaim {
14
+ readonly result: Maybe<{
15
+ readonly source: string;
16
+ readonly address: readonly string[];
17
+ readonly key: string;
18
+ readonly value: string;
19
+ }>;
20
+ readonly rest: Envs;
21
+ }
22
+
23
+ export interface EnvClaimOptions {
24
+ readonly route: readonly string[];
25
+ readonly address: readonly string[];
26
+ readonly key?: string;
27
+ }
28
+
29
+ export function env(
30
+ key: string,
31
+ ): IdentityElement<AnyParam> {
32
+ return brand<IdentityElement<AnyParam>>(
33
+ (param: AnyParam) => ({ ...param, env: key }),
34
+ );
35
+ }
36
+
37
+ export function withEnvs(
38
+ envs: readonly EnvSource[],
39
+ ): IdentityElement<AnyRoute> {
40
+ return brand<IdentityElement<AnyRoute>>(
41
+ (route: AnyRoute) => {
42
+ let phases = [...route.phases];
43
+ let phase = phases.pop()!;
44
+ phases.push({
45
+ ...phase,
46
+ envs: phase.envs.concat(envs),
47
+ });
48
+ return {
49
+ ...route,
50
+ phases,
51
+ };
52
+ },
53
+ );
54
+ }
55
+
56
+ export class Envs {
57
+ mounts: Map<RoutePath, EnvSource[]>;
58
+ claims: Set<ClaimId>;
59
+
60
+ constructor(
61
+ mounts: Map<RoutePath, EnvSource[]> = new Map(),
62
+ claims: Set<ClaimId> = new Set(),
63
+ ) {
64
+ this.mounts = mounts;
65
+ this.claims = claims;
66
+ }
67
+
68
+ mount(path: readonly string[], sources: readonly EnvSource[]): Envs {
69
+ if (sources.length === 0) {
70
+ return this;
71
+ }
72
+ let id = routeId(path);
73
+
74
+ return new Envs(
75
+ new Map([...this.mounts.entries(), [
76
+ id,
77
+ (this.mounts.get(id) ?? []).concat(sources),
78
+ ]]),
79
+ this.claims,
80
+ );
81
+ }
82
+
83
+ claim(
84
+ { route, address, key = envKey([...route, ...address]) }: EnvClaimOptions,
85
+ ): EnvClaim {
86
+ let id = claimId([...route, ...address]);
87
+ let nope: EnvClaim = { result: { exists: false }, rest: this };
88
+ if (this.claims.has(id)) {
89
+ return nope;
90
+ }
91
+
92
+ for (let end = route.length; end >= 0; end--) {
93
+ let mount = routeId(route.slice(0, end));
94
+ let sources = this.mounts.get(mount) ?? [];
95
+
96
+ for (let { name, value } of sources) {
97
+ if (!Object.hasOwn(value, key) || typeof value[key] === "undefined") {
98
+ continue;
99
+ }
100
+
101
+ let rest = new Envs(this.mounts, new Set([...this.claims, id]));
102
+ return {
103
+ result: {
104
+ exists: true,
105
+ value: {
106
+ source: name,
107
+ address,
108
+ key,
109
+ value: value[key],
110
+ },
111
+ },
112
+ rest,
113
+ };
114
+ }
115
+ }
116
+
117
+ return nope;
118
+ }
119
+ }
120
+
121
+ type ClaimId = string;
122
+
123
+ function routeId(path: readonly string[]): RoutePath {
124
+ return `/${path.join("/")}`;
125
+ }
126
+
127
+ function claimId(address: readonly string[]): ClaimId {
128
+ return JSON.stringify(address);
129
+ }
130
+
131
+ function envKey(address: readonly string[]): string {
132
+ return address.map(normalize).filter(Boolean).join("_");
133
+ }
134
+
135
+ function normalize(value: string): string {
136
+ return value
137
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2")
138
+ .replace(/([a-z\d])([A-Z])/g, "$1_$2")
139
+ .replace(/[^A-Za-z\d]+/g, "_")
140
+ .replace(/^_+|_+$/g, "")
141
+ .toUpperCase();
142
+ }
@@ -0,0 +1,12 @@
1
+ import { brand, type Extension, type Unary } from "./pipeline.js";
2
+
3
+ export function extend<const E extends readonly Unary[]>(
4
+ ...elements: E
5
+ ): Extension<E> {
6
+ return brand<Extension<E>>((start: unknown): unknown =>
7
+ elements.reduce<unknown>(
8
+ (value, element) => element(value as never),
9
+ start,
10
+ )
11
+ );
12
+ }
@@ -0,0 +1,22 @@
1
+ export type Maybe<T> = {
2
+ readonly exists: false;
3
+ } | {
4
+ readonly exists: true;
5
+ readonly value: T;
6
+ };
7
+
8
+ export function Just(): Maybe<void>;
9
+ export function Just<T>(value: T): Maybe<T>;
10
+ export function Just<T>(value?: T): Maybe<T | undefined> {
11
+ if (typeof value === "undefined") {
12
+ return { exists: true } as Maybe<T>;
13
+ } else {
14
+ return { exists: true, value };
15
+ }
16
+ }
17
+
18
+ export function Nothing<T = void>(): Maybe<T> {
19
+ return nothing as Maybe<T>;
20
+ }
21
+
22
+ const nothing = { exists: false };
@@ -0,0 +1,23 @@
1
+ import { multiple as decode } from "./decode.js";
2
+ import type { Param } from "./param.js";
3
+ import { mark, type Transform, type TransformElement } from "./pipeline.js";
4
+
5
+ export function multiple(): TransformElement<MultipleTransform> {
6
+ return mark<MultipleTransform>(
7
+ (param: Param<string, unknown, "one">) => ({
8
+ ...param,
9
+ cardinality: "many",
10
+ decode: decode(param.decode),
11
+ }),
12
+ );
13
+ }
14
+
15
+ interface MultipleTransform extends Transform {
16
+ readonly input: Param<string, unknown, "one">;
17
+ readonly output: this["input"] extends Param<
18
+ infer K,
19
+ infer Model,
20
+ "one"
21
+ > ? Param<K, Model, "many">
22
+ : never;
23
+ }
@@ -0,0 +1,57 @@
1
+ import { type AnyParam, type Param, param, type ParamModel } from "./param.js";
2
+ import { dasherize } from "./dasherize.js";
3
+ import {
4
+ brand,
5
+ type Check,
6
+ type Fold,
7
+ type ModelElement,
8
+ type Unary,
9
+ } from "./pipeline.js";
10
+ import { cli } from "./read.js";
11
+ import type { AnyRoute, Definition } from "./types.js";
12
+
13
+ export function option<
14
+ const N extends string,
15
+ const E extends readonly Unary[],
16
+ >(
17
+ named: Definition<N>,
18
+ ...elements: E & Check<Zero<N>, E>
19
+ ): ElementOf<N, Fold<Zero<N>, E>> {
20
+ const added = elements.reduce<unknown>(
21
+ (value, element) => element(value as never),
22
+ param(named, cli([`--${dasherize(named.name)}`])),
23
+ ) as AnyParam;
24
+
25
+ return brand<ElementOf<N, Fold<Zero<N>, E>>>(
26
+ (route: AnyRoute) => {
27
+ let phases = [...route.phases];
28
+ let phase = phases.pop()!;
29
+ phases.push({
30
+ ...phase,
31
+ model: {
32
+ params: {
33
+ ...phase.model.params,
34
+ [added.name]: added,
35
+ },
36
+ steps: phase.model.steps.concat((current, bindings) => ({
37
+ ok: true,
38
+ value: {
39
+ ...current,
40
+ [added.name]: bindings[added.name],
41
+ },
42
+ })),
43
+ },
44
+ });
45
+ return {
46
+ ...route,
47
+ phases,
48
+ };
49
+ },
50
+ );
51
+ }
52
+
53
+ type Zero<N extends string> = Param<N, unknown, "one">;
54
+
55
+ type ElementOf<N extends string, P> = P extends
56
+ Param<N, infer Model, infer _Cardinality> ? ModelElement<ParamModel<N, Model>>
57
+ : never;
@@ -0,0 +1,96 @@
1
+ import { type Decoder, scalar } from "./decode.js";
2
+ import {
3
+ type Check,
4
+ type Fold,
5
+ mark,
6
+ type ModelPatch,
7
+ type Transform,
8
+ type TransformElement,
9
+ type Unary,
10
+ } from "./pipeline.js";
11
+ import type { CLIBinding } from "./read.js";
12
+ import type { Definition, OutputOf, Schema } from "./types.js";
13
+
14
+ export interface Param<K extends string, T, C extends Cardinality>
15
+ extends Definition<K> {
16
+ schema: Schema<T>;
17
+ cardinality: C;
18
+ cli: CLIBinding;
19
+ decode: Decoder<Representation<C>, Decoded<C>>;
20
+ env?: string;
21
+ }
22
+
23
+ export type Cardinality = "one" | "many";
24
+
25
+ export type AnyParam =
26
+ | Param<string, unknown, "one">
27
+ | Param<string, unknown, "many">;
28
+
29
+ export type ParamModel<K extends string, V> = ModelPatch<{ [P in K]: V }>;
30
+
31
+ export type Representation<C extends Cardinality> = C extends "many" ? string[]
32
+ : string;
33
+
34
+ export function param<
35
+ const K extends string,
36
+ const E extends readonly Unary[],
37
+ >(
38
+ start: Definition<K>,
39
+ ...elements: E & Check<ParamZero<K>, E>
40
+ ): Fold<ParamZero<K>, E> {
41
+ let zero: ParamZero<K> = {
42
+ ...start,
43
+ schema: unknown,
44
+ cardinality: "one",
45
+ cli: {
46
+ read(tokens) {
47
+ let claim = tokens.claimAll(() => false);
48
+ return {
49
+ result: {
50
+ ok: true,
51
+ value: { exists: false },
52
+ issues: [],
53
+ },
54
+ claim,
55
+ };
56
+ },
57
+ },
58
+ decode: scalar,
59
+ };
60
+
61
+ return elements.reduce<unknown>(
62
+ (value, element) => element(value as never),
63
+ zero,
64
+ ) as Fold<ParamZero<K>, E>;
65
+ }
66
+
67
+ export function schema<S extends Schema>(
68
+ schema: S,
69
+ ): TransformElement<SchemaTransform<OutputOf<S>>> {
70
+ return mark<SchemaTransform<OutputOf<S>>>(
71
+ (param: AnyParam) => ({
72
+ ...param,
73
+ schema,
74
+ }),
75
+ );
76
+ }
77
+
78
+ interface SchemaTransform<Output> extends Transform {
79
+ readonly input: AnyParam;
80
+ readonly output: this["input"] extends Param<infer N, unknown, infer C>
81
+ ? Param<N, Output, C>
82
+ : never;
83
+ }
84
+
85
+ const unknown: Schema<unknown> = {
86
+ "~standard": {
87
+ version: 1,
88
+ vendor: "@bomb.sh/router",
89
+ validate: (value) => ({ value }),
90
+ },
91
+ };
92
+
93
+ type ParamZero<K extends string> = Param<K, unknown, "one">;
94
+
95
+ type Decoded<C extends Cardinality> = C extends "many" ? unknown[]
96
+ : unknown;