@velarscript/node 0.12.0 → 0.13.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 (41) hide show
  1. package/README.md +40 -11
  2. package/dist/compiler.d.ts.map +1 -1
  3. package/dist/compiler.js +99 -23
  4. package/dist/compiler.js.map +1 -1
  5. package/dist/http-runtime.d.ts.map +1 -1
  6. package/dist/http-runtime.js +19 -7
  7. package/dist/http-runtime.js.map +1 -1
  8. package/dist/node-host-runtime.d.ts.map +1 -1
  9. package/dist/node-host-runtime.js +14 -7
  10. package/dist/node-host-runtime.js.map +1 -1
  11. package/dist/node-host-worker-runtime.d.ts.map +1 -1
  12. package/dist/node-host-worker-runtime.js +59 -31
  13. package/dist/node-host-worker-runtime.js.map +1 -1
  14. package/dist/project-config.d.ts +1 -1
  15. package/dist/project-config.d.ts.map +1 -1
  16. package/dist/route-shape.d.ts +20 -0
  17. package/dist/route-shape.d.ts.map +1 -0
  18. package/dist/route-shape.js +36 -0
  19. package/dist/route-shape.js.map +1 -0
  20. package/dist/serve-runtime.d.ts.map +1 -1
  21. package/dist/serve-runtime.js +312 -52
  22. package/dist/serve-runtime.js.map +1 -1
  23. package/dist/server-analyzer.d.ts +52 -0
  24. package/dist/server-analyzer.d.ts.map +1 -1
  25. package/dist/server-analyzer.js +377 -12
  26. package/dist/server-analyzer.js.map +1 -1
  27. package/dist/server-ast.d.ts +13 -1
  28. package/dist/server-ast.d.ts.map +1 -1
  29. package/dist/server-ast.js.map +1 -1
  30. package/dist/server-emitter.d.ts +1 -0
  31. package/dist/server-emitter.d.ts.map +1 -1
  32. package/dist/server-emitter.js +13 -2
  33. package/dist/server-emitter.js.map +1 -1
  34. package/dist/server-parser.d.ts +2 -0
  35. package/dist/server-parser.d.ts.map +1 -1
  36. package/dist/server-parser.js +45 -20
  37. package/dist/server-parser.js.map +1 -1
  38. package/dist/websocket-runtime.d.ts.map +1 -1
  39. package/dist/websocket-runtime.js +73 -17
  40. package/dist/websocket-runtime.js.map +1 -1
  41. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"serve-runtime.js","sourceRoot":"","sources":["../src/serve-runtime.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,8EAA8E;AAC9E,+EAA+E;AAC/E,MAAM,CAAC,MAAM,wBAAwB,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAknGjD,CAAC,SAAS,EAAE,CAAC"}
1
+ {"version":3,"file":"serve-runtime.js","sourceRoot":"","sources":["../src/serve-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gCAAgC,EAAE,MAAM,kBAAkB,CAAC;AAEpE,0EAA0E;AAC1E,8EAA8E;AAC9E,+EAA+E;AAC/E,MAAM,CAAC,MAAM,wBAAwB,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6CAw/BL,gCAAgC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA63E5E,CAAC,SAAS,EAAE,CAAC"}
@@ -18,6 +18,16 @@ export declare function parseRouteResultHint(value: string | undefined): {
18
18
  export declare class VelarNodeAnalyzer extends Analyzer {
19
19
  private readonly contextualRouteParameters;
20
20
  private readonly routeInputs;
21
+ /** Servers declared by the module under analysis, the only spread targets this analyzer can resolve. */
22
+ private readonly moduleServers;
23
+ /** Module-level `const alias = name` and `let alias = name` bindings, so a spread of an alias resolves to the server it names. */
24
+ private readonly moduleServerAliases;
25
+ /** Local names imported from velar/serve that name a path-preserving combinator, so a spread of a call resolves through it. */
26
+ private readonly moduleServeCombinators;
27
+ /** One answer per `let` alias name to "was this binding ever reassigned?", because the predicate walks the whole program. */
28
+ private readonly stableAliases;
29
+ /** The program under analysis, held for the alias-stability walk. */
30
+ private moduleProgram;
21
31
  private readonly nodeModulePath;
22
32
  constructor(context?: AnalysisContext, extensions?: readonly CompilerAnalysisExtension[]);
23
33
  analyze(program: Program): readonly import("@velarscript/compiler").Diagnostic[];
@@ -27,6 +37,48 @@ export declare class VelarNodeAnalyzer extends Analyzer {
27
37
  readonly kind: string;
28
38
  }, parameter: Parameter): ValueType | null;
29
39
  private analyzeServer;
40
+ /**
41
+ * Enters one route into this server's shape map and compares it against every route already
42
+ * entered, whether that route was written here or composed in by a spread. Composition is why the
43
+ * entries carry an origin: a conflicting route the author cannot see in his own file has to name
44
+ * the server it came from.
45
+ */
46
+ private recordRoute;
47
+ /**
48
+ * The routes and fallback a spread composes into the server that writes it, or null when the
49
+ * spread is not statically resolvable. This analyzer sees one module, so a spread contributes
50
+ * only when its value reaches a server declared in this module: a plain identifier, an alias of
51
+ * one, or a velar/serve combinator call around one — `use`, `bodyLimit`, `docs` and `lifecycle`
52
+ * carry paths through unchanged, and `prefix` translates them by its literal path. An imported
53
+ * server, a computed prefix path, or any other expression is let through unchecked, because a
54
+ * false conflict here would block a correct program; D90 R19's runtime referee judges the final
55
+ * table at assembly instead. Composition is followed transitively; the visited set bounds a
56
+ * cycle and starts holding the composing server, so a cycle never folds a server's own routes
57
+ * back into itself and reports each as conflicting with itself.
58
+ */
59
+ private composedItems;
60
+ /**
61
+ * The server declaration a spread value names, or null when it is anything else. A
62
+ * `const other = base` alias chain of this module's own servers resolves too, because the alias
63
+ * holds exactly that ServeApp — and so does a `let` alias the whole module never reassigns,
64
+ * because an unwritten `let` holds its initializer exactly as a `const` does. A reassigned or
65
+ * ambiguous `let`, a member path, a conditional, an import, or a parameter contributes nothing.
66
+ * A call resolves through the path-preserving velar/serve combinators when the callee still
67
+ * reaches its velar/serve import: `prefix` with a literal path translates what its app argument
68
+ * declares, and `use`/`bodyLimit`/`docs`/`lifecycle` pass it through untouched. A computed
69
+ * prefix path contributes nothing — the assembly-time referee owns it. An alias's initializer
70
+ * re-enters this resolver whole, so `const scoped = prefix("/api", routes)` resolves exactly as
71
+ * the spelled-out spread does; the followed set bounds an alias cycle.
72
+ */
73
+ private resolveComposedServer;
74
+ /** Whether a `let` alias has never been reassigned, asked once per name and program. */
75
+ private aliasBindingIsStable;
76
+ /**
77
+ * Whether a name still reaches the declaration this module recorded for it. An import, a
78
+ * shadowing binding, or a parameter of the same name reaches a different binding.
79
+ */
80
+ private resolvesTo;
81
+ private analyzeNotFound;
30
82
  private analyzeRoute;
31
83
  }
32
84
  export declare function inferNodeIntrinsic(context: CompilerIntrinsicAnalysisContext): ValueType | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"server-analyzer.d.ts","sourceRoot":"","sources":["../src/server-analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EASR,KAAK,eAAe,EACpB,KAAK,yBAAyB,EAC9B,KAAK,gCAAgC,EACrC,KAAK,SAAS,EACd,KAAK,OAAO,EACZ,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,iCAAiC,CAAC;AAyBzC,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,YAAY,GAAG,UAAU,CAAC;AAC/I,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,YAAY,GAAG,UAAU,CAAC;AAC5I,KAAK,aAAa,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEvD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,oBAAoB,EAAE,IAAI,EAAE,kBAAkB,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,UAAQ,GAAG,MAAM,CAE5I;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG;IAClE,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B,GAAG,IAAI,CAcP;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAErH;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG;IAAC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAC,GAAG,IAAI,CAWjL;AAED,qBAAa,iBAAkB,SAAQ,QAAQ;IAC7C,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAgC;IAC1E,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyC;IACrE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAgB;IAE/C,YAAY,OAAO,GAAE,eAAoB,EAAE,UAAU,GAAE,SAAS,yBAAyB,EAAO,EAG/F;IAEQ,OAAO,CAAC,OAAO,EAAE,OAAO,yDAShC;IAED,UAAmB,4BAA4B,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAI7E;IAED,UAAmB,yBAAyB,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAI1E;IAED,UAAmB,kCAAkC,CACnD,SAAS,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EACpC,SAAS,EAAE,SAAS,GACnB,SAAS,GAAG,IAAI,CAYlB;IAED,OAAO,CAAC,aAAa;IAyBrB,OAAO,CAAC,YAAY;CAqGrB;AAID,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,gCAAgC,GAAG,SAAS,GAAG,SAAS,CA0KnG"}
1
+ {"version":3,"file":"server-analyzer.d.ts","sourceRoot":"","sources":["../src/server-analyzer.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
- import { Analyzer, anyType, boolType, describeType, nullType, numberType, optionalOf, stringType, unknownType, } from "@velarscript/compiler/extension";
1
+ import { Analyzer, bindingNeverReassigned, boolType, describeType, nullType, numberType, optionalOf, stringType, unknownType, spanIdentity, } from "@velarscript/compiler/extension";
2
+ import { routeShape } from "./route-shape.js";
2
3
  import { isNodeServerStatement } from "./server-ast.js";
3
4
  import { isNodeProviderType, isNodeRouteInputType, isServeRequestType, nodeProviderResult, nodeProviderType, nodeRouteInputType, nodeRouteInputValue, serveAppType, serveRequestType, } from "./server-types.js";
4
5
  const routeHintPrefix = "node.route-param:";
@@ -8,6 +9,7 @@ const sseEventType = { kind: "object", fields: new Map([
8
9
  ["data", stringType], ["event", optionalOf(stringType)], ["id", optionalOf(stringType)], ["retry", optionalOf(numberType)],
9
10
  ]), optionalFields: new Set(["event", "id", "retry"]) };
10
11
  const sseSendType = { kind: "function", parameterNames: ["event"], parameters: [{ kind: "union", members: [stringType, sseEventType] }], requiredParameters: 1, result: { kind: "promise", value: nullType } };
12
+ const serveCombinators = new Set(["prefix", "use", "bodyLimit", "docs", "lifecycle"]);
11
13
  export function routeParameterHint(source, kind, schema, descriptor = false) {
12
14
  return `${routeHintPrefix}${JSON.stringify({ source, kind, schema, descriptor })}`;
13
15
  }
@@ -45,12 +47,47 @@ export function parseRouteResultHint(value) {
45
47
  export class VelarNodeAnalyzer extends Analyzer {
46
48
  contextualRouteParameters = new Map();
47
49
  routeInputs = new Map();
50
+ /** Servers declared by the module under analysis, the only spread targets this analyzer can resolve. */
51
+ moduleServers = new Map();
52
+ /** Module-level `const alias = name` and `let alias = name` bindings, so a spread of an alias resolves to the server it names. */
53
+ moduleServerAliases = new Map();
54
+ /** Local names imported from velar/serve that name a path-preserving combinator, so a spread of a call resolves through it. */
55
+ moduleServeCombinators = new Map();
56
+ /** One answer per `let` alias name to "was this binding ever reassigned?", because the predicate walks the whole program. */
57
+ stableAliases = new Map();
58
+ /** The program under analysis, held for the alias-stability walk. */
59
+ moduleProgram = null;
48
60
  nodeModulePath;
49
61
  constructor(context = {}, extensions = []) {
50
62
  super(context, extensions);
51
63
  this.nodeModulePath = context.path ?? null;
52
64
  }
53
65
  analyze(program) {
66
+ this.moduleServers.clear();
67
+ this.moduleServerAliases.clear();
68
+ this.moduleServeCombinators.clear();
69
+ this.stableAliases.clear();
70
+ this.moduleProgram = program;
71
+ for (const statement of program.body) {
72
+ if (isNodeServerStatement(statement)) {
73
+ if (!this.moduleServers.has(statement.name))
74
+ this.moduleServers.set(statement.name, statement);
75
+ continue;
76
+ }
77
+ if (statement.kind === "ImportDeclaration" && statement.source === "velar/serve") {
78
+ for (const specifier of statement.specifiers) {
79
+ if (specifier.namespace || !serveCombinators.has(specifier.imported))
80
+ continue;
81
+ if (!this.moduleServeCombinators.has(specifier.local)) {
82
+ this.moduleServeCombinators.set(specifier.local, { imported: specifier.imported, span: specifier.span });
83
+ }
84
+ }
85
+ continue;
86
+ }
87
+ const alias = moduleServerAlias(statement);
88
+ if (alias && !this.moduleServerAliases.has(alias.name))
89
+ this.moduleServerAliases.set(alias.name, alias);
90
+ }
54
91
  if (!(this.nodeModulePath ?? "").endsWith(".test.vel")) {
55
92
  for (const statement of program.body) {
56
93
  if ((statement.kind === "ImportDeclaration" || statement.kind === "ReExportDeclaration") && statement.source === "velar/server-test") {
@@ -93,22 +130,221 @@ export class VelarNodeAnalyzer extends Analyzer {
93
130
  if (!this.isPredeclared(statement))
94
131
  this.declareBinding(statement.name, false, serveAppType, statement.span);
95
132
  const routes = new Map();
133
+ let notFound = null;
96
134
  for (const item of statement.items) {
97
135
  if (item.kind === "NodeServerSpread") {
98
136
  this.requireAssignable(this.inferExpression(item.value, serveAppType), serveAppType, item.value.span);
137
+ const composed = this.composedItems(statement, item);
138
+ if (!composed)
139
+ continue;
140
+ if (composed.notFound) {
141
+ if (notFound)
142
+ this.typeError(describeFallbackCollision(notFound, composed.notFound), item.span);
143
+ else
144
+ notFound = composed.notFound;
145
+ }
146
+ for (const entry of composed.routes)
147
+ this.recordRoute(entry, routes, item.span);
99
148
  continue;
100
149
  }
101
- const shape = routeShape(item.path);
102
- const key = `${item.method} ${shape}`;
103
- const previous = routes.get(key);
104
- if (previous) {
105
- this.typeError(`Route '${item.method} ${item.path}' conflicts with '${previous.method} ${previous.path}'; parameter names do not make two route shapes distinct`, item.pathSpan);
150
+ if (item.kind === "NodeNotFoundDeclaration") {
151
+ const written = { fallback: item, spread: null, origin: [] };
152
+ if (notFound)
153
+ this.typeError(describeFallbackCollision(notFound, written), item.span);
154
+ else
155
+ notFound = written;
156
+ this.analyzeNotFound(item);
157
+ continue;
106
158
  }
107
- else
108
- routes.set(key, item);
159
+ this.recordRoute({ route: item, path: item.path, spread: null, origin: [] }, routes, item.pathSpan);
109
160
  this.analyzeRoute(item);
110
161
  }
111
162
  }
163
+ /**
164
+ * Enters one route into this server's shape map and compares it against every route already
165
+ * entered, whether that route was written here or composed in by a spread. Composition is why the
166
+ * entries carry an origin: a conflicting route the author cannot see in his own file has to name
167
+ * the server it came from.
168
+ */
169
+ recordRoute(entry, routes, span) {
170
+ const key = `${entry.route.method} ${routeShape(entry.path)}`;
171
+ const previous = routes.get(key);
172
+ if (previous) {
173
+ this.typeError(describeRouteCollision(previous, entry), span);
174
+ return;
175
+ }
176
+ // Two routes of one method can share a concrete path. Where one declares a literal at every
177
+ // position the other does and more, the router's literal-beats-capture score picks it every
178
+ // time, which is the intended precedence behind '/users/me' beside '/users/{id:string}'.
179
+ // Where neither is more specific the winner is declaration order alone and the loser can
180
+ // never run, so the overlap is an error. A pair whose shared path is unrealizable — a
181
+ // '{n:number}' capture against the literal 'b' — is not an overlap.
182
+ const segments = routeSegments(entry.path);
183
+ for (const other of routes.values()) {
184
+ if (other.route.method !== entry.route.method)
185
+ continue;
186
+ // Two routes one spread composed in were already compared against each other while that
187
+ // server was analyzed; reporting them again here would duplicate its diagnostic.
188
+ if (entry.spread !== null && other.spread === entry.spread)
189
+ continue;
190
+ const declared = routeSegments(other.path);
191
+ if (routeSpecificityDecides(declared, segments))
192
+ continue;
193
+ const shared = routeSharedPath(declared, segments);
194
+ if (shared === null)
195
+ continue;
196
+ this.typeError(`Route ${describeComposedRoute(other)} overlaps ${describeComposedRoute(entry)}; both match '${shared}' and neither is more specific — narrow or remove one`, span);
197
+ }
198
+ routes.set(key, entry);
199
+ }
200
+ /**
201
+ * The routes and fallback a spread composes into the server that writes it, or null when the
202
+ * spread is not statically resolvable. This analyzer sees one module, so a spread contributes
203
+ * only when its value reaches a server declared in this module: a plain identifier, an alias of
204
+ * one, or a velar/serve combinator call around one — `use`, `bodyLimit`, `docs` and `lifecycle`
205
+ * carry paths through unchanged, and `prefix` translates them by its literal path. An imported
206
+ * server, a computed prefix path, or any other expression is let through unchecked, because a
207
+ * false conflict here would block a correct program; D90 R19's runtime referee judges the final
208
+ * table at assembly instead. Composition is followed transitively; the visited set bounds a
209
+ * cycle and starts holding the composing server, so a cycle never folds a server's own routes
210
+ * back into itself and reports each as conflicting with itself.
211
+ */
212
+ composedItems(statement, spread) {
213
+ const target = this.resolveComposedServer(spread.value);
214
+ if (!target)
215
+ return null;
216
+ const routes = [];
217
+ const shapes = new Set();
218
+ const visited = new Set([statement]);
219
+ let notFound = null;
220
+ const collect = (server, prefix, origin) => {
221
+ if (visited.has(server))
222
+ return;
223
+ visited.add(server);
224
+ for (const item of server.items) {
225
+ if (item.kind === "NodeServerSpread") {
226
+ const nested = this.resolveComposedServer(item.value);
227
+ if (nested)
228
+ collect(nested.server, prefix + nested.prefix, [...origin, nested.server.name]);
229
+ continue;
230
+ }
231
+ if (item.kind === "NodeNotFoundDeclaration") {
232
+ // The runtime refuses `prefix` around an app with @notFound outright, so a fallback seen
233
+ // through a prefix never reaches any composed table and composing it here would report
234
+ // a duplicate the program cannot have.
235
+ if (prefix === "")
236
+ notFound ??= { fallback: item, spread, origin };
237
+ continue;
238
+ }
239
+ // A server that conflicts with itself already reported it; one entry per shape is what
240
+ // reaches the composing server, exactly as one entry per shape reaches its own map.
241
+ const path = prefixedRoutePath(prefix, item.path);
242
+ const shape = `${item.method} ${routeShape(path)}`;
243
+ if (shapes.has(shape))
244
+ continue;
245
+ shapes.add(shape);
246
+ routes.push({ route: item, path, spread, origin });
247
+ }
248
+ };
249
+ collect(target.server, target.prefix, [target.server.name]);
250
+ return { routes, notFound };
251
+ }
252
+ /**
253
+ * The server declaration a spread value names, or null when it is anything else. A
254
+ * `const other = base` alias chain of this module's own servers resolves too, because the alias
255
+ * holds exactly that ServeApp — and so does a `let` alias the whole module never reassigns,
256
+ * because an unwritten `let` holds its initializer exactly as a `const` does. A reassigned or
257
+ * ambiguous `let`, a member path, a conditional, an import, or a parameter contributes nothing.
258
+ * A call resolves through the path-preserving velar/serve combinators when the callee still
259
+ * reaches its velar/serve import: `prefix` with a literal path translates what its app argument
260
+ * declares, and `use`/`bodyLimit`/`docs`/`lifecycle` pass it through untouched. A computed
261
+ * prefix path contributes nothing — the assembly-time referee owns it. An alias's initializer
262
+ * re-enters this resolver whole, so `const scoped = prefix("/api", routes)` resolves exactly as
263
+ * the spelled-out spread does; the followed set bounds an alias cycle.
264
+ */
265
+ resolveComposedServer(value, followed = new Set()) {
266
+ if (value.kind === "CallExpression" && !value.optional) {
267
+ if (value.callee.kind !== "IdentifierExpression")
268
+ return null;
269
+ const combinator = this.moduleServeCombinators.get(value.callee.name);
270
+ if (!combinator || !this.resolvesTo(value.callee.name, combinator.span))
271
+ return null;
272
+ const appIndex = combinator.imported === "prefix" ? 1 : 0;
273
+ const app = value.arguments[appIndex];
274
+ if (!app || value.argumentNames?.slice(0, appIndex + 1).some((argumentName) => argumentName !== null))
275
+ return null;
276
+ const inner = this.resolveComposedServer(app, followed);
277
+ if (!inner)
278
+ return null;
279
+ if (combinator.imported !== "prefix")
280
+ return inner;
281
+ const path = value.arguments[0];
282
+ if (!path || path.kind !== "LiteralExpression" || typeof path.value !== "string")
283
+ return null;
284
+ // The same literal shapes the runtime accepts; anything else is the runtime referee's to
285
+ // refuse, and claiming routes for it here would report conflicts against a table that
286
+ // never assembles.
287
+ if (path.value === "/")
288
+ return inner;
289
+ if (!path.value.startsWith("/") || path.value.endsWith("/") || /[{}*?#\\]|\/\//u.test(path.value))
290
+ return null;
291
+ return { server: inner.server, prefix: path.value + inner.prefix };
292
+ }
293
+ if (value.kind !== "IdentifierExpression")
294
+ return null;
295
+ const name = value.name;
296
+ const declaration = this.moduleServers.get(name);
297
+ if (declaration)
298
+ return this.resolvesTo(name, declaration.span) ? { server: declaration, prefix: "" } : null;
299
+ const alias = this.moduleServerAliases.get(name);
300
+ if (!alias || followed.has(name) || !this.resolvesTo(name, alias.span))
301
+ return null;
302
+ if (alias.binding === "let" && !this.aliasBindingIsStable(alias))
303
+ return null;
304
+ followed.add(name);
305
+ return this.resolveComposedServer(alias.initializer, followed);
306
+ }
307
+ /** Whether a `let` alias has never been reassigned, asked once per name and program. */
308
+ aliasBindingIsStable(alias) {
309
+ const cached = this.stableAliases.get(alias.name);
310
+ if (cached !== undefined)
311
+ return cached;
312
+ const stable = this.moduleProgram !== null && bindingNeverReassigned(this.moduleProgram, alias.name, alias.span);
313
+ this.stableAliases.set(alias.name, stable);
314
+ return stable;
315
+ }
316
+ /**
317
+ * Whether a name still reaches the declaration this module recorded for it. An import, a
318
+ * shadowing binding, or a parameter of the same name reaches a different binding.
319
+ */
320
+ resolvesTo(name, span) {
321
+ const binding = this.lookup(name);
322
+ return binding !== null && spanIdentity(binding.span) === spanIdentity(span);
323
+ }
324
+ analyzeNotFound(fallback) {
325
+ this.analyzeFunctionDeclaration(fallback, null, true, false, true, "Not-found fallback");
326
+ if (fallback.parameters.length > 1) {
327
+ this.typeError("@notFound accepts at most one Request parameter", fallback.signatureSpan);
328
+ }
329
+ const parameter = fallback.parameters[0];
330
+ if (parameter) {
331
+ if (parameter.rest)
332
+ this.typeError("@notFound Request cannot be a rest parameter", parameter.span);
333
+ if (parameter.defaultValue)
334
+ this.typeError("@notFound Request is supplied by the server and cannot have a default value", parameter.span);
335
+ if (!parameter.type)
336
+ this.typeError("@notFound Request requires the explicit Request type", parameter.span);
337
+ else {
338
+ const resolved = this.expandAliases(this.resolveValidatedAnnotation(parameter.type));
339
+ if (!isServeRequestType(resolved))
340
+ this.typeError(`@notFound parameter must be Request; received ${describeType(resolved)}`, parameter.span);
341
+ }
342
+ }
343
+ const result = this.expandAliases(this.inferredFunctionResult(fallback));
344
+ if (!isRouteResult(result, (identity) => this.fieldsOf(identity), new Set())) {
345
+ this.typeError(`@notFound must return Data or a response from velar/serve; received ${describeType(result)}`, fallback.returnType?.span ?? fallback.span);
346
+ }
347
+ }
112
348
  analyzeRoute(route) {
113
349
  const pathNames = validateRoutePath(route.path, route.pathSpan, (message) => this.typeError(message, route.pathSpan));
114
350
  this.analyzeFunctionDeclaration(route, null, true, false, true, "Route");
@@ -216,7 +452,7 @@ export function inferNodeIntrinsic(context) {
216
452
  switch (intrinsic.name) {
217
453
  case "serve.response.json": {
218
454
  arity(1, 3);
219
- const value = inferAt(0, anyType);
455
+ const value = inferAt(0, unknownType);
220
456
  if (argumentAt(1))
221
457
  inferAt(1, numberType);
222
458
  if (argumentAt(2))
@@ -225,7 +461,7 @@ export function inferNodeIntrinsic(context) {
225
461
  }
226
462
  case "serve.response.created": {
227
463
  arity(1, 2);
228
- const value = inferAt(0, anyType);
464
+ const value = inferAt(0, unknownType);
229
465
  if (argumentAt(1))
230
466
  inferAt(1, optionalOf(responseHeadersType));
231
467
  return nodeResponseValue("json", value, 201, "application/json");
@@ -619,8 +855,137 @@ function openApiObjectSchema(fields, fieldsOf, seen, enumValuesOf) {
619
855
  additionalProperties: false,
620
856
  };
621
857
  }
622
- function routeShape(path) {
623
- return path.split("/").map((part) => part.startsWith("{") && part.endsWith("}") ? "{}" : part).join("/");
858
+ /**
859
+ * A module-level `const name = expression` or `let name = expression` binding, the indirect
860
+ * spellings of a spread target that can still be exactly the value the initializer resolves to: a
861
+ * bare name, or a velar/serve combinator call the resolver sees through. A `let` alias resolves
862
+ * only after the stability predicate confirms the module never reassigns it — that check belongs
863
+ * to the resolver, which is the point that knows the whole program. A pattern binding is excluded
864
+ * because it never holds the whole value.
865
+ */
866
+ function moduleServerAlias(statement) {
867
+ if (statement.kind !== "VariableDeclaration")
868
+ return null;
869
+ const declaration = statement;
870
+ if (declaration.pattern.kind !== "NameBindingPattern")
871
+ return null;
872
+ if (declaration.initializer.kind !== "IdentifierExpression" && declaration.initializer.kind !== "CallExpression")
873
+ return null;
874
+ return { name: declaration.pattern.name, initializer: declaration.initializer, binding: declaration.binding, span: declaration.pattern.span };
875
+ }
876
+ function describeComposedOrigin(entry) {
877
+ return entry.origin.map((name) => `'${name}'`).join(" → ");
878
+ }
879
+ function describeComposedRoute(entry) {
880
+ // The effective path: a route seen through prefix(...) collides at its translated address, and
881
+ // that address is the one the author has to narrow.
882
+ const route = `'${entry.route.method} ${entry.path}'`;
883
+ return entry.origin.length === 0 ? route : `${route} (composed in from ${describeComposedOrigin(entry)})`;
884
+ }
885
+ /**
886
+ * Why two routes claim one method and shape. Three causes reach this point and each names a
887
+ * different repair, so the message has to tell them apart: one server composed in along two paths
888
+ * declares its route once and cannot be narrowed at all, two routes spelling the same path have no
889
+ * parameter names to blame, and only the third is the shape collision parameter names hide.
890
+ */
891
+ function describeRouteCollision(previous, entry) {
892
+ // One declaration reaching this server twice is only reachable through spreads: a server's own
893
+ // items are never collected back into it, so both sides carry an origin.
894
+ const declaring = entry.origin[entry.origin.length - 1];
895
+ if (previous.route === entry.route && declaring !== undefined) {
896
+ const paths = describeComposedOrigin(previous) === describeComposedOrigin(entry)
897
+ ? `both times from ${describeComposedOrigin(entry)}`
898
+ : `from ${describeComposedOrigin(previous)} and from ${describeComposedOrigin(entry)}`;
899
+ return `Route '${entry.route.method} ${entry.path}' is composed in twice, ${paths}; '${declaring}' declares it once — remove one spread`;
900
+ }
901
+ if (previous.path === entry.path) {
902
+ return `Route ${describeComposedRoute(entry)} duplicates ${describeComposedRoute(previous)}; one method and path answer from a single route`;
903
+ }
904
+ return `Route ${describeComposedRoute(entry)} conflicts with ${describeComposedRoute(previous)}; parameter names do not make two route shapes distinct`;
905
+ }
906
+ /**
907
+ * Both sides of a duplicate @notFound. A fallback a spread composes in is invisible in the author's
908
+ * own file, so naming one contributor and not the other leaves him looking for a declaration that
909
+ * is not there; two spreads each composing one name neither by default.
910
+ */
911
+ function describeFallbackCollision(previous, entry) {
912
+ const rule = "A server can declare only one @notFound fallback";
913
+ if (previous.origin.length === 0 && entry.origin.length === 0)
914
+ return rule;
915
+ const source = (fallback, second) => {
916
+ const article = second ? "another" : "one";
917
+ return fallback.origin.length === 0
918
+ ? `this server declares ${article}`
919
+ : `${describeComposedOrigin(fallback)} composes ${article} in`;
920
+ };
921
+ return `${rule}; ${source(previous, false)} and ${source(entry, true)}`;
922
+ }
923
+ /** A composed route's address as the runtime's `prefix` will spell it: the literal prefix, then the path. */
924
+ function prefixedRoutePath(prefix, path) {
925
+ if (prefix === "")
926
+ return path;
927
+ return prefix + (path === "/" ? "" : path);
928
+ }
929
+ const routeDecimalPattern = /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?$/u;
930
+ function routeSegments(path) {
931
+ return path.split("/").map((segment) => {
932
+ const match = /^\{([A-Za-z_][A-Za-z0-9_]*):([A-Za-z_][A-Za-z0-9_]*)\}$/u.exec(segment);
933
+ return match ? { capture: match[1], captureType: match[2] } : { literal: segment };
934
+ });
935
+ }
936
+ function routeCaptureAdmits(captureType, literal) {
937
+ if (captureType === "number")
938
+ return routeDecimalPattern.test(literal) && Number.isFinite(Number(literal));
939
+ if (captureType === "bool")
940
+ return literal === "true" || literal === "false";
941
+ return true;
942
+ }
943
+ function routeSharedSegment(left, right) {
944
+ if ("literal" in left) {
945
+ if ("literal" in right)
946
+ return left.literal === right.literal ? left.literal : null;
947
+ return routeCaptureAdmits(right.captureType, left.literal) ? left.literal : null;
948
+ }
949
+ if ("literal" in right)
950
+ return routeCaptureAdmits(left.captureType, right.literal) ? right.literal : null;
951
+ const types = new Set([left.captureType, right.captureType]);
952
+ if (types.has("number") && types.has("bool"))
953
+ return null;
954
+ if (types.has("number"))
955
+ return "1";
956
+ if (types.has("bool"))
957
+ return "true";
958
+ return left.capture;
959
+ }
960
+ function routeSharedPath(left, right) {
961
+ if (left.length !== right.length)
962
+ return null;
963
+ const shared = [];
964
+ for (let index = 0; index < left.length; index += 1) {
965
+ const segment = routeSharedSegment(left[index], right[index]);
966
+ if (segment === null)
967
+ return null;
968
+ shared.push(segment);
969
+ }
970
+ return shared.join("/");
971
+ }
972
+ function routeLiteralPositions(segments) {
973
+ const positions = new Set();
974
+ for (let index = 0; index < segments.length; index += 1)
975
+ if ("literal" in segments[index])
976
+ positions.add(index);
977
+ return positions;
978
+ }
979
+ function routeSpecificityDecides(left, right) {
980
+ const leftLiterals = routeLiteralPositions(left);
981
+ const rightLiterals = routeLiteralPositions(right);
982
+ if (leftLiterals.size === rightLiterals.size)
983
+ return false;
984
+ const [subset, superset] = leftLiterals.size < rightLiterals.size ? [leftLiterals, rightLiterals] : [rightLiterals, leftLiterals];
985
+ for (const position of subset)
986
+ if (!superset.has(position))
987
+ return false;
988
+ return true;
624
989
  }
625
990
  function validateRoutePath(path, _sourceSpan, report) {
626
991
  const names = new Set();