@velarscript/node 0.11.0 → 0.12.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 (62) hide show
  1. package/README.md +102 -14
  2. package/dist/compiler.d.ts +3 -0
  3. package/dist/compiler.d.ts.map +1 -1
  4. package/dist/compiler.js +299 -76
  5. package/dist/compiler.js.map +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +3 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/node-host-runtime.d.ts.map +1 -1
  11. package/dist/node-host-runtime.js +30 -14
  12. package/dist/node-host-runtime.js.map +1 -1
  13. package/dist/node-host-worker-runtime.d.ts.map +1 -1
  14. package/dist/node-host-worker-runtime.js +495 -93
  15. package/dist/node-host-worker-runtime.js.map +1 -1
  16. package/dist/project-config.d.ts +17 -0
  17. package/dist/project-config.d.ts.map +1 -0
  18. package/dist/project-config.js +66 -0
  19. package/dist/project-config.js.map +1 -0
  20. package/dist/serve-runtime.d.ts.map +1 -1
  21. package/dist/serve-runtime.js +2885 -157
  22. package/dist/serve-runtime.js.map +1 -1
  23. package/dist/server-analyzer.d.ts +34 -0
  24. package/dist/server-analyzer.d.ts.map +1 -0
  25. package/dist/server-analyzer.js +659 -0
  26. package/dist/server-analyzer.js.map +1 -0
  27. package/dist/server-ast.d.ts +42 -0
  28. package/dist/server-ast.d.ts.map +1 -0
  29. package/dist/server-ast.js +16 -0
  30. package/dist/server-ast.js.map +1 -0
  31. package/dist/server-emitter.d.ts +16 -0
  32. package/dist/server-emitter.d.ts.map +1 -0
  33. package/dist/server-emitter.js +166 -0
  34. package/dist/server-emitter.js.map +1 -0
  35. package/dist/server-inspection.d.ts +3 -0
  36. package/dist/server-inspection.d.ts.map +1 -0
  37. package/dist/server-inspection.js +12 -0
  38. package/dist/server-inspection.js.map +1 -0
  39. package/dist/server-lexer.d.ts +13 -0
  40. package/dist/server-lexer.d.ts.map +1 -0
  41. package/dist/server-lexer.js +65 -0
  42. package/dist/server-lexer.js.map +1 -0
  43. package/dist/server-parser.d.ts +16 -0
  44. package/dist/server-parser.d.ts.map +1 -0
  45. package/dist/server-parser.js +207 -0
  46. package/dist/server-parser.js.map +1 -0
  47. package/dist/server-semantic.d.ts +3 -0
  48. package/dist/server-semantic.d.ts.map +1 -0
  49. package/dist/server-semantic.js +31 -0
  50. package/dist/server-semantic.js.map +1 -0
  51. package/dist/server-types.d.ts +25 -0
  52. package/dist/server-types.d.ts.map +1 -0
  53. package/dist/server-types.js +60 -0
  54. package/dist/server-types.js.map +1 -0
  55. package/dist/websocket-runtime.d.ts.map +1 -1
  56. package/dist/websocket-runtime.js +169 -15
  57. package/dist/websocket-runtime.js.map +1 -1
  58. package/package.json +10 -2
  59. package/dist/sqlite-runtime.d.ts +0 -3
  60. package/dist/sqlite-runtime.d.ts.map +0 -1
  61. package/dist/sqlite-runtime.js +0 -92
  62. package/dist/sqlite-runtime.js.map +0 -1
@@ -0,0 +1,659 @@
1
+ import { Analyzer, anyType, boolType, describeType, nullType, numberType, optionalOf, stringType, unknownType, } from "@velarscript/compiler/extension";
2
+ import { isNodeServerStatement } from "./server-ast.js";
3
+ import { isNodeProviderType, isNodeRouteInputType, isServeRequestType, nodeProviderResult, nodeProviderType, nodeRouteInputType, nodeRouteInputValue, serveAppType, serveRequestType, } from "./server-types.js";
4
+ const routeHintPrefix = "node.route-param:";
5
+ const routeResultHintPrefix = "node.route-result:";
6
+ const responseHeadersType = { kind: "map", key: stringType, value: stringType };
7
+ const sseEventType = { kind: "object", fields: new Map([
8
+ ["data", stringType], ["event", optionalOf(stringType)], ["id", optionalOf(stringType)], ["retry", optionalOf(numberType)],
9
+ ]), optionalFields: new Set(["event", "id", "retry"]) };
10
+ const sseSendType = { kind: "function", parameterNames: ["event"], parameters: [{ kind: "union", members: [stringType, sseEventType] }], requiredParameters: 1, result: { kind: "promise", value: nullType } };
11
+ export function routeParameterHint(source, kind, schema, descriptor = false) {
12
+ return `${routeHintPrefix}${JSON.stringify({ source, kind, schema, descriptor })}`;
13
+ }
14
+ export function parseRouteParameterHint(value) {
15
+ if (!value?.startsWith(routeHintPrefix))
16
+ return null;
17
+ try {
18
+ const parsed = JSON.parse(value.slice(routeHintPrefix.length));
19
+ if (!parsed.source || !parsed.kind || !parsed.schema || typeof parsed.schema !== "object")
20
+ return null;
21
+ return { source: parsed.source, kind: parsed.kind, schema: parsed.schema, descriptor: parsed.descriptor === true };
22
+ }
23
+ catch {
24
+ return null;
25
+ }
26
+ }
27
+ export function routeResultHint(schema, contentTypes, status) {
28
+ return `${routeResultHintPrefix}${JSON.stringify({ schema, contentTypes, status })}`;
29
+ }
30
+ export function parseRouteResultHint(value) {
31
+ if (!value?.startsWith(routeResultHintPrefix))
32
+ return null;
33
+ try {
34
+ const parsed = JSON.parse(value.slice(routeResultHintPrefix.length));
35
+ if (!parsed || typeof parsed !== "object" || !parsed.schema || typeof parsed.schema !== "object" || Array.isArray(parsed.schema)
36
+ || !Array.isArray(parsed.contentTypes) || !parsed.contentTypes.every((item) => typeof item === "string")
37
+ || parsed.status !== null && (typeof parsed.status !== "number" || !Number.isSafeInteger(parsed.status) || parsed.status < 200 || parsed.status > 599))
38
+ return null;
39
+ return { schema: parsed.schema, contentTypes: parsed.contentTypes, status: parsed.status };
40
+ }
41
+ catch {
42
+ return null;
43
+ }
44
+ }
45
+ export class VelarNodeAnalyzer extends Analyzer {
46
+ contextualRouteParameters = new Map();
47
+ routeInputs = new Map();
48
+ nodeModulePath;
49
+ constructor(context = {}, extensions = []) {
50
+ super(context, extensions);
51
+ this.nodeModulePath = context.path ?? null;
52
+ }
53
+ analyze(program) {
54
+ if (!(this.nodeModulePath ?? "").endsWith(".test.vel")) {
55
+ for (const statement of program.body) {
56
+ if ((statement.kind === "ImportDeclaration" || statement.kind === "ReExportDeclaration") && statement.source === "velar/server-test") {
57
+ this.typeError("'velar/server-test' is an in-process test capability; import it only from a '*.test.vel' module", statement.sourceSpan);
58
+ }
59
+ }
60
+ }
61
+ return super.analyze(program);
62
+ }
63
+ predeclareExtensionStatement(statement) {
64
+ if (!isNodeServerStatement(statement))
65
+ return false;
66
+ this.declareBinding(statement.name, false, serveAppType, statement.span);
67
+ return true;
68
+ }
69
+ analyzeExtensionStatement(statement) {
70
+ if (!isNodeServerStatement(statement))
71
+ return false;
72
+ this.analyzeServer(statement);
73
+ return true;
74
+ }
75
+ contextualFunctionParameterDefault(statement, parameter) {
76
+ if (statement.kind !== "NodeRouteDeclaration" || !parameter.defaultValue)
77
+ return null;
78
+ const inferred = this.expandAliases(this.inferParameterDefault(parameter.defaultValue));
79
+ const key = `${parameter.span.start}:${parameter.span.end}`;
80
+ if (isNodeRouteInputType(inferred)) {
81
+ this.routeInputs.set(key, inferred);
82
+ const value = this.expandAliases(nodeRouteInputValue(inferred));
83
+ this.contextualRouteParameters.set(key, value);
84
+ return value;
85
+ }
86
+ this.contextualRouteParameters.set(key, inferred);
87
+ return inferred;
88
+ }
89
+ analyzeServer(statement) {
90
+ if (!this.isTopLevelScope()) {
91
+ this.typeError("A server is a module declaration; move it to the top level", statement.span);
92
+ }
93
+ if (!this.isPredeclared(statement))
94
+ this.declareBinding(statement.name, false, serveAppType, statement.span);
95
+ const routes = new Map();
96
+ for (const item of statement.items) {
97
+ if (item.kind === "NodeServerSpread") {
98
+ this.requireAssignable(this.inferExpression(item.value, serveAppType), serveAppType, item.value.span);
99
+ continue;
100
+ }
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);
106
+ }
107
+ else
108
+ routes.set(key, item);
109
+ this.analyzeRoute(item);
110
+ }
111
+ }
112
+ analyzeRoute(route) {
113
+ const pathNames = validateRoutePath(route.path, route.pathSpan, (message) => this.typeError(message, route.pathSpan));
114
+ this.analyzeFunctionDeclaration(route, null, true, false, true, "Route");
115
+ let bodies = 0;
116
+ let forms = 0;
117
+ let requests = 0;
118
+ const declared = new Set();
119
+ for (const parameter of route.parameters) {
120
+ if (declared.has(parameter.name)) {
121
+ this.typeError(`Route parameter '${parameter.name}' is declared more than once`, parameter.span);
122
+ }
123
+ declared.add(parameter.name);
124
+ const key = `${parameter.span.start}:${parameter.span.end}`;
125
+ const routeInput = this.routeInputs.get(key);
126
+ if (!parameter.type && !this.contextualRouteParameters.has(key)) {
127
+ this.typeError(`Route parameter '${parameter.name}' requires an explicit type`, parameter.span);
128
+ continue;
129
+ }
130
+ const resolved = parameter.type
131
+ ? this.expandAliases(this.resolveValidatedAnnotation(parameter.type))
132
+ : this.contextualRouteParameters.get(key) ?? unknownType;
133
+ const scalar = scalarKind(resolved);
134
+ let source;
135
+ let kind;
136
+ if (pathNames.has(parameter.name)) {
137
+ source = "path";
138
+ pathNames.delete(parameter.name);
139
+ if (!scalar || scalar === "list") {
140
+ this.typeError(`Path parameter '${parameter.name}' must be string, number, bool, or an enum; received ${describeType(resolved)}`, parameter.span);
141
+ continue;
142
+ }
143
+ kind = scalar;
144
+ if (parameter.defaultValue)
145
+ this.typeError(`Path parameter '${parameter.name}' cannot have a default value`, parameter.span);
146
+ }
147
+ else if (routeInput) {
148
+ source = routeInput.role;
149
+ if (source === "form" || source === "upload") {
150
+ forms += 1;
151
+ if (route.method !== "POST" && route.method !== "PUT" && route.method !== "PATCH") {
152
+ this.typeError(`${route.method} routes cannot declare form or upload inputs`, parameter.span);
153
+ }
154
+ }
155
+ if (source === "request") {
156
+ kind = "request";
157
+ requests += 1;
158
+ if (requests > 1)
159
+ this.typeError("A route can declare only one Request parameter", parameter.span);
160
+ }
161
+ else if (source === "upload")
162
+ kind = "upload";
163
+ else if (source === "dependency")
164
+ kind = "dependency";
165
+ else if (source === "security")
166
+ kind = "security";
167
+ else
168
+ kind = scalar ?? "data";
169
+ }
170
+ else if (isServeRequestType(resolved)) {
171
+ source = "request";
172
+ kind = "request";
173
+ requests += 1;
174
+ if (requests > 1)
175
+ this.typeError("A route can declare only one Request parameter", parameter.span);
176
+ if (parameter.defaultValue)
177
+ this.typeError("A Request parameter is supplied by the server and cannot have a default value", parameter.span);
178
+ }
179
+ else if (scalar) {
180
+ source = "query";
181
+ kind = scalar;
182
+ }
183
+ else {
184
+ source = "body";
185
+ kind = "data";
186
+ bodies += 1;
187
+ if (route.method !== "POST" && route.method !== "PUT" && route.method !== "PATCH") {
188
+ this.typeError(`${route.method} routes do not infer a JSON body; use scalar query parameters or an explicit Request`, parameter.span);
189
+ }
190
+ if (bodies > 1)
191
+ this.typeError("A route can declare only one structured JSON body parameter", parameter.span);
192
+ if (parameter.defaultValue)
193
+ this.typeError("A structured JSON body parameter cannot have a default value", parameter.span);
194
+ if (!isNamedDataRecord(resolved, (identity) => this.fieldsOf(identity), new Set())) {
195
+ this.typeError(`A route body must be a concrete Data record type; received ${describeType(resolved)}`, parameter.span);
196
+ }
197
+ }
198
+ this.extensionCalls.set(key, routeParameterHint(source, kind, openApiSchema(resolved, (identity) => this.fieldsOf(identity), new Set(), (identity) => this.enumValuesOf(identity)), Boolean(routeInput)));
199
+ }
200
+ if (forms > 0 && bodies > 0) {
201
+ this.typeError("A route cannot combine an inferred JSON body with form or upload inputs", route.span);
202
+ }
203
+ for (const missing of pathNames) {
204
+ this.typeError(`Route path capture '${missing}' could not declare its route input; rewrite the capture as '{${missing}:string}'`, route.pathSpan);
205
+ }
206
+ const result = this.expandAliases(this.inferredFunctionResult(route));
207
+ this.extensionCalls.set(`${route.signatureSpan.start}:${route.signatureSpan.end}`, routeResultHint(openApiResponseSchema(result, (identity) => this.fieldsOf(identity), new Set(), (identity) => this.enumValuesOf(identity)), openApiResponseContentTypes(result), openApiResponseStatus(result)));
208
+ if (!isRouteResult(result, (identity) => this.fieldsOf(identity), new Set())) {
209
+ this.typeError(`Route '${route.name}' must return Data or a response from velar/serve; received ${describeType(result)}`, route.returnType?.span ?? route.span);
210
+ }
211
+ }
212
+ }
213
+ const uploadType = { kind: "named", name: "Upload", identity: "velar/serve#type:Upload" };
214
+ export function inferNodeIntrinsic(context) {
215
+ const { intrinsic, argumentAt, callSpan, arity, inferAt, callbackAt, runtimeTypeAt, expandAliases } = context;
216
+ switch (intrinsic.name) {
217
+ case "serve.response.json": {
218
+ arity(1, 3);
219
+ const value = inferAt(0, anyType);
220
+ if (argumentAt(1))
221
+ inferAt(1, numberType);
222
+ if (argumentAt(2))
223
+ inferAt(2, optionalOf(responseHeadersType));
224
+ return nodeResponseValue("json", value, literalStatus(argumentAt(1), 200), "application/json");
225
+ }
226
+ case "serve.response.created": {
227
+ arity(1, 2);
228
+ const value = inferAt(0, anyType);
229
+ if (argumentAt(1))
230
+ inferAt(1, optionalOf(responseHeadersType));
231
+ return nodeResponseValue("json", value, 201, "application/json");
232
+ }
233
+ case "serve.response.noContent": {
234
+ arity(0, 2);
235
+ if (argumentAt(0))
236
+ inferAt(0, nullType);
237
+ if (argumentAt(1))
238
+ inferAt(1, optionalOf(responseHeadersType));
239
+ return nodeResponseValue("text", stringType, 204, "text/plain");
240
+ }
241
+ case "serve.response.redirect": {
242
+ arity(1, 3);
243
+ inferAt(0, stringType);
244
+ if (argumentAt(1))
245
+ inferAt(1, numberType);
246
+ if (argumentAt(2))
247
+ inferAt(2, optionalOf(responseHeadersType));
248
+ return nodeResponseValue("text", stringType, literalStatus(argumentAt(1), 302), "text/plain");
249
+ }
250
+ case "serve.response.text": {
251
+ arity(1, 4);
252
+ inferAt(0, stringType);
253
+ if (argumentAt(1))
254
+ inferAt(1, numberType);
255
+ if (argumentAt(2))
256
+ inferAt(2, stringType);
257
+ if (argumentAt(3))
258
+ inferAt(3, optionalOf(responseHeadersType));
259
+ return nodeResponseValue("text", stringType, literalStatus(argumentAt(1), 200), "text/plain");
260
+ }
261
+ case "serve.response.sse": {
262
+ arity(1, 2);
263
+ const producer = callbackAt(0, [sseSendType], { kind: "promise", value: nullType });
264
+ if (argumentAt(1))
265
+ inferAt(1, optionalOf(responseHeadersType));
266
+ return nodeResponseValue("stream", producer, 200, "text/event-stream");
267
+ }
268
+ case "serve.response.background": {
269
+ arity(2, 2);
270
+ const response = expandAliases(inferAt(0));
271
+ requireResponseValue(context, response, argumentAt(0)?.span ?? callSpan);
272
+ callbackAt(1, [], unknownType);
273
+ return response;
274
+ }
275
+ case "serve.response.setCookie": {
276
+ arity(3, 8);
277
+ const response = expandAliases(inferAt(0));
278
+ requireResponseValue(context, response, argumentAt(0)?.span ?? callSpan);
279
+ inferAt(1, stringType);
280
+ inferAt(2, stringType);
281
+ if (argumentAt(3))
282
+ inferAt(3, stringType);
283
+ if (argumentAt(4))
284
+ inferAt(4, boolType);
285
+ if (argumentAt(5))
286
+ inferAt(5, boolType);
287
+ if (argumentAt(6))
288
+ inferAt(6, stringType);
289
+ if (argumentAt(7))
290
+ inferAt(7, optionalOf(numberType));
291
+ return response;
292
+ }
293
+ case "serve.response.clearCookie": {
294
+ arity(2, 3);
295
+ const response = expandAliases(inferAt(0));
296
+ requireResponseValue(context, response, argumentAt(0)?.span ?? callSpan);
297
+ inferAt(1, stringType);
298
+ if (argumentAt(2))
299
+ inferAt(2, stringType);
300
+ return response;
301
+ }
302
+ case "serve.input.query":
303
+ case "serve.input.header":
304
+ case "serve.input.cookie": {
305
+ arity(0, 2);
306
+ inferAt(0, stringType);
307
+ const fallback = argumentAt(1);
308
+ let result = stringType;
309
+ if (fallback) {
310
+ const inferred = expandAliases(inferAt(1, { kind: "union", members: [stringType, { kind: "null" }] }));
311
+ if (inferred.kind === "null" || inferred.kind === "optional")
312
+ result = optionalOf(stringType);
313
+ }
314
+ const source = intrinsic.name.slice("serve.input.".length);
315
+ return nodeRouteInputType(source, result);
316
+ }
317
+ case "serve.input.form": {
318
+ arity(1, 1);
319
+ return nodeRouteInputType("form", runtimeTypeAt(0));
320
+ }
321
+ case "serve.input.upload": {
322
+ arity(0, 2);
323
+ inferAt(0, stringType);
324
+ if (argumentAt(1))
325
+ inferAt(1, { kind: "number" });
326
+ return nodeRouteInputType("upload", uploadType);
327
+ }
328
+ case "serve.input.request": {
329
+ arity(0, 0);
330
+ return nodeRouteInputType("request", serveRequestType);
331
+ }
332
+ case "serve.input.dependency": {
333
+ arity(1, 1);
334
+ const provider = expandAliases(inferAt(0));
335
+ if (!isNodeProviderType(provider)) {
336
+ context.typeError(`input.dependency requires a Provider, received ${describeType(provider)}`, argumentAt(0)?.span ?? callSpan);
337
+ return nodeRouteInputType("dependency", unknownType);
338
+ }
339
+ return nodeRouteInputType("dependency", nodeProviderResult(provider));
340
+ }
341
+ case "serve.security.apiKey": {
342
+ arity(1, 2);
343
+ inferAt(0, stringType);
344
+ inferAt(1, stringType);
345
+ return nodeRouteInputType("security", stringType, { scheme: "apiKey" });
346
+ }
347
+ case "serve.security.basic": {
348
+ arity(0, 1);
349
+ inferAt(0, stringType);
350
+ return nodeRouteInputType("security", {
351
+ kind: "object",
352
+ fields: new Map([["username", stringType], ["password", stringType]]),
353
+ }, { scheme: "http", protocol: "basic" });
354
+ }
355
+ case "serve.security.bearer": {
356
+ arity(0, 1);
357
+ inferAt(0, stringType);
358
+ return nodeRouteInputType("security", stringType, { scheme: "http", protocol: "bearer" });
359
+ }
360
+ case "serve.security.oauth2": {
361
+ arity(1, 3);
362
+ inferAt(0, stringType);
363
+ inferAt(1, stringType);
364
+ inferAt(2, { kind: "list", element: stringType });
365
+ return nodeRouteInputType("security", stringType, { scheme: "oauth2" });
366
+ }
367
+ case "serve.security.openId": {
368
+ arity(1, 1);
369
+ inferAt(0, stringType);
370
+ return nodeRouteInputType("security", stringType, { scheme: "openIdConnect" });
371
+ }
372
+ case "serve.provide": {
373
+ arity(2, 5);
374
+ const inputs = expandAliases(inferAt(0));
375
+ const resolved = new Map();
376
+ if (inputs.kind !== "object") {
377
+ context.typeError(`provide inputs must be an object of input descriptors, received ${describeType(inputs)}`, argumentAt(0)?.span ?? callSpan);
378
+ }
379
+ else {
380
+ for (const [name, value] of inputs.fields) {
381
+ const descriptor = expandAliases(value);
382
+ if (!isNodeRouteInputType(descriptor)) {
383
+ context.typeError(`Provider input '${name}' must be created by input or security, received ${describeType(descriptor)}`, argumentAt(0)?.span ?? callSpan);
384
+ resolved.set(name, unknownType);
385
+ }
386
+ else
387
+ resolved.set(name, nodeRouteInputValue(descriptor));
388
+ }
389
+ }
390
+ const values = { kind: "object", fields: resolved };
391
+ const resolver = callbackAt(1, [values], unknownType);
392
+ const rawResult = resolver.kind === "function" || resolver.kind === "action" || resolver.kind === "intrinsic"
393
+ ? resolver.result
394
+ : unknownType;
395
+ const result = expandAliases(rawResult).kind === "promise"
396
+ ? expandAliases(rawResult).value
397
+ : rawResult;
398
+ if (argumentAt(2))
399
+ inferAt(2, stringType);
400
+ if (argumentAt(3))
401
+ callbackAt(3, [result], unknownType);
402
+ if (argumentAt(4))
403
+ inferAt(4, boolType);
404
+ return nodeProviderType(values, result);
405
+ }
406
+ default:
407
+ return undefined;
408
+ }
409
+ }
410
+ function literalStatus(expression, fallback) {
411
+ if (expression === null)
412
+ return fallback;
413
+ return expression.kind === "LiteralExpression" && typeof expression.value === "number"
414
+ && Number.isSafeInteger(expression.value) && expression.value >= 200 && expression.value <= 599
415
+ ? expression.value
416
+ : null;
417
+ }
418
+ function nodeResponseValue(body, value, status, contentType) {
419
+ const fields = new Map([["status", numberType], [body, value], ["headers", responseHeadersType]]);
420
+ if (body === "text")
421
+ fields.set("contentType", stringType);
422
+ const response = {
423
+ kind: "object",
424
+ fields,
425
+ optionalFields: new Set(body === "text" ? ["contentType", "headers"] : ["headers"]),
426
+ nodeResponse: { status, contentType },
427
+ };
428
+ return response;
429
+ }
430
+ function scalarKind(type) {
431
+ const value = type.kind === "optional" ? type.inner : type;
432
+ if (value.kind === "string")
433
+ return "string";
434
+ if (value.kind === "number")
435
+ return "number";
436
+ if (value.kind === "bool")
437
+ return "bool";
438
+ if (value.kind === "enum" || value.kind === "enumMember")
439
+ return "enum";
440
+ if (value.kind === "list") {
441
+ const element = scalarKind(value.element);
442
+ if (element === "string" || element === "number" || element === "bool" || element === "enum")
443
+ return "list";
444
+ }
445
+ return null;
446
+ }
447
+ function isNamedDataRecord(type, fieldsOf, seen) {
448
+ if (type.kind !== "named")
449
+ return false;
450
+ const identity = type.identity ?? type.name;
451
+ const fields = fieldsOf(identity);
452
+ if (!fields)
453
+ return false;
454
+ if (seen.has(identity))
455
+ return true;
456
+ const next = new Set([...seen, identity]);
457
+ return [...fields.values()].every((field) => isData(field, fieldsOf, next));
458
+ }
459
+ function isData(type, fieldsOf, seen) {
460
+ if (type.kind === "null" || type.kind === "string" || type.kind === "number" || type.kind === "bool"
461
+ || type.kind === "enum" || type.kind === "enumMember")
462
+ return true;
463
+ if (type.kind === "optional")
464
+ return isData(type.inner, fieldsOf, seen);
465
+ if (type.kind === "list" || type.kind === "record")
466
+ return isData(type.kind === "list" ? type.element : type.value, fieldsOf, seen);
467
+ if (type.kind === "union")
468
+ return type.members.every((member) => isData(member, fieldsOf, seen));
469
+ if (type.kind === "object")
470
+ return [...type.fields.values()].every((field) => isData(field, fieldsOf, seen));
471
+ if (type.kind === "named") {
472
+ if (type.name === "Duration")
473
+ return true;
474
+ const identity = type.identity ?? type.name;
475
+ if (seen.has(identity))
476
+ return true;
477
+ const fields = fieldsOf(identity);
478
+ if (!fields)
479
+ return false;
480
+ const next = new Set([...seen, identity]);
481
+ return [...fields.values()].every((field) => isData(field, fieldsOf, next));
482
+ }
483
+ return false;
484
+ }
485
+ function isResponseShape(type) {
486
+ if (type.kind !== "object")
487
+ return false;
488
+ return type.fields.has("status")
489
+ && (type.fields.has("json") || type.fields.has("text") || type.fields.has("stream"));
490
+ }
491
+ function requireResponseValue(context, type, span) {
492
+ const response = type.kind === "union" ? type.members.every(isResponseShape) : isResponseShape(type);
493
+ if (!response)
494
+ context.typeError(`Expected a ServeResponse, received ${describeType(type)}`, span);
495
+ }
496
+ function isRouteResult(type, fieldsOf, seen) {
497
+ if (type.kind === "union")
498
+ return type.members.every((member) => isRouteResult(member, fieldsOf, seen));
499
+ return isResponseShape(type) || isData(type, fieldsOf, seen);
500
+ }
501
+ function openApiResponseSchema(type, fieldsOf, seen, enumValuesOf) {
502
+ if (type.kind === "union") {
503
+ const schemas = type.members.map((member) => openApiResponseSchema(member, fieldsOf, seen, enumValuesOf));
504
+ return schemas.length === 1 ? schemas[0] : { anyOf: schemas };
505
+ }
506
+ if (type.kind === "object" && type.fields.has("status")) {
507
+ const json = type.fields.get("json");
508
+ if (json)
509
+ return openApiSchema(json, fieldsOf, seen, enumValuesOf);
510
+ if (type.fields.has("text") || type.fields.has("stream"))
511
+ return { type: "string" };
512
+ }
513
+ return openApiSchema(type, fieldsOf, seen, enumValuesOf);
514
+ }
515
+ function openApiResponseContentTypes(type) {
516
+ const output = new Set();
517
+ const visit = (value) => {
518
+ if (value.kind === "union") {
519
+ value.members.forEach(visit);
520
+ return;
521
+ }
522
+ const metadata = value.nodeResponse;
523
+ if (metadata) {
524
+ output.add(metadata.contentType);
525
+ return;
526
+ }
527
+ if (value.kind === "object" && value.fields.has("status")) {
528
+ if (value.fields.has("json"))
529
+ output.add("application/json");
530
+ else if (value.fields.has("text") || value.fields.has("stream"))
531
+ output.add("text/plain");
532
+ else
533
+ output.add("application/octet-stream");
534
+ return;
535
+ }
536
+ output.add("application/json");
537
+ };
538
+ visit(type);
539
+ return [...output];
540
+ }
541
+ function openApiResponseStatus(type) {
542
+ const statuses = new Set();
543
+ let unknown = false;
544
+ const visit = (value) => {
545
+ if (value.kind === "union") {
546
+ value.members.forEach(visit);
547
+ return;
548
+ }
549
+ const metadata = value.nodeResponse;
550
+ if (metadata) {
551
+ if (metadata.status === null)
552
+ unknown = true;
553
+ else
554
+ statuses.add(metadata.status);
555
+ return;
556
+ }
557
+ statuses.add(200);
558
+ };
559
+ visit(type);
560
+ return !unknown && statuses.size === 1 ? [...statuses][0] : null;
561
+ }
562
+ function openApiSchema(type, fieldsOf, seen, enumValuesOf) {
563
+ if (type.kind === "string" || type.kind === "number" || type.kind === "bool") {
564
+ return { type: type.kind === "bool" ? "boolean" : type.kind };
565
+ }
566
+ if (type.kind === "null")
567
+ return { type: "null" };
568
+ if (type.kind === "enum") {
569
+ const values = enumValuesOf(type.identity) ?? enumValuesOf(type.name);
570
+ return values ? { type: "string", enum: values } : { type: "string" };
571
+ }
572
+ if (type.kind === "enumMember")
573
+ return { type: "string", enum: [type.member] };
574
+ if (type.kind === "unknown" || type.kind === "any")
575
+ return {};
576
+ if (type.kind === "optional") {
577
+ return { anyOf: [openApiSchema(type.inner, fieldsOf, seen, enumValuesOf), { type: "null" }] };
578
+ }
579
+ if (type.kind === "union") {
580
+ return { anyOf: type.members.map((member) => openApiSchema(member, fieldsOf, seen, enumValuesOf)) };
581
+ }
582
+ if (type.kind === "list" || type.kind === "set") {
583
+ return {
584
+ type: "array",
585
+ items: openApiSchema(type.element, fieldsOf, seen, enumValuesOf),
586
+ ...(type.kind === "set" ? { uniqueItems: true } : {}),
587
+ };
588
+ }
589
+ if (type.kind === "record") {
590
+ return { type: "object", additionalProperties: openApiSchema(type.value, fieldsOf, seen, enumValuesOf) };
591
+ }
592
+ if (type.kind === "object")
593
+ return openApiObjectSchema(type.fields, fieldsOf, seen, enumValuesOf);
594
+ if (type.kind === "named") {
595
+ if (type.name === "Duration")
596
+ return { type: "number" };
597
+ const identity = type.identity ?? type.name;
598
+ if (seen.has(identity))
599
+ return { type: "object" };
600
+ const fields = fieldsOf(identity);
601
+ if (!fields)
602
+ return {};
603
+ return openApiObjectSchema(fields, fieldsOf, new Set([...seen, identity]), enumValuesOf);
604
+ }
605
+ return {};
606
+ }
607
+ function openApiObjectSchema(fields, fieldsOf, seen, enumValuesOf) {
608
+ const properties = Object.create(null);
609
+ const required = [];
610
+ for (const [name, field] of fields) {
611
+ properties[name] = openApiSchema(field, fieldsOf, seen, enumValuesOf);
612
+ if (field.kind !== "optional")
613
+ required.push(name);
614
+ }
615
+ return {
616
+ type: "object",
617
+ properties,
618
+ ...(required.length > 0 ? { required } : {}),
619
+ additionalProperties: false,
620
+ };
621
+ }
622
+ function routeShape(path) {
623
+ return path.split("/").map((part) => part.startsWith("{") && part.endsWith("}") ? "{}" : part).join("/");
624
+ }
625
+ function validateRoutePath(path, _sourceSpan, report) {
626
+ const names = new Set();
627
+ if (!path.startsWith("/"))
628
+ report("A route path must start with '/'");
629
+ if (path.length > 1 && path.endsWith("/"))
630
+ report("A route path must not end with '/' unless it is the root path");
631
+ if (path.includes("?") || path.includes("#"))
632
+ report("A route path contains only its pathname; declare query parameters in the route signature");
633
+ if (path.includes("\\"))
634
+ report("A route path uses '/', never a backslash");
635
+ if (path.includes("//"))
636
+ report("A route path cannot contain an empty segment");
637
+ const segments = path.split("/").slice(1);
638
+ for (let index = 0; index < segments.length; index += 1) {
639
+ const segment = segments[index];
640
+ if (segment === "*") {
641
+ report("A source route does not use wildcards; compose staticFiles(...) for a checked file fallback");
642
+ continue;
643
+ }
644
+ if (!segment.startsWith("{") && !segment.endsWith("}"))
645
+ continue;
646
+ const match = /^\{([A-Za-z_][A-Za-z0-9_]*):([A-Za-z_][A-Za-z0-9_]*)\}$/u.exec(segment);
647
+ if (!match) {
648
+ report(`Route path capture '${segment}' must use '{name:type}' with a half-width ':'`);
649
+ continue;
650
+ }
651
+ const name = match[1];
652
+ if (names.has(name))
653
+ report(`Route path capture '${name}' appears more than once`);
654
+ else
655
+ names.add(name);
656
+ }
657
+ return names;
658
+ }
659
+ //# sourceMappingURL=server-analyzer.js.map