@supacloud/compiler 0.25.0 → 0.26.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.
- package/README.md +66 -4
- package/dist/benchmark.d.ts +6 -0
- package/dist/cli.js +232 -41
- package/dist/contract-manifest.d.ts +2 -0
- package/dist/generate.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +273 -39
- package/dist/repair-plan.d.ts +17 -0
- package/dist/types.d.ts +8 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -129,7 +129,9 @@ function buildContractManifest(graph, artifacts) {
|
|
|
129
129
|
...route.responses === undefined ? {} : {
|
|
130
130
|
responseSchemas: Object.fromEntries(Object.entries(route.responses).sort(([left], [right]) => left.localeCompare(right)))
|
|
131
131
|
},
|
|
132
|
-
...route.contract?.evidence === undefined ? {} : { evidence: route.contract.evidence }
|
|
132
|
+
...route.contract?.evidence === undefined ? {} : { evidence: route.contract.evidence },
|
|
133
|
+
...route.parse === undefined ? {} : { parse: route.parse },
|
|
134
|
+
...route.allowDeleteBody === undefined ? {} : { allowDeleteBody: route.allowDeleteBody }
|
|
133
135
|
});
|
|
134
136
|
}
|
|
135
137
|
}
|
|
@@ -218,7 +220,7 @@ function renderApplication(graph, options) {
|
|
|
218
220
|
providers: mod.providers.filter((p) => p.providedIn !== "root" || p.multi || referencedTokens.has(p.token) || p.exported)
|
|
219
221
|
}));
|
|
220
222
|
}
|
|
221
|
-
const imports = new ImportManager;
|
|
223
|
+
const imports = new ImportManager(new Set(["CompiledApplicationModule"]));
|
|
222
224
|
const factorySections = [];
|
|
223
225
|
const descriptorEntries = [];
|
|
224
226
|
for (const module of modules) {
|
|
@@ -235,7 +237,13 @@ function renderApplication(graph, options) {
|
|
|
235
237
|
"",
|
|
236
238
|
TYPE_GUARDS,
|
|
237
239
|
"",
|
|
238
|
-
|
|
240
|
+
...modules.length > 0 ? [
|
|
241
|
+
'export type CompiledApplicationModule = Omit<CompiledModule, "name" | "createServices"> & (',
|
|
242
|
+
...modules.map((module) => ` | { name: ${JSON.stringify(module.name)}; createServices: typeof create${pascalName(module.name)}Services }`),
|
|
243
|
+
");"
|
|
244
|
+
] : ["export type CompiledApplicationModule = CompiledModule;"],
|
|
245
|
+
"",
|
|
246
|
+
"export function createCompiledModules(): CompiledApplicationModule[] {",
|
|
239
247
|
" return [",
|
|
240
248
|
...descriptorEntries.map((entry) => indent(entry, 4) + ","),
|
|
241
249
|
" ];",
|
|
@@ -316,7 +324,9 @@ function renderApplication(graph, options) {
|
|
|
316
324
|
};
|
|
317
325
|
}
|
|
318
326
|
async function generateApplication(graph, options) {
|
|
319
|
-
|
|
327
|
+
return writeRenderedApplication(renderApplication(graph, options), options);
|
|
328
|
+
}
|
|
329
|
+
async function writeRenderedApplication(rendered, options) {
|
|
320
330
|
await mkdir(options.outDir, { recursive: true });
|
|
321
331
|
const applicationPath = join2(options.outDir, "application.ts");
|
|
322
332
|
const manifestPath = join2(options.outDir, "app.manifest.json");
|
|
@@ -403,8 +413,12 @@ function indent(text, spaces) {
|
|
|
403
413
|
}
|
|
404
414
|
|
|
405
415
|
class ImportManager {
|
|
416
|
+
reserved;
|
|
406
417
|
entries = new Map;
|
|
407
418
|
lookup = new Map;
|
|
419
|
+
constructor(reserved = new Set) {
|
|
420
|
+
this.reserved = reserved;
|
|
421
|
+
}
|
|
408
422
|
get size() {
|
|
409
423
|
return this.entries.size;
|
|
410
424
|
}
|
|
@@ -419,7 +433,7 @@ class ImportManager {
|
|
|
419
433
|
return existing;
|
|
420
434
|
let local = exported;
|
|
421
435
|
let counter = 2;
|
|
422
|
-
while (this.entries.has(local)) {
|
|
436
|
+
while (this.entries.has(local) || this.reserved.has(local)) {
|
|
423
437
|
local = `${exported}${counter}`;
|
|
424
438
|
counter += 1;
|
|
425
439
|
}
|
|
@@ -476,7 +490,7 @@ class ModuleGenerator {
|
|
|
476
490
|
renderDescriptor() {
|
|
477
491
|
const lines = [
|
|
478
492
|
`{`,
|
|
479
|
-
` name: ${JSON.stringify(this.module.name)},`,
|
|
493
|
+
` name: ${JSON.stringify(this.module.name)} as const,`,
|
|
480
494
|
` createServices: create${this.pascal}Services,`
|
|
481
495
|
];
|
|
482
496
|
if (this.hasFactoryContent("request")) {
|
|
@@ -615,6 +629,10 @@ class ModuleGenerator {
|
|
|
615
629
|
if (route.contract !== undefined) {
|
|
616
630
|
fields.push(`contract: ${JSON.stringify(route.contract)}`);
|
|
617
631
|
}
|
|
632
|
+
if (route.parse === "none")
|
|
633
|
+
fields.push('parse: "none"');
|
|
634
|
+
if (route.allowDeleteBody === true)
|
|
635
|
+
fields.push("allowDeleteBody: true");
|
|
618
636
|
if (route.schemaKinds !== undefined && Object.keys(route.schemaKinds).length > 0) {
|
|
619
637
|
fields.push(`schemaKinds: ${JSON.stringify(route.schemaKinds)}`);
|
|
620
638
|
}
|
|
@@ -714,7 +732,7 @@ ${indent(item, 2)}`).join(",")}
|
|
|
714
732
|
`function create${this.pascal}Services(`,
|
|
715
733
|
` deps: Record<string, unknown>,`,
|
|
716
734
|
` imported: Record<string, Record<string, unknown>>,`,
|
|
717
|
-
`)
|
|
735
|
+
`) {`,
|
|
718
736
|
indent(this.renderFactoryBody("services"), 2),
|
|
719
737
|
`}`
|
|
720
738
|
].join(`
|
|
@@ -811,7 +829,7 @@ ${indent(item, 2)}`).join(",")}
|
|
|
811
829
|
`);
|
|
812
830
|
}
|
|
813
831
|
const entries = [...returns.entries()].map(([key, expr]) => key === expr ? key : `${key}: ${expr}`);
|
|
814
|
-
lines.push(`return { ${entries.join(", ")} };`);
|
|
832
|
+
lines.push(`return retainPlatformDependencies({ ${entries.join(", ")} }, deps);`);
|
|
815
833
|
return lines.join(`
|
|
816
834
|
`);
|
|
817
835
|
}
|
|
@@ -912,6 +930,9 @@ ${indent(item, 2)}`).join(",")}
|
|
|
912
930
|
return this.depExpr(own.useExisting ?? token, kind, options);
|
|
913
931
|
}
|
|
914
932
|
}
|
|
933
|
+
if (own && factoryOfScope(own.scope) === "services" && kind !== "services" && !isSkipSelf && !isSelf) {
|
|
934
|
+
return `services.${camelName(token)}`;
|
|
935
|
+
}
|
|
915
936
|
if (isSelf) {
|
|
916
937
|
return isOptional ? "undefined" : `services.${camelName(token)}`;
|
|
917
938
|
}
|
|
@@ -929,13 +950,14 @@ ${indent(item, 2)}`).join(",")}
|
|
|
929
950
|
return `imported.${mod.name}.${camelName(token)}`;
|
|
930
951
|
}
|
|
931
952
|
}
|
|
932
|
-
if (isOptional && !this.graph.externalTokens.includes(token)) {
|
|
933
|
-
return "undefined";
|
|
934
|
-
}
|
|
935
953
|
if (isSelf)
|
|
936
954
|
return isOptional ? "undefined" : `services.${camelName(token)}`;
|
|
937
955
|
if (kind === "services")
|
|
938
956
|
return isOptional ? `(deps.${camelName(token)} ?? undefined)` : `deps.${camelName(token)}`;
|
|
957
|
+
if (!own || isSkipSelf) {
|
|
958
|
+
const external = `platformDependencies.get(services)?.${camelName(token)}`;
|
|
959
|
+
return isOptional ? `(${external} ?? undefined)` : external;
|
|
960
|
+
}
|
|
939
961
|
return isOptional ? `(services.${camelName(token)} ?? undefined)` : `services.${camelName(token)}`;
|
|
940
962
|
}
|
|
941
963
|
}
|
|
@@ -963,7 +985,7 @@ function orderProviders(providers) {
|
|
|
963
985
|
function renderClient(graph, options) {
|
|
964
986
|
const rootDir = options?.rootDir ?? process.cwd();
|
|
965
987
|
const outDir = options?.outDir ?? process.cwd();
|
|
966
|
-
const imports = new ImportManager;
|
|
988
|
+
const imports = new ImportManager(new Set(["ApiClientError", "ApiClientErrorCode", "ApiClientErrorDetails"]));
|
|
967
989
|
const controllerEntries = [];
|
|
968
990
|
const routeTypes = [];
|
|
969
991
|
const schemaEntries = [];
|
|
@@ -1021,11 +1043,14 @@ function renderClient(graph, options) {
|
|
|
1021
1043
|
}).filter((entry) => entry.length === 2));
|
|
1022
1044
|
if (Object.keys(responseSchemas).length > 0)
|
|
1023
1045
|
usesValue = true;
|
|
1024
|
-
const paramNames = [...new Set(
|
|
1046
|
+
const paramNames = [...new Set([
|
|
1047
|
+
...(fullPath.match(/:([a-zA-Z0-9_]+)/g) ?? []).map((p) => p.slice(1)),
|
|
1048
|
+
...fullPath.endsWith("/*") ? ["*"] : []
|
|
1049
|
+
])];
|
|
1025
1050
|
const pathParamsType = paramNames.length > 0 ? `{ ${paramNames.map((p) => `${/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(p) ? p : JSON.stringify(p)}: string | number`).join("; ")} }` : "Record<string, string | number>";
|
|
1026
1051
|
const paramsType = paramsSchema ? `Static<typeof ${paramsSchema.local}>${paramNames.length > 0 ? ` & ${pathParamsType}` : ""}` : pathParamsType;
|
|
1027
1052
|
const queryType = querySchema ? `Static<typeof ${querySchema.local}>` : "Record<string, unknown>";
|
|
1028
|
-
const bodyType = bodySchema ? `Static<typeof ${bodySchema.local}>` : "unknown";
|
|
1053
|
+
const bodyType = route.parse === "none" ? "BodyInit" : bodySchema ? `Static<typeof ${bodySchema.local}>` : "unknown";
|
|
1029
1054
|
const headersType = headersSchema ? `Static<typeof ${headersSchema.local}>` : "Record<string, string>";
|
|
1030
1055
|
const cookieType = cookieSchema ? `Static<typeof ${cookieSchema.local}>` : "Record<string, string | number | boolean>";
|
|
1031
1056
|
const responseTypes = [
|
|
@@ -1062,7 +1087,9 @@ function renderClient(graph, options) {
|
|
|
1062
1087
|
["queryTransforms", route.queryTransforms],
|
|
1063
1088
|
["queryDefaults", route.queryDefaults],
|
|
1064
1089
|
["title", route.title],
|
|
1065
|
-
["data", route.data]
|
|
1090
|
+
["data", route.data],
|
|
1091
|
+
["parse", route.parse],
|
|
1092
|
+
["allowDeleteBody", route.allowDeleteBody]
|
|
1066
1093
|
]) {
|
|
1067
1094
|
if (value !== undefined)
|
|
1068
1095
|
routeFields.push(`${key}: ${JSON.stringify(value)}`);
|
|
@@ -1096,7 +1123,7 @@ function renderClient(graph, options) {
|
|
|
1096
1123
|
const responseSchemaArgument = responseSchemaEntries.length > 0 ? `{ ${responseSchemaEntries.map(([status, local]) => `${JSON.stringify(status)}: ${local}`).join(", ")} }` : "undefined";
|
|
1097
1124
|
const responseKindArgument = responseKind === undefined ? "undefined" : JSON.stringify(responseKind);
|
|
1098
1125
|
routeMethods.push(`
|
|
1099
|
-
${route.handler}: makeRoute<${requestTypeName}, ${responseType === "never" ? "never" : responseTypeName}>(${JSON.stringify(route.method)}, ${JSON.stringify(fullPath)}, ${responseSchemaArgument}, ${responseKindArgument}),`);
|
|
1126
|
+
${route.handler}: makeRoute<${requestTypeName}, ${responseType === "never" ? "never" : responseTypeName}>(${JSON.stringify(route.method)}, ${JSON.stringify(fullPath)}, ${responseSchemaArgument}, ${responseKindArgument}, ${route.parse === "none"}),`);
|
|
1100
1127
|
}
|
|
1101
1128
|
controllerEntries.push(`
|
|
1102
1129
|
${controllerKey}: {${routeMethods.join("")}
|
|
@@ -1127,6 +1154,40 @@ function renderClient(graph, options) {
|
|
|
1127
1154
|
"",
|
|
1128
1155
|
"export type ResponseDecoder<T> = (value: unknown) => T;",
|
|
1129
1156
|
"",
|
|
1157
|
+
'export type ApiClientErrorCode = "API_HTTP_ERROR" | "API_RESPONSE_INVALID" | "API_RESPONSE_UNDECLARED";',
|
|
1158
|
+
"",
|
|
1159
|
+
"export interface ApiClientErrorDetails {",
|
|
1160
|
+
" code: ApiClientErrorCode;",
|
|
1161
|
+
" method: string;",
|
|
1162
|
+
" path: string;",
|
|
1163
|
+
" status: number;",
|
|
1164
|
+
" requestId?: string | null;",
|
|
1165
|
+
" response?: Response;",
|
|
1166
|
+
"}",
|
|
1167
|
+
"",
|
|
1168
|
+
"/** Transport/contract metadata, not proof that a failed write rolled back. */",
|
|
1169
|
+
"export class ApiClientError extends Error {",
|
|
1170
|
+
" readonly code: ApiClientErrorCode;",
|
|
1171
|
+
" readonly method: string;",
|
|
1172
|
+
" readonly path: string;",
|
|
1173
|
+
" readonly status: number;",
|
|
1174
|
+
" readonly requestId: string | undefined;",
|
|
1175
|
+
" /** Undeclared HTTP response for explicit inspection; excluded from JSON serialization. */",
|
|
1176
|
+
" readonly response: Response | undefined;",
|
|
1177
|
+
" constructor(message: string, details: ApiClientErrorDetails) {",
|
|
1178
|
+
" super(message);",
|
|
1179
|
+
' this.name = "ApiClientError";',
|
|
1180
|
+
" const { code, method, path, status, requestId, response } = details;",
|
|
1181
|
+
" this.code = code;",
|
|
1182
|
+
" this.method = method;",
|
|
1183
|
+
" this.path = path;",
|
|
1184
|
+
" this.status = status;",
|
|
1185
|
+
" this.requestId = requestId && /^[A-Za-z0-9._:-]{1,256}$/.test(requestId) ? requestId : undefined;",
|
|
1186
|
+
" this.response = response;",
|
|
1187
|
+
' Object.defineProperty(this, "response", { enumerable: false });',
|
|
1188
|
+
" }",
|
|
1189
|
+
"}",
|
|
1190
|
+
"",
|
|
1130
1191
|
"export type RouteMethod<",
|
|
1131
1192
|
" Options extends ClientRequestOptions = ClientRequestOptions,",
|
|
1132
1193
|
" Result = never,",
|
|
@@ -1180,6 +1241,13 @@ function renderClient(graph, options) {
|
|
|
1180
1241
|
' if (typeof value !== "string" && typeof value !== "number") throw new TypeError(`Missing route parameter: ${key}`);',
|
|
1181
1242
|
" return encodeURIComponent(String(value));",
|
|
1182
1243
|
" });",
|
|
1244
|
+
' if (path.endsWith("/*")) {',
|
|
1245
|
+
' const wildcard = params && Object.hasOwn(params, "*") ? params["*"] : undefined;',
|
|
1246
|
+
' if (typeof wildcard !== "string" && typeof wildcard !== "number") throw new TypeError("Missing route parameter: *");',
|
|
1247
|
+
' const segments = String(wildcard).split("/");',
|
|
1248
|
+
' if (segments.some(segment => segment === "." || segment === "..")) throw new TypeError("Wildcard dot segments are not supported by Fetch URLs");',
|
|
1249
|
+
' url = url.slice(0, -1) + segments.map(encodeURIComponent).join("/");',
|
|
1250
|
+
" }",
|
|
1183
1251
|
" if (query) {",
|
|
1184
1252
|
" const searchParams = new URLSearchParams();",
|
|
1185
1253
|
" for (const [k, v] of Object.entries(query)) {",
|
|
@@ -1366,6 +1434,10 @@ function renderClient(graph, options) {
|
|
|
1366
1434
|
"export function createApiClient(config: ApiClientConfig = {}) {",
|
|
1367
1435
|
" const fetcher = config.fetch ?? globalThis.fetch.bind(globalThis);",
|
|
1368
1436
|
' const baseUrl = (config.baseUrl ?? "").replace(/\\/+$/, "");',
|
|
1437
|
+
" function rawBody(value: unknown): BodyInit {",
|
|
1438
|
+
' if (typeof value === "string" || value instanceof Blob || value instanceof FormData || value instanceof URLSearchParams || value instanceof ArrayBuffer || ArrayBuffer.isView(value) || value instanceof ReadableStream) return value as BodyInit;',
|
|
1439
|
+
' throw new TypeError("Raw routes require a BodyInit value; JSON encoding is not implicit");',
|
|
1440
|
+
" }",
|
|
1369
1441
|
"",
|
|
1370
1442
|
" async function request<T>(",
|
|
1371
1443
|
" method: string,",
|
|
@@ -1374,6 +1446,7 @@ function renderClient(graph, options) {
|
|
|
1374
1446
|
" decode: ResponseDecoder<T>,",
|
|
1375
1447
|
` responseSchemas?: Readonly<Record<string, ${responseSchemaType}>>,`,
|
|
1376
1448
|
" responseKind?: string,",
|
|
1449
|
+
" raw?: boolean,",
|
|
1377
1450
|
" ): Promise<T>;",
|
|
1378
1451
|
" async function request<T>(",
|
|
1379
1452
|
" method: string,",
|
|
@@ -1382,6 +1455,7 @@ function renderClient(graph, options) {
|
|
|
1382
1455
|
" decode?: ResponseDecoder<T>,",
|
|
1383
1456
|
` responseSchemas?: Readonly<Record<string, ${responseSchemaType}>>,`,
|
|
1384
1457
|
" responseKind?: string,",
|
|
1458
|
+
" raw?: boolean,",
|
|
1385
1459
|
" ): Promise<T | unknown>;",
|
|
1386
1460
|
" async function request(",
|
|
1387
1461
|
" method: string,",
|
|
@@ -1395,11 +1469,12 @@ function renderClient(graph, options) {
|
|
|
1395
1469
|
" decode?: ResponseDecoder<T>,",
|
|
1396
1470
|
` responseSchemas?: Readonly<Record<string, ${responseSchemaType}>>,`,
|
|
1397
1471
|
" responseKind?: string,",
|
|
1472
|
+
" raw = false,",
|
|
1398
1473
|
" ): Promise<T | unknown> {",
|
|
1399
1474
|
" const url = `${baseUrl}${buildRouteUrl(path, options.params, options.query)}`;",
|
|
1400
1475
|
' const customHeaders = typeof config.headers === "function" ? await config.headers() : config.headers;',
|
|
1401
1476
|
" const headers: Record<string, string> = {",
|
|
1402
|
-
' "content-type": "application/json",',
|
|
1477
|
+
' ...(raw ? {} : { "content-type": "application/json" }),',
|
|
1403
1478
|
" ...customHeaders,",
|
|
1404
1479
|
" ...options.headers,",
|
|
1405
1480
|
" };",
|
|
@@ -1418,21 +1493,27 @@ function renderClient(graph, options) {
|
|
|
1418
1493
|
" return fetcher(reqPayload.url, {",
|
|
1419
1494
|
" method: reqPayload.method,",
|
|
1420
1495
|
" headers: reqPayload.headers,",
|
|
1421
|
-
" ...(reqPayload.body === undefined ? {} : { body: JSON.stringify(reqPayload.body) }),",
|
|
1496
|
+
" ...(reqPayload.body === undefined ? {} : { body: raw ? rawBody(reqPayload.body) : JSON.stringify(reqPayload.body) }),",
|
|
1497
|
+
' ...(raw && reqPayload.body instanceof ReadableStream ? { duplex: "half" } : {}),',
|
|
1422
1498
|
" });",
|
|
1423
1499
|
" };",
|
|
1424
1500
|
" const response = await executeChain(0, { method, url, headers, body: options.body });",
|
|
1425
1501
|
" const declaredSchema = responseSchemas ? selectResponseSchema(response.status, responseSchemas) : undefined;",
|
|
1502
|
+
" const failure = (code: ApiClientErrorCode, message: string) => new ApiClientError(message, {",
|
|
1503
|
+
' code, method, path, status: response.status, requestId: response.headers.get("x-request-id"),',
|
|
1504
|
+
' ...(code === "API_HTTP_ERROR" ? { response } : {}),',
|
|
1505
|
+
" });",
|
|
1426
1506
|
" if (!response.ok && declaredSchema === undefined) {",
|
|
1427
|
-
|
|
1428
|
-
" throw new Error(`API request failed: ${method} ${path} -> ${response.status} ${errBody}`);",
|
|
1507
|
+
' throw failure("API_HTTP_ERROR", `API request failed: ${method} ${path} -> ${response.status}`);',
|
|
1429
1508
|
" }",
|
|
1430
1509
|
' const contentType = response.headers?.get("content-type") ?? "";',
|
|
1431
1510
|
" let value: unknown;",
|
|
1432
1511
|
" if ([204, 205, 304].includes(response.status)) {",
|
|
1433
1512
|
" value = undefined;",
|
|
1434
1513
|
' } else if (contentType.includes("application/json") || contentType.includes("+json")) {',
|
|
1435
|
-
"
|
|
1514
|
+
" const responseText = await response.text();",
|
|
1515
|
+
" try { value = JSON.parse(responseText); }",
|
|
1516
|
+
' catch { throw failure("API_RESPONSE_INVALID", "Response is not valid JSON"); }',
|
|
1436
1517
|
' } else if (responseKind === "binary" || contentType.includes("application/octet-stream")) {',
|
|
1437
1518
|
" value = await response.arrayBuffer();",
|
|
1438
1519
|
' } else if (responseKind === "stream") {',
|
|
@@ -1443,20 +1524,24 @@ function renderClient(graph, options) {
|
|
|
1443
1524
|
" if (!responseSchemas) return decode ? decode(value) : value;",
|
|
1444
1525
|
" if (declaredSchema === undefined) {",
|
|
1445
1526
|
' const transportSuccess = response.ok && (responseKind === "binary" || responseKind === "stream");',
|
|
1446
|
-
|
|
1527
|
+
' if (!transportSuccess) throw failure("API_RESPONSE_UNDECLARED", `No response schema declared for HTTP ${response.status}`);',
|
|
1447
1528
|
" return decode ? decode(value) : value;",
|
|
1448
1529
|
" }",
|
|
1449
|
-
...usesValue ? [
|
|
1530
|
+
...usesValue ? [
|
|
1531
|
+
" let checked: unknown;",
|
|
1532
|
+
" try { checked = decodeResponseSchema(value, response.status, responseSchemas, { normalize: config.normalize ?? true }); }",
|
|
1533
|
+
' catch { throw failure("API_RESPONSE_INVALID", "Response does not match schema"); }'
|
|
1534
|
+
] : [" const checked = value;"],
|
|
1450
1535
|
" return decode ? decode(checked) : checked;",
|
|
1451
1536
|
" }",
|
|
1452
1537
|
"",
|
|
1453
|
-
` function makeRoute<Options extends ClientRequestOptions, Result = never>(method: string, path: string, responseSchemas?: Readonly<Record<string, ${responseSchemaType}>>, responseKind?: string): RouteMethod<Options, Result> {`,
|
|
1538
|
+
` function makeRoute<Options extends ClientRequestOptions, Result = never>(method: string, path: string, responseSchemas?: Readonly<Record<string, ${responseSchemaType}>>, responseKind?: string, raw = false): RouteMethod<Options, Result> {`,
|
|
1454
1539
|
" function route<T>(options: Options, decode: ResponseDecoder<T>): Promise<T>;",
|
|
1455
1540
|
" function route(options: Options): Promise<Result>;",
|
|
1456
1541
|
" function route(options?: Options): Promise<unknown>;",
|
|
1457
1542
|
" function route<T>(options?: Options, decode?: ResponseDecoder<T>): Promise<T | unknown> {",
|
|
1458
1543
|
" const requestOptions = options ?? {};",
|
|
1459
|
-
" return request(method, path, requestOptions, decode, responseSchemas, responseKind);",
|
|
1544
|
+
" return request(method, path, requestOptions, decode, responseSchemas, responseKind, raw);",
|
|
1460
1545
|
" }",
|
|
1461
1546
|
" return route as RouteMethod<Options, Result>;",
|
|
1462
1547
|
" }",
|
|
@@ -1513,11 +1598,15 @@ function renderOpenApi(graph, options) {
|
|
|
1513
1598
|
for (const module of graph.modules) {
|
|
1514
1599
|
for (const controller of module.controllers) {
|
|
1515
1600
|
for (const route of controller.routes) {
|
|
1601
|
+
const documentation = route.data?.openapi;
|
|
1602
|
+
if (documentation !== null && typeof documentation === "object" && "hide" in documentation && documentation.hide === true)
|
|
1603
|
+
continue;
|
|
1516
1604
|
const fullPath = joinRoutePaths(controller.path, route.path);
|
|
1517
1605
|
const responseEntries = normalizedResponseEntries(route.responses);
|
|
1518
1606
|
const hasResponseMap = responseEntries.length > 0;
|
|
1519
1607
|
const pathParams = [...new Set([
|
|
1520
1608
|
...(fullPath.match(/:([A-Za-z0-9_]+)/g) ?? []).map((item) => item.slice(1)),
|
|
1609
|
+
...fullPath.endsWith("/*") ? ["*"] : [],
|
|
1521
1610
|
...route.pathParams ?? []
|
|
1522
1611
|
].filter((item) => item.length > 0))];
|
|
1523
1612
|
const queryParams = [...new Set([
|
|
@@ -1550,6 +1639,8 @@ function renderOpenApi(graph, options) {
|
|
|
1550
1639
|
}).filter((entry) => entry.length === 2)) } : {},
|
|
1551
1640
|
responseContentType,
|
|
1552
1641
|
responseKind: responseKind ?? (route.nativeResponse ? "native-response" : undefined),
|
|
1642
|
+
...route.parse === undefined ? {} : { parse: route.parse },
|
|
1643
|
+
...route.allowDeleteBody === undefined ? {} : { allowDeleteBody: route.allowDeleteBody },
|
|
1553
1644
|
module: module.name,
|
|
1554
1645
|
controller: controller.className,
|
|
1555
1646
|
handler: route.handler,
|
|
@@ -1633,6 +1724,8 @@ function renderOpenApi(graph, options) {
|
|
|
1633
1724
|
" canMatch?: readonly string[];",
|
|
1634
1725
|
" canDeactivate?: readonly string[];",
|
|
1635
1726
|
" contract?: Record<string, unknown>;",
|
|
1727
|
+
' parse?: "none";',
|
|
1728
|
+
" allowDeleteBody?: true;",
|
|
1636
1729
|
" data?: Record<string, unknown>;",
|
|
1637
1730
|
" requiresAuth?: boolean;",
|
|
1638
1731
|
"};",
|
|
@@ -1830,8 +1923,15 @@ function renderOpenApi(graph, options) {
|
|
|
1830
1923
|
' return { $ref: `#/components/schemas/${encodeURIComponent(name).replaceAll("%2F", "/")}` };',
|
|
1831
1924
|
"}",
|
|
1832
1925
|
"",
|
|
1926
|
+
"function wildcardParameterName(path: string): string {",
|
|
1927
|
+
" const names = new Set([...path.matchAll(/:([A-Za-z0-9_]+)/g)].map(match => match[1]));",
|
|
1928
|
+
' let name = "wildcard";',
|
|
1929
|
+
' while (names.has(name)) name += "_";',
|
|
1930
|
+
" return name;",
|
|
1931
|
+
"}",
|
|
1932
|
+
"",
|
|
1833
1933
|
"function openApiPath(path: string): string {",
|
|
1834
|
-
' return path.replace(/:([A-Za-z0-9_]+)/g, "{$1}");',
|
|
1934
|
+
' return path.replace(/:([A-Za-z0-9_]+)/g, "{$1}").replace(/\\/\\*$/, `/{${wildcardParameterName(path)}}`);',
|
|
1835
1935
|
"}",
|
|
1836
1936
|
"",
|
|
1837
1937
|
"function responseHasBody(status: string, kind: string | undefined): boolean {",
|
|
@@ -1861,7 +1961,7 @@ function renderOpenApi(graph, options) {
|
|
|
1861
1961
|
" const parameters: Record<string, unknown>[] = [];",
|
|
1862
1962
|
" const paramsSchema = definition.paramsSchema ? OPENAPI_SCHEMA_REGISTRY[definition.paramsSchema as keyof typeof OPENAPI_SCHEMA_REGISTRY] : undefined;",
|
|
1863
1963
|
" for (const name of definition.pathParams) parameters.push({",
|
|
1864
|
-
' name, in: "path", required: true, schema: schemaProperty(paramsSchema, name),',
|
|
1964
|
+
' name: name === "*" ? wildcardParameterName(definition.path) : name, in: "path", required: true, schema: schemaProperty(paramsSchema, name),',
|
|
1865
1965
|
" });",
|
|
1866
1966
|
" const querySchema = definition.querySchema ? OPENAPI_SCHEMA_REGISTRY[definition.querySchema as keyof typeof OPENAPI_SCHEMA_REGISTRY] : undefined;",
|
|
1867
1967
|
" const queryNames = new Set([...(querySchema ? schemaPropertyNames(querySchema) : []), ...definition.queryParams]);",
|
|
@@ -1898,6 +1998,7 @@ function renderOpenApi(graph, options) {
|
|
|
1898
1998
|
" operationId: definition.operationId, tags: [...definition.tags], summary: definition.summary, responses,",
|
|
1899
1999
|
" ...(parameters.length === 0 ? {} : { parameters }),",
|
|
1900
2000
|
' ...(definition.bodySchema ? { requestBody: { required: !schemaAllowsUndefined(OPENAPI_SCHEMA_REGISTRY[definition.bodySchema as keyof typeof OPENAPI_SCHEMA_REGISTRY]), content: { "application/json": { schema: schemaReference(definition.bodySchema) } } } } : {}),',
|
|
2001
|
+
' ...(definition.parse === "none" ? { requestBody: { required: false, description: "Raw request bytes; validation and media types are owned by the domain handler.", content: { "*/*": { schema: { type: "string", format: "binary" } } } } } : {}),',
|
|
1901
2002
|
" ...(definition.requiresAuth ? { security: [{ bearerAuth: [] }] } : {}),",
|
|
1902
2003
|
' "x-supacloud": {',
|
|
1903
2004
|
" module: definition.module, controller: definition.controller, handler: definition.handler,",
|
|
@@ -1907,6 +2008,8 @@ function renderOpenApi(graph, options) {
|
|
|
1907
2008
|
" ...(definition.canMatch ? { canMatch: [...definition.canMatch] } : {}),",
|
|
1908
2009
|
" ...(definition.canDeactivate ? { canDeactivate: [...definition.canDeactivate] } : {}),",
|
|
1909
2010
|
" ...(definition.contract ? { contract: definition.contract } : {}),",
|
|
2011
|
+
" ...(definition.parse ? { parse: definition.parse } : {}),",
|
|
2012
|
+
" ...(definition.allowDeleteBody ? { allowDeleteBody: true } : {}),",
|
|
1910
2013
|
" ...(definition.data ? { data: definition.data } : {}),",
|
|
1911
2014
|
" },",
|
|
1912
2015
|
" };",
|
|
@@ -2002,6 +2105,8 @@ function renderPermissions(graph) {
|
|
|
2002
2105
|
`);
|
|
2003
2106
|
}
|
|
2004
2107
|
var HEADER = "// GENERATED BY @supacloud/compiler — do not edit", INTERFACES = `export interface CompiledRoute {
|
|
2108
|
+
parse?: "none";
|
|
2109
|
+
allowDeleteBody?: true;
|
|
2005
2110
|
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
|
|
2006
2111
|
path: string;
|
|
2007
2112
|
handler: string;
|
|
@@ -2154,6 +2259,12 @@ function resolveFactoryValue(value: unknown): unknown {
|
|
|
2154
2259
|
}
|
|
2155
2260
|
|
|
2156
2261
|
const scopeDestructions = new WeakMap<object, Promise<void>>();
|
|
2262
|
+
const platformDependencies = new WeakMap<object, Record<string, unknown>>();
|
|
2263
|
+
|
|
2264
|
+
function retainPlatformDependencies<T extends Record<string, unknown>>(services: T, deps: Record<string, unknown>): T {
|
|
2265
|
+
platformDependencies.set(services, deps);
|
|
2266
|
+
return services;
|
|
2267
|
+
}
|
|
2157
2268
|
|
|
2158
2269
|
function destroyScopeInstances(
|
|
2159
2270
|
scope: Record<string, unknown>,
|
|
@@ -3616,6 +3727,7 @@ var COMPILER_DIAGNOSTIC_CODES = {
|
|
|
3616
3727
|
};
|
|
3617
3728
|
function validateGraph(graph, options = false) {
|
|
3618
3729
|
const strict = typeof options === "boolean" ? options : options.strict ?? false;
|
|
3730
|
+
const generateOpenApi = typeof options === "object" && options.generateOpenApi === true;
|
|
3619
3731
|
const diagnostics = [];
|
|
3620
3732
|
for (const module of graph.modules) {
|
|
3621
3733
|
for (const owner of [...module.providers, ...module.controllers]) {
|
|
@@ -3751,7 +3863,7 @@ function validateGraph(graph, options = false) {
|
|
|
3751
3863
|
}
|
|
3752
3864
|
const fullPath = joinRoutePaths2(controller.path, route.path);
|
|
3753
3865
|
const rawFullPath = joinRawRoutePaths(controller.path, route.path);
|
|
3754
|
-
const key = `${route.method} ${fullPath}`;
|
|
3866
|
+
const key = `${route.method} ${generateOpenApi ? fullPath.replace(/:[A-Za-z0-9_]+|\*$/g, "{}") : fullPath}`;
|
|
3755
3867
|
const openApiMatch = route.path.match(/\{([a-zA-Z0-9_]+)\}/);
|
|
3756
3868
|
if (openApiMatch) {
|
|
3757
3869
|
error("missing-param-colon", `Route path '${route.path}' in '${controller.className}.${route.handler}' uses OpenAPI-style '{${openApiMatch[1]}}'. SupaCloud routes require Express/Angular-style ':${openApiMatch[1]}'.`, controller.file, undefined, `Replace '{${openApiMatch[1]}}' with ':${openApiMatch[1]}'.`);
|
|
@@ -3851,7 +3963,13 @@ function validateGraph(graph, options = false) {
|
|
|
3851
3963
|
}
|
|
3852
3964
|
}
|
|
3853
3965
|
}
|
|
3854
|
-
if (
|
|
3966
|
+
if (route.allowDeleteBody && (route.method !== "DELETE" || !route.body)) {
|
|
3967
|
+
error("invalid-delete-body-opt-in", `Route ${controller.className}.${route.handler} allowDeleteBody requires DELETE and an explicit body schema.`, controller.file);
|
|
3968
|
+
}
|
|
3969
|
+
if (route.parse === "none" && (route.body || route.hasBodyBinding || route.contract?.body !== "domain" || !route.contract.evidence?.trim())) {
|
|
3970
|
+
error("invalid-raw-body-contract", `Route ${controller.className}.${route.handler} parse: "none" requires domain body ownership and evidence, without a parsed body schema or @Body binding.`, controller.file);
|
|
3971
|
+
}
|
|
3972
|
+
if ((route.method === "GET" || route.method === "HEAD" || route.method === "OPTIONS" || route.method === "DELETE" && !route.allowDeleteBody) && (route.hasBodyBinding || route.body)) {
|
|
3855
3973
|
error("disallowed-body-on-get-delete", `Route handler ${controller.className}.${route.handler} binds @Body() or declares body schema on HTTP ${route.method} route '${route.path}'. Request bodies are not supported on ${route.method} requests.`, controller.file, undefined, `Use POST, PUT, or PATCH for routes accepting a request body, or bind parameters via @Query() / @Param().`, {
|
|
3856
3974
|
type: "remove_route_body_binding",
|
|
3857
3975
|
targetFile: controller.file,
|
|
@@ -4563,6 +4681,7 @@ async function analyzeProject(rootDir, include, cache, changedPaths) {
|
|
|
4563
4681
|
tokensByName: new Map,
|
|
4564
4682
|
classesByName: new Map,
|
|
4565
4683
|
variablesByName: new Map,
|
|
4684
|
+
moduleHandlerFiles: new Map,
|
|
4566
4685
|
diagnostics: []
|
|
4567
4686
|
};
|
|
4568
4687
|
const nativeTraitFiles = new Map;
|
|
@@ -4653,10 +4772,11 @@ async function analyzeProject(rootDir, include, cache, changedPaths) {
|
|
|
4653
4772
|
const hasChangedFile = entry.ownedFiles.some((f) => changedFiles.has(f));
|
|
4654
4773
|
const moduleFileExists = currentFileHashes.has(entry.module.file);
|
|
4655
4774
|
const isAffectedByDep = affectedModuleNames.has(modName);
|
|
4656
|
-
if (!hasChangedFile && !isAffectedByDep && moduleFileExists) {
|
|
4775
|
+
if (!hasChangedFile && !isAffectedByDep && moduleFileExists && entry.handlerFiles !== undefined) {
|
|
4657
4776
|
modulesToKeep.set(modName, entry);
|
|
4658
4777
|
reusedModules.push(modName);
|
|
4659
4778
|
finalModules.push(entry.module);
|
|
4779
|
+
ctx.moduleHandlerFiles.set(modName, entry.handlerFiles);
|
|
4660
4780
|
if (entry.diagnostics)
|
|
4661
4781
|
finalDiagnostics.push(...entry.diagnostics);
|
|
4662
4782
|
}
|
|
@@ -4676,6 +4796,7 @@ async function analyzeProject(rootDir, include, cache, changedPaths) {
|
|
|
4676
4796
|
}
|
|
4677
4797
|
cache.modules.set(parsed.name, {
|
|
4678
4798
|
module: parsed,
|
|
4799
|
+
handlerFiles: ctx.moduleHandlerFiles.get(parsed.name) ?? [],
|
|
4679
4800
|
ownedFiles: [...ownedFiles],
|
|
4680
4801
|
fileHashes,
|
|
4681
4802
|
diagnostics: moduleDiagnostics
|
|
@@ -4844,6 +4965,7 @@ async function analyzeProject(rootDir, include, cache, changedPaths) {
|
|
|
4844
4965
|
externalTokens,
|
|
4845
4966
|
diagnostics: ctx.diagnostics.map(withDiagnosticMetadata),
|
|
4846
4967
|
tokenNames,
|
|
4968
|
+
moduleHandlerFiles: Object.fromEntries(ctx.moduleHandlerFiles),
|
|
4847
4969
|
...cache ? { cacheStats: { reusedModules, reanalyzedModules } } : {}
|
|
4848
4970
|
};
|
|
4849
4971
|
}
|
|
@@ -5056,6 +5178,7 @@ function parseModule(candidate, nameByNode, ctx) {
|
|
|
5056
5178
|
arrayProp(options, "commands").forEach(collectHandler);
|
|
5057
5179
|
arrayProp(options, "jobs").forEach(collectHandler);
|
|
5058
5180
|
arrayProp(options, "queries").forEach(collectHandler);
|
|
5181
|
+
ctx.moduleHandlerFiles.set(name, [...new Set([...handlerClasses].map((cls) => sourcePath(ctx.rootDir, cls.getSourceFile().fileName)))].sort());
|
|
5059
5182
|
const commands = [];
|
|
5060
5183
|
const jobs = [];
|
|
5061
5184
|
const queries = [];
|
|
@@ -5234,6 +5357,25 @@ function resolveStaticObjectLiteral(input, ctx, seen = new Set) {
|
|
|
5234
5357
|
}
|
|
5235
5358
|
return ts4.isObjectLiteralExpression(input) ? input : undefined;
|
|
5236
5359
|
}
|
|
5360
|
+
function routePolicyProperty(object, name, ctx, seen = new Set) {
|
|
5361
|
+
if (seen.has(object))
|
|
5362
|
+
return { unresolved: true };
|
|
5363
|
+
const ancestry = new Set(seen).add(object);
|
|
5364
|
+
let result = { unresolved: false };
|
|
5365
|
+
for (const property of object.properties) {
|
|
5366
|
+
if (ts4.isSpreadAssignment(property)) {
|
|
5367
|
+
const spread = resolveStaticObjectLiteral(property.expression, ctx);
|
|
5368
|
+
const nested = spread ? routePolicyProperty(spread, name, ctx, ancestry) : { unresolved: true };
|
|
5369
|
+
if (nested.unresolved || nested.value !== undefined)
|
|
5370
|
+
result = nested;
|
|
5371
|
+
} else if (ts4.isComputedPropertyName(property.name)) {
|
|
5372
|
+
result = { unresolved: true };
|
|
5373
|
+
} else if (propertyName(property.name) === name) {
|
|
5374
|
+
result = ts4.isPropertyAssignment(property) ? { value: property.initializer, unresolved: false } : { unresolved: true };
|
|
5375
|
+
}
|
|
5376
|
+
}
|
|
5377
|
+
return result;
|
|
5378
|
+
}
|
|
5237
5379
|
function commandModeProp(object, name) {
|
|
5238
5380
|
const value = stringLiteralProp(object, name);
|
|
5239
5381
|
return value === "required" || value === "none" ? value : undefined;
|
|
@@ -5741,10 +5883,13 @@ function parseController(input, ctx) {
|
|
|
5741
5883
|
const pathParams = [];
|
|
5742
5884
|
const paramRegex = /:([a-zA-Z0-9_]+)/g;
|
|
5743
5885
|
let match;
|
|
5744
|
-
|
|
5886
|
+
const fullPath = joinRoutePaths(path, routePath);
|
|
5887
|
+
while ((match = paramRegex.exec(fullPath)) !== null) {
|
|
5745
5888
|
if (match[1] !== undefined)
|
|
5746
5889
|
pathParams.push(match[1]);
|
|
5747
5890
|
}
|
|
5891
|
+
if (joinRoutePaths(path, routePath).endsWith("/*"))
|
|
5892
|
+
pathParams.push("*");
|
|
5748
5893
|
if (pathParams.length > 0)
|
|
5749
5894
|
route.pathParams = pathParams;
|
|
5750
5895
|
const paramBindings = [];
|
|
@@ -5895,6 +6040,40 @@ function parseController(input, ctx) {
|
|
|
5895
6040
|
const optionsArg = args[1];
|
|
5896
6041
|
const optionsObject = resolveStaticObjectLiteral(optionsArg, ctx);
|
|
5897
6042
|
if (optionsObject) {
|
|
6043
|
+
const parsePolicy = routePolicyProperty(optionsObject, "parse", ctx);
|
|
6044
|
+
const deletePolicy = routePolicyProperty(optionsObject, "allowDeleteBody", ctx);
|
|
6045
|
+
if (parsePolicy.unresolved || deletePolicy.unresolved) {
|
|
6046
|
+
ctx.diagnostics.push({
|
|
6047
|
+
severity: "error",
|
|
6048
|
+
code: "unresolved-route-body-policy",
|
|
6049
|
+
file,
|
|
6050
|
+
message: `Route ${route.handler} body policy must be statically resolvable; dynamic spreads, computed keys and shorthand policies are unsupported.`
|
|
6051
|
+
});
|
|
6052
|
+
}
|
|
6053
|
+
const parse = parsePolicy.value;
|
|
6054
|
+
if (parse) {
|
|
6055
|
+
if (ts4.isStringLiteral(parse) && parse.text === "none")
|
|
6056
|
+
route.parse = "none";
|
|
6057
|
+
else
|
|
6058
|
+
ctx.diagnostics.push({
|
|
6059
|
+
severity: "error",
|
|
6060
|
+
code: "invalid-route-parser",
|
|
6061
|
+
file,
|
|
6062
|
+
message: `Route ${route.handler} parse must be the literal "none".`
|
|
6063
|
+
});
|
|
6064
|
+
}
|
|
6065
|
+
const allowDeleteBody = deletePolicy.value;
|
|
6066
|
+
if (allowDeleteBody) {
|
|
6067
|
+
if (allowDeleteBody.kind === ts4.SyntaxKind.TrueKeyword)
|
|
6068
|
+
route.allowDeleteBody = true;
|
|
6069
|
+
else
|
|
6070
|
+
ctx.diagnostics.push({
|
|
6071
|
+
severity: "error",
|
|
6072
|
+
code: "invalid-delete-body-opt-in",
|
|
6073
|
+
file,
|
|
6074
|
+
message: `Route ${route.handler} allowDeleteBody must be the literal true.`
|
|
6075
|
+
});
|
|
6076
|
+
}
|
|
5898
6077
|
const legacyResponseExpr = getProp(optionsObject, "response");
|
|
5899
6078
|
const responsesExpr = getProp(optionsObject, "responses");
|
|
5900
6079
|
if (legacyResponseExpr !== undefined && responsesExpr !== undefined) {
|
|
@@ -7086,7 +7265,7 @@ async function compileProject(options) {
|
|
|
7086
7265
|
...options,
|
|
7087
7266
|
...options.cache ? { artifactHashes: options.cache.generatedHashes } : {}
|
|
7088
7267
|
};
|
|
7089
|
-
const written = !hasErrors || options.writeOnError === true ? await
|
|
7268
|
+
const written = !hasErrors || options.writeOnError === true ? await writeRenderedApplication(rendered, generatedOptions) : [];
|
|
7090
7269
|
if (!hasErrors) {
|
|
7091
7270
|
for (const [filename, content] of Object.entries(graphql.files)) {
|
|
7092
7271
|
const path = join4(options.outDir, filename);
|
|
@@ -7227,10 +7406,7 @@ function explainGraph(graph, subject) {
|
|
|
7227
7406
|
throw new Error(`No module, provider, or external token named "${subject}". Known names: ${known.join(", ") || "(none)"}`);
|
|
7228
7407
|
}
|
|
7229
7408
|
function createContextPack(graph, subject) {
|
|
7230
|
-
const subjectModule = graph
|
|
7231
|
-
if (!subjectModule) {
|
|
7232
|
-
throw new Error(`No module named "${subject}". Context packs require a module name.`);
|
|
7233
|
-
}
|
|
7409
|
+
const subjectModule = resolveContextModule(graph, subject);
|
|
7234
7410
|
const byName = new Map(graph.modules.map((module) => [module.name, module]));
|
|
7235
7411
|
const selected = new Set([subjectModule.name]);
|
|
7236
7412
|
const reverseDependents = new Map;
|
|
@@ -7278,6 +7454,7 @@ function createContextPack(graph, subject) {
|
|
|
7278
7454
|
module.file,
|
|
7279
7455
|
...module.providers.map((provider) => provider.file),
|
|
7280
7456
|
...module.controllers.map((controller) => controller.file),
|
|
7457
|
+
...graph.moduleHandlerFiles?.[module.name] ?? [],
|
|
7281
7458
|
...allAspects(module).flatMap((aspect) => aspect.file ? [aspect.file] : [])
|
|
7282
7459
|
]).concat(graphql ? [graphql.schema, ...queryDocuments] : []))].sort();
|
|
7283
7460
|
const referencedTokens = new Set;
|
|
@@ -7307,6 +7484,18 @@ function createContextPack(graph, subject) {
|
|
|
7307
7484
|
}
|
|
7308
7485
|
};
|
|
7309
7486
|
}
|
|
7487
|
+
function resolveContextModule(graph, subject) {
|
|
7488
|
+
const exact = graph.modules.find((module) => module.name === subject);
|
|
7489
|
+
if (exact)
|
|
7490
|
+
return exact;
|
|
7491
|
+
const matches = graph.modules.filter((module) => module.className === subject || module.providers.some((provider) => provider.token === subject || provider.useClass === subject) || module.controllers.some((controller) => controller.className === subject) || module.commands.some((command) => command.name === subject || command.className === subject) || (module.jobs ?? []).some((job) => job.name === subject || job.className === subject || job.serviceKey === subject) || module.queries.some((query) => query.name === subject || query.className === subject));
|
|
7492
|
+
if (matches.length === 1)
|
|
7493
|
+
return matches[0];
|
|
7494
|
+
if (matches.length > 1) {
|
|
7495
|
+
throw new Error(`Ambiguous context target "${subject}". Select a module name: ${matches.map((module) => module.name).sort().join(", ")}`);
|
|
7496
|
+
}
|
|
7497
|
+
throw new Error(`No context target named "${subject}". Select a module name: ${graph.modules.map((module) => module.name).sort().join(", ") || "(none)"}`);
|
|
7498
|
+
}
|
|
7310
7499
|
function allAspects(module) {
|
|
7311
7500
|
return [
|
|
7312
7501
|
...module.aspects ?? [],
|
|
@@ -12310,9 +12499,9 @@ function compilerVersion() {
|
|
|
12310
12499
|
}
|
|
12311
12500
|
function migrationDependencies() {
|
|
12312
12501
|
return {
|
|
12313
|
-
"@supacloud/app": "0.
|
|
12502
|
+
"@supacloud/app": "0.17.0",
|
|
12314
12503
|
"@supacloud/compiler": compilerVersion(),
|
|
12315
|
-
"@supacloud/elysia": "0.
|
|
12504
|
+
"@supacloud/elysia": "0.19.0",
|
|
12316
12505
|
elysia: "1.4.30",
|
|
12317
12506
|
typescript: "7.0.2"
|
|
12318
12507
|
};
|
|
@@ -12393,6 +12582,8 @@ function isDefineRouteContractCall(node, checker) {
|
|
|
12393
12582
|
return resolveSymbol(checker.getSymbolAtLocation(node.expression), checker)?.name === "defineRouteContract";
|
|
12394
12583
|
}
|
|
12395
12584
|
function isRouteDecoratorCall(node, checker) {
|
|
12585
|
+
if (node.arguments.length < 2)
|
|
12586
|
+
return false;
|
|
12396
12587
|
const name = node.expression.getText(node.getSourceFile());
|
|
12397
12588
|
if (ts12.isIdentifier(node.expression) && ROUTE_DECORATORS2.has(node.expression.text))
|
|
12398
12589
|
return true;
|
|
@@ -12677,7 +12868,7 @@ async function migrateProject(options) {
|
|
|
12677
12868
|
const before = sourceByPath.get(absoluteFile);
|
|
12678
12869
|
if (before === undefined)
|
|
12679
12870
|
continue;
|
|
12680
|
-
const result = projectResults
|
|
12871
|
+
const result = projectResults ? projectResults.results.get(absoluteFile) ?? { changed: false, content: before, replacements: 0, issues: [] } : migration.apply(before, file);
|
|
12681
12872
|
sourceByPath.set(absoluteFile, result.content);
|
|
12682
12873
|
if (result.changed && result.issues.length === 0) {
|
|
12683
12874
|
pendingWrites.set(absoluteFile, result.content);
|
|
@@ -13444,7 +13635,7 @@ Usage:
|
|
|
13444
13635
|
supacloud-compiler dev [rootDir] [options]
|
|
13445
13636
|
supacloud-compiler graph [rootDir] [options]
|
|
13446
13637
|
supacloud-compiler explain <name> [rootDir] [options]
|
|
13447
|
-
supacloud-compiler context <
|
|
13638
|
+
supacloud-compiler context <name> [rootDir] [options]
|
|
13448
13639
|
supacloud-compiler doctor [rootDir] [options]
|
|
13449
13640
|
supacloud-compiler migrate [rootDir] [options]
|
|
13450
13641
|
supacloud-compiler migration-assess [rootDir] [options]
|
|
@@ -13959,7 +14150,7 @@ Source change detected; compiling...`);
|
|
|
13959
14150
|
}
|
|
13960
14151
|
} else if (command === "context") {
|
|
13961
14152
|
if (!query) {
|
|
13962
|
-
console.error("Error: context requires a module name");
|
|
14153
|
+
console.error("Error: context requires a module or owned symbol name");
|
|
13963
14154
|
process.exit(1);
|
|
13964
14155
|
}
|
|
13965
14156
|
try {
|