@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/index.js
CHANGED
|
@@ -128,7 +128,9 @@ function buildContractManifest(graph, artifacts) {
|
|
|
128
128
|
...route.responses === undefined ? {} : {
|
|
129
129
|
responseSchemas: Object.fromEntries(Object.entries(route.responses).sort(([left], [right]) => left.localeCompare(right)))
|
|
130
130
|
},
|
|
131
|
-
...route.contract?.evidence === undefined ? {} : { evidence: route.contract.evidence }
|
|
131
|
+
...route.contract?.evidence === undefined ? {} : { evidence: route.contract.evidence },
|
|
132
|
+
...route.parse === undefined ? {} : { parse: route.parse },
|
|
133
|
+
...route.allowDeleteBody === undefined ? {} : { allowDeleteBody: route.allowDeleteBody }
|
|
132
134
|
});
|
|
133
135
|
}
|
|
134
136
|
}
|
|
@@ -217,7 +219,7 @@ function renderApplication(graph, options) {
|
|
|
217
219
|
providers: mod.providers.filter((p) => p.providedIn !== "root" || p.multi || referencedTokens.has(p.token) || p.exported)
|
|
218
220
|
}));
|
|
219
221
|
}
|
|
220
|
-
const imports = new ImportManager;
|
|
222
|
+
const imports = new ImportManager(new Set(["CompiledApplicationModule"]));
|
|
221
223
|
const factorySections = [];
|
|
222
224
|
const descriptorEntries = [];
|
|
223
225
|
for (const module of modules) {
|
|
@@ -234,7 +236,13 @@ function renderApplication(graph, options) {
|
|
|
234
236
|
"",
|
|
235
237
|
TYPE_GUARDS,
|
|
236
238
|
"",
|
|
237
|
-
|
|
239
|
+
...modules.length > 0 ? [
|
|
240
|
+
'export type CompiledApplicationModule = Omit<CompiledModule, "name" | "createServices"> & (',
|
|
241
|
+
...modules.map((module) => ` | { name: ${JSON.stringify(module.name)}; createServices: typeof create${pascalName(module.name)}Services }`),
|
|
242
|
+
");"
|
|
243
|
+
] : ["export type CompiledApplicationModule = CompiledModule;"],
|
|
244
|
+
"",
|
|
245
|
+
"export function createCompiledModules(): CompiledApplicationModule[] {",
|
|
238
246
|
" return [",
|
|
239
247
|
...descriptorEntries.map((entry) => indent(entry, 4) + ","),
|
|
240
248
|
" ];",
|
|
@@ -315,7 +323,9 @@ function renderApplication(graph, options) {
|
|
|
315
323
|
};
|
|
316
324
|
}
|
|
317
325
|
async function generateApplication(graph, options) {
|
|
318
|
-
|
|
326
|
+
return writeRenderedApplication(renderApplication(graph, options), options);
|
|
327
|
+
}
|
|
328
|
+
async function writeRenderedApplication(rendered, options) {
|
|
319
329
|
await mkdir(options.outDir, { recursive: true });
|
|
320
330
|
const applicationPath = join2(options.outDir, "application.ts");
|
|
321
331
|
const manifestPath = join2(options.outDir, "app.manifest.json");
|
|
@@ -402,8 +412,12 @@ function indent(text, spaces) {
|
|
|
402
412
|
}
|
|
403
413
|
|
|
404
414
|
class ImportManager {
|
|
415
|
+
reserved;
|
|
405
416
|
entries = new Map;
|
|
406
417
|
lookup = new Map;
|
|
418
|
+
constructor(reserved = new Set) {
|
|
419
|
+
this.reserved = reserved;
|
|
420
|
+
}
|
|
407
421
|
get size() {
|
|
408
422
|
return this.entries.size;
|
|
409
423
|
}
|
|
@@ -418,7 +432,7 @@ class ImportManager {
|
|
|
418
432
|
return existing;
|
|
419
433
|
let local = exported;
|
|
420
434
|
let counter = 2;
|
|
421
|
-
while (this.entries.has(local)) {
|
|
435
|
+
while (this.entries.has(local) || this.reserved.has(local)) {
|
|
422
436
|
local = `${exported}${counter}`;
|
|
423
437
|
counter += 1;
|
|
424
438
|
}
|
|
@@ -475,7 +489,7 @@ class ModuleGenerator {
|
|
|
475
489
|
renderDescriptor() {
|
|
476
490
|
const lines = [
|
|
477
491
|
`{`,
|
|
478
|
-
` name: ${JSON.stringify(this.module.name)},`,
|
|
492
|
+
` name: ${JSON.stringify(this.module.name)} as const,`,
|
|
479
493
|
` createServices: create${this.pascal}Services,`
|
|
480
494
|
];
|
|
481
495
|
if (this.hasFactoryContent("request")) {
|
|
@@ -614,6 +628,10 @@ class ModuleGenerator {
|
|
|
614
628
|
if (route.contract !== undefined) {
|
|
615
629
|
fields.push(`contract: ${JSON.stringify(route.contract)}`);
|
|
616
630
|
}
|
|
631
|
+
if (route.parse === "none")
|
|
632
|
+
fields.push('parse: "none"');
|
|
633
|
+
if (route.allowDeleteBody === true)
|
|
634
|
+
fields.push("allowDeleteBody: true");
|
|
617
635
|
if (route.schemaKinds !== undefined && Object.keys(route.schemaKinds).length > 0) {
|
|
618
636
|
fields.push(`schemaKinds: ${JSON.stringify(route.schemaKinds)}`);
|
|
619
637
|
}
|
|
@@ -713,7 +731,7 @@ ${indent(item, 2)}`).join(",")}
|
|
|
713
731
|
`function create${this.pascal}Services(`,
|
|
714
732
|
` deps: Record<string, unknown>,`,
|
|
715
733
|
` imported: Record<string, Record<string, unknown>>,`,
|
|
716
|
-
`)
|
|
734
|
+
`) {`,
|
|
717
735
|
indent(this.renderFactoryBody("services"), 2),
|
|
718
736
|
`}`
|
|
719
737
|
].join(`
|
|
@@ -810,7 +828,7 @@ ${indent(item, 2)}`).join(",")}
|
|
|
810
828
|
`);
|
|
811
829
|
}
|
|
812
830
|
const entries = [...returns.entries()].map(([key, expr]) => key === expr ? key : `${key}: ${expr}`);
|
|
813
|
-
lines.push(`return { ${entries.join(", ")} };`);
|
|
831
|
+
lines.push(`return retainPlatformDependencies({ ${entries.join(", ")} }, deps);`);
|
|
814
832
|
return lines.join(`
|
|
815
833
|
`);
|
|
816
834
|
}
|
|
@@ -911,6 +929,9 @@ ${indent(item, 2)}`).join(",")}
|
|
|
911
929
|
return this.depExpr(own.useExisting ?? token, kind, options);
|
|
912
930
|
}
|
|
913
931
|
}
|
|
932
|
+
if (own && factoryOfScope(own.scope) === "services" && kind !== "services" && !isSkipSelf && !isSelf) {
|
|
933
|
+
return `services.${camelName(token)}`;
|
|
934
|
+
}
|
|
914
935
|
if (isSelf) {
|
|
915
936
|
return isOptional ? "undefined" : `services.${camelName(token)}`;
|
|
916
937
|
}
|
|
@@ -928,13 +949,14 @@ ${indent(item, 2)}`).join(",")}
|
|
|
928
949
|
return `imported.${mod.name}.${camelName(token)}`;
|
|
929
950
|
}
|
|
930
951
|
}
|
|
931
|
-
if (isOptional && !this.graph.externalTokens.includes(token)) {
|
|
932
|
-
return "undefined";
|
|
933
|
-
}
|
|
934
952
|
if (isSelf)
|
|
935
953
|
return isOptional ? "undefined" : `services.${camelName(token)}`;
|
|
936
954
|
if (kind === "services")
|
|
937
955
|
return isOptional ? `(deps.${camelName(token)} ?? undefined)` : `deps.${camelName(token)}`;
|
|
956
|
+
if (!own || isSkipSelf) {
|
|
957
|
+
const external = `platformDependencies.get(services)?.${camelName(token)}`;
|
|
958
|
+
return isOptional ? `(${external} ?? undefined)` : external;
|
|
959
|
+
}
|
|
938
960
|
return isOptional ? `(services.${camelName(token)} ?? undefined)` : `services.${camelName(token)}`;
|
|
939
961
|
}
|
|
940
962
|
}
|
|
@@ -962,7 +984,7 @@ function orderProviders(providers) {
|
|
|
962
984
|
function renderClient(graph, options) {
|
|
963
985
|
const rootDir = options?.rootDir ?? process.cwd();
|
|
964
986
|
const outDir = options?.outDir ?? process.cwd();
|
|
965
|
-
const imports = new ImportManager;
|
|
987
|
+
const imports = new ImportManager(new Set(["ApiClientError", "ApiClientErrorCode", "ApiClientErrorDetails"]));
|
|
966
988
|
const controllerEntries = [];
|
|
967
989
|
const routeTypes = [];
|
|
968
990
|
const schemaEntries = [];
|
|
@@ -1020,11 +1042,14 @@ function renderClient(graph, options) {
|
|
|
1020
1042
|
}).filter((entry) => entry.length === 2));
|
|
1021
1043
|
if (Object.keys(responseSchemas).length > 0)
|
|
1022
1044
|
usesValue = true;
|
|
1023
|
-
const paramNames = [...new Set(
|
|
1045
|
+
const paramNames = [...new Set([
|
|
1046
|
+
...(fullPath.match(/:([a-zA-Z0-9_]+)/g) ?? []).map((p) => p.slice(1)),
|
|
1047
|
+
...fullPath.endsWith("/*") ? ["*"] : []
|
|
1048
|
+
])];
|
|
1024
1049
|
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>";
|
|
1025
1050
|
const paramsType = paramsSchema ? `Static<typeof ${paramsSchema.local}>${paramNames.length > 0 ? ` & ${pathParamsType}` : ""}` : pathParamsType;
|
|
1026
1051
|
const queryType = querySchema ? `Static<typeof ${querySchema.local}>` : "Record<string, unknown>";
|
|
1027
|
-
const bodyType = bodySchema ? `Static<typeof ${bodySchema.local}>` : "unknown";
|
|
1052
|
+
const bodyType = route.parse === "none" ? "BodyInit" : bodySchema ? `Static<typeof ${bodySchema.local}>` : "unknown";
|
|
1028
1053
|
const headersType = headersSchema ? `Static<typeof ${headersSchema.local}>` : "Record<string, string>";
|
|
1029
1054
|
const cookieType = cookieSchema ? `Static<typeof ${cookieSchema.local}>` : "Record<string, string | number | boolean>";
|
|
1030
1055
|
const responseTypes = [
|
|
@@ -1061,7 +1086,9 @@ function renderClient(graph, options) {
|
|
|
1061
1086
|
["queryTransforms", route.queryTransforms],
|
|
1062
1087
|
["queryDefaults", route.queryDefaults],
|
|
1063
1088
|
["title", route.title],
|
|
1064
|
-
["data", route.data]
|
|
1089
|
+
["data", route.data],
|
|
1090
|
+
["parse", route.parse],
|
|
1091
|
+
["allowDeleteBody", route.allowDeleteBody]
|
|
1065
1092
|
]) {
|
|
1066
1093
|
if (value !== undefined)
|
|
1067
1094
|
routeFields.push(`${key}: ${JSON.stringify(value)}`);
|
|
@@ -1095,7 +1122,7 @@ function renderClient(graph, options) {
|
|
|
1095
1122
|
const responseSchemaArgument = responseSchemaEntries.length > 0 ? `{ ${responseSchemaEntries.map(([status, local]) => `${JSON.stringify(status)}: ${local}`).join(", ")} }` : "undefined";
|
|
1096
1123
|
const responseKindArgument = responseKind === undefined ? "undefined" : JSON.stringify(responseKind);
|
|
1097
1124
|
routeMethods.push(`
|
|
1098
|
-
${route.handler}: makeRoute<${requestTypeName}, ${responseType === "never" ? "never" : responseTypeName}>(${JSON.stringify(route.method)}, ${JSON.stringify(fullPath)}, ${responseSchemaArgument}, ${responseKindArgument}),`);
|
|
1125
|
+
${route.handler}: makeRoute<${requestTypeName}, ${responseType === "never" ? "never" : responseTypeName}>(${JSON.stringify(route.method)}, ${JSON.stringify(fullPath)}, ${responseSchemaArgument}, ${responseKindArgument}, ${route.parse === "none"}),`);
|
|
1099
1126
|
}
|
|
1100
1127
|
controllerEntries.push(`
|
|
1101
1128
|
${controllerKey}: {${routeMethods.join("")}
|
|
@@ -1126,6 +1153,40 @@ function renderClient(graph, options) {
|
|
|
1126
1153
|
"",
|
|
1127
1154
|
"export type ResponseDecoder<T> = (value: unknown) => T;",
|
|
1128
1155
|
"",
|
|
1156
|
+
'export type ApiClientErrorCode = "API_HTTP_ERROR" | "API_RESPONSE_INVALID" | "API_RESPONSE_UNDECLARED";',
|
|
1157
|
+
"",
|
|
1158
|
+
"export interface ApiClientErrorDetails {",
|
|
1159
|
+
" code: ApiClientErrorCode;",
|
|
1160
|
+
" method: string;",
|
|
1161
|
+
" path: string;",
|
|
1162
|
+
" status: number;",
|
|
1163
|
+
" requestId?: string | null;",
|
|
1164
|
+
" response?: Response;",
|
|
1165
|
+
"}",
|
|
1166
|
+
"",
|
|
1167
|
+
"/** Transport/contract metadata, not proof that a failed write rolled back. */",
|
|
1168
|
+
"export class ApiClientError extends Error {",
|
|
1169
|
+
" readonly code: ApiClientErrorCode;",
|
|
1170
|
+
" readonly method: string;",
|
|
1171
|
+
" readonly path: string;",
|
|
1172
|
+
" readonly status: number;",
|
|
1173
|
+
" readonly requestId: string | undefined;",
|
|
1174
|
+
" /** Undeclared HTTP response for explicit inspection; excluded from JSON serialization. */",
|
|
1175
|
+
" readonly response: Response | undefined;",
|
|
1176
|
+
" constructor(message: string, details: ApiClientErrorDetails) {",
|
|
1177
|
+
" super(message);",
|
|
1178
|
+
' this.name = "ApiClientError";',
|
|
1179
|
+
" const { code, method, path, status, requestId, response } = details;",
|
|
1180
|
+
" this.code = code;",
|
|
1181
|
+
" this.method = method;",
|
|
1182
|
+
" this.path = path;",
|
|
1183
|
+
" this.status = status;",
|
|
1184
|
+
" this.requestId = requestId && /^[A-Za-z0-9._:-]{1,256}$/.test(requestId) ? requestId : undefined;",
|
|
1185
|
+
" this.response = response;",
|
|
1186
|
+
' Object.defineProperty(this, "response", { enumerable: false });',
|
|
1187
|
+
" }",
|
|
1188
|
+
"}",
|
|
1189
|
+
"",
|
|
1129
1190
|
"export type RouteMethod<",
|
|
1130
1191
|
" Options extends ClientRequestOptions = ClientRequestOptions,",
|
|
1131
1192
|
" Result = never,",
|
|
@@ -1179,6 +1240,13 @@ function renderClient(graph, options) {
|
|
|
1179
1240
|
' if (typeof value !== "string" && typeof value !== "number") throw new TypeError(`Missing route parameter: ${key}`);',
|
|
1180
1241
|
" return encodeURIComponent(String(value));",
|
|
1181
1242
|
" });",
|
|
1243
|
+
' if (path.endsWith("/*")) {',
|
|
1244
|
+
' const wildcard = params && Object.hasOwn(params, "*") ? params["*"] : undefined;',
|
|
1245
|
+
' if (typeof wildcard !== "string" && typeof wildcard !== "number") throw new TypeError("Missing route parameter: *");',
|
|
1246
|
+
' const segments = String(wildcard).split("/");',
|
|
1247
|
+
' if (segments.some(segment => segment === "." || segment === "..")) throw new TypeError("Wildcard dot segments are not supported by Fetch URLs");',
|
|
1248
|
+
' url = url.slice(0, -1) + segments.map(encodeURIComponent).join("/");',
|
|
1249
|
+
" }",
|
|
1182
1250
|
" if (query) {",
|
|
1183
1251
|
" const searchParams = new URLSearchParams();",
|
|
1184
1252
|
" for (const [k, v] of Object.entries(query)) {",
|
|
@@ -1365,6 +1433,10 @@ function renderClient(graph, options) {
|
|
|
1365
1433
|
"export function createApiClient(config: ApiClientConfig = {}) {",
|
|
1366
1434
|
" const fetcher = config.fetch ?? globalThis.fetch.bind(globalThis);",
|
|
1367
1435
|
' const baseUrl = (config.baseUrl ?? "").replace(/\\/+$/, "");',
|
|
1436
|
+
" function rawBody(value: unknown): BodyInit {",
|
|
1437
|
+
' 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;',
|
|
1438
|
+
' throw new TypeError("Raw routes require a BodyInit value; JSON encoding is not implicit");',
|
|
1439
|
+
" }",
|
|
1368
1440
|
"",
|
|
1369
1441
|
" async function request<T>(",
|
|
1370
1442
|
" method: string,",
|
|
@@ -1373,6 +1445,7 @@ function renderClient(graph, options) {
|
|
|
1373
1445
|
" decode: ResponseDecoder<T>,",
|
|
1374
1446
|
` responseSchemas?: Readonly<Record<string, ${responseSchemaType}>>,`,
|
|
1375
1447
|
" responseKind?: string,",
|
|
1448
|
+
" raw?: boolean,",
|
|
1376
1449
|
" ): Promise<T>;",
|
|
1377
1450
|
" async function request<T>(",
|
|
1378
1451
|
" method: string,",
|
|
@@ -1381,6 +1454,7 @@ function renderClient(graph, options) {
|
|
|
1381
1454
|
" decode?: ResponseDecoder<T>,",
|
|
1382
1455
|
` responseSchemas?: Readonly<Record<string, ${responseSchemaType}>>,`,
|
|
1383
1456
|
" responseKind?: string,",
|
|
1457
|
+
" raw?: boolean,",
|
|
1384
1458
|
" ): Promise<T | unknown>;",
|
|
1385
1459
|
" async function request(",
|
|
1386
1460
|
" method: string,",
|
|
@@ -1394,11 +1468,12 @@ function renderClient(graph, options) {
|
|
|
1394
1468
|
" decode?: ResponseDecoder<T>,",
|
|
1395
1469
|
` responseSchemas?: Readonly<Record<string, ${responseSchemaType}>>,`,
|
|
1396
1470
|
" responseKind?: string,",
|
|
1471
|
+
" raw = false,",
|
|
1397
1472
|
" ): Promise<T | unknown> {",
|
|
1398
1473
|
" const url = `${baseUrl}${buildRouteUrl(path, options.params, options.query)}`;",
|
|
1399
1474
|
' const customHeaders = typeof config.headers === "function" ? await config.headers() : config.headers;',
|
|
1400
1475
|
" const headers: Record<string, string> = {",
|
|
1401
|
-
' "content-type": "application/json",',
|
|
1476
|
+
' ...(raw ? {} : { "content-type": "application/json" }),',
|
|
1402
1477
|
" ...customHeaders,",
|
|
1403
1478
|
" ...options.headers,",
|
|
1404
1479
|
" };",
|
|
@@ -1417,21 +1492,27 @@ function renderClient(graph, options) {
|
|
|
1417
1492
|
" return fetcher(reqPayload.url, {",
|
|
1418
1493
|
" method: reqPayload.method,",
|
|
1419
1494
|
" headers: reqPayload.headers,",
|
|
1420
|
-
" ...(reqPayload.body === undefined ? {} : { body: JSON.stringify(reqPayload.body) }),",
|
|
1495
|
+
" ...(reqPayload.body === undefined ? {} : { body: raw ? rawBody(reqPayload.body) : JSON.stringify(reqPayload.body) }),",
|
|
1496
|
+
' ...(raw && reqPayload.body instanceof ReadableStream ? { duplex: "half" } : {}),',
|
|
1421
1497
|
" });",
|
|
1422
1498
|
" };",
|
|
1423
1499
|
" const response = await executeChain(0, { method, url, headers, body: options.body });",
|
|
1424
1500
|
" const declaredSchema = responseSchemas ? selectResponseSchema(response.status, responseSchemas) : undefined;",
|
|
1501
|
+
" const failure = (code: ApiClientErrorCode, message: string) => new ApiClientError(message, {",
|
|
1502
|
+
' code, method, path, status: response.status, requestId: response.headers.get("x-request-id"),',
|
|
1503
|
+
' ...(code === "API_HTTP_ERROR" ? { response } : {}),',
|
|
1504
|
+
" });",
|
|
1425
1505
|
" if (!response.ok && declaredSchema === undefined) {",
|
|
1426
|
-
|
|
1427
|
-
" throw new Error(`API request failed: ${method} ${path} -> ${response.status} ${errBody}`);",
|
|
1506
|
+
' throw failure("API_HTTP_ERROR", `API request failed: ${method} ${path} -> ${response.status}`);',
|
|
1428
1507
|
" }",
|
|
1429
1508
|
' const contentType = response.headers?.get("content-type") ?? "";',
|
|
1430
1509
|
" let value: unknown;",
|
|
1431
1510
|
" if ([204, 205, 304].includes(response.status)) {",
|
|
1432
1511
|
" value = undefined;",
|
|
1433
1512
|
' } else if (contentType.includes("application/json") || contentType.includes("+json")) {',
|
|
1434
|
-
"
|
|
1513
|
+
" const responseText = await response.text();",
|
|
1514
|
+
" try { value = JSON.parse(responseText); }",
|
|
1515
|
+
' catch { throw failure("API_RESPONSE_INVALID", "Response is not valid JSON"); }',
|
|
1435
1516
|
' } else if (responseKind === "binary" || contentType.includes("application/octet-stream")) {',
|
|
1436
1517
|
" value = await response.arrayBuffer();",
|
|
1437
1518
|
' } else if (responseKind === "stream") {',
|
|
@@ -1442,20 +1523,24 @@ function renderClient(graph, options) {
|
|
|
1442
1523
|
" if (!responseSchemas) return decode ? decode(value) : value;",
|
|
1443
1524
|
" if (declaredSchema === undefined) {",
|
|
1444
1525
|
' const transportSuccess = response.ok && (responseKind === "binary" || responseKind === "stream");',
|
|
1445
|
-
|
|
1526
|
+
' if (!transportSuccess) throw failure("API_RESPONSE_UNDECLARED", `No response schema declared for HTTP ${response.status}`);',
|
|
1446
1527
|
" return decode ? decode(value) : value;",
|
|
1447
1528
|
" }",
|
|
1448
|
-
...usesValue ? [
|
|
1529
|
+
...usesValue ? [
|
|
1530
|
+
" let checked: unknown;",
|
|
1531
|
+
" try { checked = decodeResponseSchema(value, response.status, responseSchemas, { normalize: config.normalize ?? true }); }",
|
|
1532
|
+
' catch { throw failure("API_RESPONSE_INVALID", "Response does not match schema"); }'
|
|
1533
|
+
] : [" const checked = value;"],
|
|
1449
1534
|
" return decode ? decode(checked) : checked;",
|
|
1450
1535
|
" }",
|
|
1451
1536
|
"",
|
|
1452
|
-
` function makeRoute<Options extends ClientRequestOptions, Result = never>(method: string, path: string, responseSchemas?: Readonly<Record<string, ${responseSchemaType}>>, responseKind?: string): RouteMethod<Options, Result> {`,
|
|
1537
|
+
` function makeRoute<Options extends ClientRequestOptions, Result = never>(method: string, path: string, responseSchemas?: Readonly<Record<string, ${responseSchemaType}>>, responseKind?: string, raw = false): RouteMethod<Options, Result> {`,
|
|
1453
1538
|
" function route<T>(options: Options, decode: ResponseDecoder<T>): Promise<T>;",
|
|
1454
1539
|
" function route(options: Options): Promise<Result>;",
|
|
1455
1540
|
" function route(options?: Options): Promise<unknown>;",
|
|
1456
1541
|
" function route<T>(options?: Options, decode?: ResponseDecoder<T>): Promise<T | unknown> {",
|
|
1457
1542
|
" const requestOptions = options ?? {};",
|
|
1458
|
-
" return request(method, path, requestOptions, decode, responseSchemas, responseKind);",
|
|
1543
|
+
" return request(method, path, requestOptions, decode, responseSchemas, responseKind, raw);",
|
|
1459
1544
|
" }",
|
|
1460
1545
|
" return route as RouteMethod<Options, Result>;",
|
|
1461
1546
|
" }",
|
|
@@ -1512,11 +1597,15 @@ function renderOpenApi(graph, options) {
|
|
|
1512
1597
|
for (const module of graph.modules) {
|
|
1513
1598
|
for (const controller of module.controllers) {
|
|
1514
1599
|
for (const route of controller.routes) {
|
|
1600
|
+
const documentation = route.data?.openapi;
|
|
1601
|
+
if (documentation !== null && typeof documentation === "object" && "hide" in documentation && documentation.hide === true)
|
|
1602
|
+
continue;
|
|
1515
1603
|
const fullPath = joinRoutePaths(controller.path, route.path);
|
|
1516
1604
|
const responseEntries = normalizedResponseEntries(route.responses);
|
|
1517
1605
|
const hasResponseMap = responseEntries.length > 0;
|
|
1518
1606
|
const pathParams = [...new Set([
|
|
1519
1607
|
...(fullPath.match(/:([A-Za-z0-9_]+)/g) ?? []).map((item) => item.slice(1)),
|
|
1608
|
+
...fullPath.endsWith("/*") ? ["*"] : [],
|
|
1520
1609
|
...route.pathParams ?? []
|
|
1521
1610
|
].filter((item) => item.length > 0))];
|
|
1522
1611
|
const queryParams = [...new Set([
|
|
@@ -1549,6 +1638,8 @@ function renderOpenApi(graph, options) {
|
|
|
1549
1638
|
}).filter((entry) => entry.length === 2)) } : {},
|
|
1550
1639
|
responseContentType,
|
|
1551
1640
|
responseKind: responseKind ?? (route.nativeResponse ? "native-response" : undefined),
|
|
1641
|
+
...route.parse === undefined ? {} : { parse: route.parse },
|
|
1642
|
+
...route.allowDeleteBody === undefined ? {} : { allowDeleteBody: route.allowDeleteBody },
|
|
1552
1643
|
module: module.name,
|
|
1553
1644
|
controller: controller.className,
|
|
1554
1645
|
handler: route.handler,
|
|
@@ -1632,6 +1723,8 @@ function renderOpenApi(graph, options) {
|
|
|
1632
1723
|
" canMatch?: readonly string[];",
|
|
1633
1724
|
" canDeactivate?: readonly string[];",
|
|
1634
1725
|
" contract?: Record<string, unknown>;",
|
|
1726
|
+
' parse?: "none";',
|
|
1727
|
+
" allowDeleteBody?: true;",
|
|
1635
1728
|
" data?: Record<string, unknown>;",
|
|
1636
1729
|
" requiresAuth?: boolean;",
|
|
1637
1730
|
"};",
|
|
@@ -1829,8 +1922,15 @@ function renderOpenApi(graph, options) {
|
|
|
1829
1922
|
' return { $ref: `#/components/schemas/${encodeURIComponent(name).replaceAll("%2F", "/")}` };',
|
|
1830
1923
|
"}",
|
|
1831
1924
|
"",
|
|
1925
|
+
"function wildcardParameterName(path: string): string {",
|
|
1926
|
+
" const names = new Set([...path.matchAll(/:([A-Za-z0-9_]+)/g)].map(match => match[1]));",
|
|
1927
|
+
' let name = "wildcard";',
|
|
1928
|
+
' while (names.has(name)) name += "_";',
|
|
1929
|
+
" return name;",
|
|
1930
|
+
"}",
|
|
1931
|
+
"",
|
|
1832
1932
|
"function openApiPath(path: string): string {",
|
|
1833
|
-
' return path.replace(/:([A-Za-z0-9_]+)/g, "{$1}");',
|
|
1933
|
+
' return path.replace(/:([A-Za-z0-9_]+)/g, "{$1}").replace(/\\/\\*$/, `/{${wildcardParameterName(path)}}`);',
|
|
1834
1934
|
"}",
|
|
1835
1935
|
"",
|
|
1836
1936
|
"function responseHasBody(status: string, kind: string | undefined): boolean {",
|
|
@@ -1860,7 +1960,7 @@ function renderOpenApi(graph, options) {
|
|
|
1860
1960
|
" const parameters: Record<string, unknown>[] = [];",
|
|
1861
1961
|
" const paramsSchema = definition.paramsSchema ? OPENAPI_SCHEMA_REGISTRY[definition.paramsSchema as keyof typeof OPENAPI_SCHEMA_REGISTRY] : undefined;",
|
|
1862
1962
|
" for (const name of definition.pathParams) parameters.push({",
|
|
1863
|
-
' name, in: "path", required: true, schema: schemaProperty(paramsSchema, name),',
|
|
1963
|
+
' name: name === "*" ? wildcardParameterName(definition.path) : name, in: "path", required: true, schema: schemaProperty(paramsSchema, name),',
|
|
1864
1964
|
" });",
|
|
1865
1965
|
" const querySchema = definition.querySchema ? OPENAPI_SCHEMA_REGISTRY[definition.querySchema as keyof typeof OPENAPI_SCHEMA_REGISTRY] : undefined;",
|
|
1866
1966
|
" const queryNames = new Set([...(querySchema ? schemaPropertyNames(querySchema) : []), ...definition.queryParams]);",
|
|
@@ -1897,6 +1997,7 @@ function renderOpenApi(graph, options) {
|
|
|
1897
1997
|
" operationId: definition.operationId, tags: [...definition.tags], summary: definition.summary, responses,",
|
|
1898
1998
|
" ...(parameters.length === 0 ? {} : { parameters }),",
|
|
1899
1999
|
' ...(definition.bodySchema ? { requestBody: { required: !schemaAllowsUndefined(OPENAPI_SCHEMA_REGISTRY[definition.bodySchema as keyof typeof OPENAPI_SCHEMA_REGISTRY]), content: { "application/json": { schema: schemaReference(definition.bodySchema) } } } } : {}),',
|
|
2000
|
+
' ...(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" } } } } } : {}),',
|
|
1900
2001
|
" ...(definition.requiresAuth ? { security: [{ bearerAuth: [] }] } : {}),",
|
|
1901
2002
|
' "x-supacloud": {',
|
|
1902
2003
|
" module: definition.module, controller: definition.controller, handler: definition.handler,",
|
|
@@ -1906,6 +2007,8 @@ function renderOpenApi(graph, options) {
|
|
|
1906
2007
|
" ...(definition.canMatch ? { canMatch: [...definition.canMatch] } : {}),",
|
|
1907
2008
|
" ...(definition.canDeactivate ? { canDeactivate: [...definition.canDeactivate] } : {}),",
|
|
1908
2009
|
" ...(definition.contract ? { contract: definition.contract } : {}),",
|
|
2010
|
+
" ...(definition.parse ? { parse: definition.parse } : {}),",
|
|
2011
|
+
" ...(definition.allowDeleteBody ? { allowDeleteBody: true } : {}),",
|
|
1909
2012
|
" ...(definition.data ? { data: definition.data } : {}),",
|
|
1910
2013
|
" },",
|
|
1911
2014
|
" };",
|
|
@@ -2001,6 +2104,8 @@ function renderPermissions(graph) {
|
|
|
2001
2104
|
`);
|
|
2002
2105
|
}
|
|
2003
2106
|
var HEADER = "// GENERATED BY @supacloud/compiler — do not edit", INTERFACES = `export interface CompiledRoute {
|
|
2107
|
+
parse?: "none";
|
|
2108
|
+
allowDeleteBody?: true;
|
|
2004
2109
|
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
|
|
2005
2110
|
path: string;
|
|
2006
2111
|
handler: string;
|
|
@@ -2153,6 +2258,12 @@ function resolveFactoryValue(value: unknown): unknown {
|
|
|
2153
2258
|
}
|
|
2154
2259
|
|
|
2155
2260
|
const scopeDestructions = new WeakMap<object, Promise<void>>();
|
|
2261
|
+
const platformDependencies = new WeakMap<object, Record<string, unknown>>();
|
|
2262
|
+
|
|
2263
|
+
function retainPlatformDependencies<T extends Record<string, unknown>>(services: T, deps: Record<string, unknown>): T {
|
|
2264
|
+
platformDependencies.set(services, deps);
|
|
2265
|
+
return services;
|
|
2266
|
+
}
|
|
2156
2267
|
|
|
2157
2268
|
function destroyScopeInstances(
|
|
2158
2269
|
scope: Record<string, unknown>,
|
|
@@ -3611,6 +3722,7 @@ var COMPILER_DIAGNOSTIC_CODES = {
|
|
|
3611
3722
|
};
|
|
3612
3723
|
function validateGraph(graph, options = false) {
|
|
3613
3724
|
const strict = typeof options === "boolean" ? options : options.strict ?? false;
|
|
3725
|
+
const generateOpenApi = typeof options === "object" && options.generateOpenApi === true;
|
|
3614
3726
|
const diagnostics = [];
|
|
3615
3727
|
for (const module of graph.modules) {
|
|
3616
3728
|
for (const owner of [...module.providers, ...module.controllers]) {
|
|
@@ -3746,7 +3858,7 @@ function validateGraph(graph, options = false) {
|
|
|
3746
3858
|
}
|
|
3747
3859
|
const fullPath = joinRoutePaths2(controller.path, route.path);
|
|
3748
3860
|
const rawFullPath = joinRawRoutePaths(controller.path, route.path);
|
|
3749
|
-
const key = `${route.method} ${fullPath}`;
|
|
3861
|
+
const key = `${route.method} ${generateOpenApi ? fullPath.replace(/:[A-Za-z0-9_]+|\*$/g, "{}") : fullPath}`;
|
|
3750
3862
|
const openApiMatch = route.path.match(/\{([a-zA-Z0-9_]+)\}/);
|
|
3751
3863
|
if (openApiMatch) {
|
|
3752
3864
|
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]}'.`);
|
|
@@ -3846,7 +3958,13 @@ function validateGraph(graph, options = false) {
|
|
|
3846
3958
|
}
|
|
3847
3959
|
}
|
|
3848
3960
|
}
|
|
3849
|
-
if (
|
|
3961
|
+
if (route.allowDeleteBody && (route.method !== "DELETE" || !route.body)) {
|
|
3962
|
+
error("invalid-delete-body-opt-in", `Route ${controller.className}.${route.handler} allowDeleteBody requires DELETE and an explicit body schema.`, controller.file);
|
|
3963
|
+
}
|
|
3964
|
+
if (route.parse === "none" && (route.body || route.hasBodyBinding || route.contract?.body !== "domain" || !route.contract.evidence?.trim())) {
|
|
3965
|
+
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);
|
|
3966
|
+
}
|
|
3967
|
+
if ((route.method === "GET" || route.method === "HEAD" || route.method === "OPTIONS" || route.method === "DELETE" && !route.allowDeleteBody) && (route.hasBodyBinding || route.body)) {
|
|
3850
3968
|
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().`, {
|
|
3851
3969
|
type: "remove_route_body_binding",
|
|
3852
3970
|
targetFile: controller.file,
|
|
@@ -4558,6 +4676,7 @@ async function analyzeProject(rootDir, include, cache, changedPaths) {
|
|
|
4558
4676
|
tokensByName: new Map,
|
|
4559
4677
|
classesByName: new Map,
|
|
4560
4678
|
variablesByName: new Map,
|
|
4679
|
+
moduleHandlerFiles: new Map,
|
|
4561
4680
|
diagnostics: []
|
|
4562
4681
|
};
|
|
4563
4682
|
const nativeTraitFiles = new Map;
|
|
@@ -4648,10 +4767,11 @@ async function analyzeProject(rootDir, include, cache, changedPaths) {
|
|
|
4648
4767
|
const hasChangedFile = entry.ownedFiles.some((f) => changedFiles.has(f));
|
|
4649
4768
|
const moduleFileExists = currentFileHashes.has(entry.module.file);
|
|
4650
4769
|
const isAffectedByDep = affectedModuleNames.has(modName);
|
|
4651
|
-
if (!hasChangedFile && !isAffectedByDep && moduleFileExists) {
|
|
4770
|
+
if (!hasChangedFile && !isAffectedByDep && moduleFileExists && entry.handlerFiles !== undefined) {
|
|
4652
4771
|
modulesToKeep.set(modName, entry);
|
|
4653
4772
|
reusedModules.push(modName);
|
|
4654
4773
|
finalModules.push(entry.module);
|
|
4774
|
+
ctx.moduleHandlerFiles.set(modName, entry.handlerFiles);
|
|
4655
4775
|
if (entry.diagnostics)
|
|
4656
4776
|
finalDiagnostics.push(...entry.diagnostics);
|
|
4657
4777
|
}
|
|
@@ -4671,6 +4791,7 @@ async function analyzeProject(rootDir, include, cache, changedPaths) {
|
|
|
4671
4791
|
}
|
|
4672
4792
|
cache.modules.set(parsed.name, {
|
|
4673
4793
|
module: parsed,
|
|
4794
|
+
handlerFiles: ctx.moduleHandlerFiles.get(parsed.name) ?? [],
|
|
4674
4795
|
ownedFiles: [...ownedFiles],
|
|
4675
4796
|
fileHashes,
|
|
4676
4797
|
diagnostics: moduleDiagnostics
|
|
@@ -4839,6 +4960,7 @@ async function analyzeProject(rootDir, include, cache, changedPaths) {
|
|
|
4839
4960
|
externalTokens,
|
|
4840
4961
|
diagnostics: ctx.diagnostics.map(withDiagnosticMetadata),
|
|
4841
4962
|
tokenNames,
|
|
4963
|
+
moduleHandlerFiles: Object.fromEntries(ctx.moduleHandlerFiles),
|
|
4842
4964
|
...cache ? { cacheStats: { reusedModules, reanalyzedModules } } : {}
|
|
4843
4965
|
};
|
|
4844
4966
|
}
|
|
@@ -5051,6 +5173,7 @@ function parseModule(candidate, nameByNode, ctx) {
|
|
|
5051
5173
|
arrayProp(options, "commands").forEach(collectHandler);
|
|
5052
5174
|
arrayProp(options, "jobs").forEach(collectHandler);
|
|
5053
5175
|
arrayProp(options, "queries").forEach(collectHandler);
|
|
5176
|
+
ctx.moduleHandlerFiles.set(name, [...new Set([...handlerClasses].map((cls) => sourcePath(ctx.rootDir, cls.getSourceFile().fileName)))].sort());
|
|
5054
5177
|
const commands = [];
|
|
5055
5178
|
const jobs = [];
|
|
5056
5179
|
const queries = [];
|
|
@@ -5229,6 +5352,25 @@ function resolveStaticObjectLiteral(input, ctx, seen = new Set) {
|
|
|
5229
5352
|
}
|
|
5230
5353
|
return ts4.isObjectLiteralExpression(input) ? input : undefined;
|
|
5231
5354
|
}
|
|
5355
|
+
function routePolicyProperty(object, name, ctx, seen = new Set) {
|
|
5356
|
+
if (seen.has(object))
|
|
5357
|
+
return { unresolved: true };
|
|
5358
|
+
const ancestry = new Set(seen).add(object);
|
|
5359
|
+
let result = { unresolved: false };
|
|
5360
|
+
for (const property of object.properties) {
|
|
5361
|
+
if (ts4.isSpreadAssignment(property)) {
|
|
5362
|
+
const spread = resolveStaticObjectLiteral(property.expression, ctx);
|
|
5363
|
+
const nested = spread ? routePolicyProperty(spread, name, ctx, ancestry) : { unresolved: true };
|
|
5364
|
+
if (nested.unresolved || nested.value !== undefined)
|
|
5365
|
+
result = nested;
|
|
5366
|
+
} else if (ts4.isComputedPropertyName(property.name)) {
|
|
5367
|
+
result = { unresolved: true };
|
|
5368
|
+
} else if (propertyName(property.name) === name) {
|
|
5369
|
+
result = ts4.isPropertyAssignment(property) ? { value: property.initializer, unresolved: false } : { unresolved: true };
|
|
5370
|
+
}
|
|
5371
|
+
}
|
|
5372
|
+
return result;
|
|
5373
|
+
}
|
|
5232
5374
|
function commandModeProp(object, name) {
|
|
5233
5375
|
const value = stringLiteralProp(object, name);
|
|
5234
5376
|
return value === "required" || value === "none" ? value : undefined;
|
|
@@ -5736,10 +5878,13 @@ function parseController(input, ctx) {
|
|
|
5736
5878
|
const pathParams = [];
|
|
5737
5879
|
const paramRegex = /:([a-zA-Z0-9_]+)/g;
|
|
5738
5880
|
let match;
|
|
5739
|
-
|
|
5881
|
+
const fullPath = joinRoutePaths(path, routePath);
|
|
5882
|
+
while ((match = paramRegex.exec(fullPath)) !== null) {
|
|
5740
5883
|
if (match[1] !== undefined)
|
|
5741
5884
|
pathParams.push(match[1]);
|
|
5742
5885
|
}
|
|
5886
|
+
if (joinRoutePaths(path, routePath).endsWith("/*"))
|
|
5887
|
+
pathParams.push("*");
|
|
5743
5888
|
if (pathParams.length > 0)
|
|
5744
5889
|
route.pathParams = pathParams;
|
|
5745
5890
|
const paramBindings = [];
|
|
@@ -5890,6 +6035,40 @@ function parseController(input, ctx) {
|
|
|
5890
6035
|
const optionsArg = args[1];
|
|
5891
6036
|
const optionsObject = resolveStaticObjectLiteral(optionsArg, ctx);
|
|
5892
6037
|
if (optionsObject) {
|
|
6038
|
+
const parsePolicy = routePolicyProperty(optionsObject, "parse", ctx);
|
|
6039
|
+
const deletePolicy = routePolicyProperty(optionsObject, "allowDeleteBody", ctx);
|
|
6040
|
+
if (parsePolicy.unresolved || deletePolicy.unresolved) {
|
|
6041
|
+
ctx.diagnostics.push({
|
|
6042
|
+
severity: "error",
|
|
6043
|
+
code: "unresolved-route-body-policy",
|
|
6044
|
+
file,
|
|
6045
|
+
message: `Route ${route.handler} body policy must be statically resolvable; dynamic spreads, computed keys and shorthand policies are unsupported.`
|
|
6046
|
+
});
|
|
6047
|
+
}
|
|
6048
|
+
const parse = parsePolicy.value;
|
|
6049
|
+
if (parse) {
|
|
6050
|
+
if (ts4.isStringLiteral(parse) && parse.text === "none")
|
|
6051
|
+
route.parse = "none";
|
|
6052
|
+
else
|
|
6053
|
+
ctx.diagnostics.push({
|
|
6054
|
+
severity: "error",
|
|
6055
|
+
code: "invalid-route-parser",
|
|
6056
|
+
file,
|
|
6057
|
+
message: `Route ${route.handler} parse must be the literal "none".`
|
|
6058
|
+
});
|
|
6059
|
+
}
|
|
6060
|
+
const allowDeleteBody = deletePolicy.value;
|
|
6061
|
+
if (allowDeleteBody) {
|
|
6062
|
+
if (allowDeleteBody.kind === ts4.SyntaxKind.TrueKeyword)
|
|
6063
|
+
route.allowDeleteBody = true;
|
|
6064
|
+
else
|
|
6065
|
+
ctx.diagnostics.push({
|
|
6066
|
+
severity: "error",
|
|
6067
|
+
code: "invalid-delete-body-opt-in",
|
|
6068
|
+
file,
|
|
6069
|
+
message: `Route ${route.handler} allowDeleteBody must be the literal true.`
|
|
6070
|
+
});
|
|
6071
|
+
}
|
|
5893
6072
|
const legacyResponseExpr = getProp(optionsObject, "response");
|
|
5894
6073
|
const responsesExpr = getProp(optionsObject, "responses");
|
|
5895
6074
|
if (legacyResponseExpr !== undefined && responsesExpr !== undefined) {
|
|
@@ -7083,7 +7262,7 @@ async function compileProject(options) {
|
|
|
7083
7262
|
...options,
|
|
7084
7263
|
...options.cache ? { artifactHashes: options.cache.generatedHashes } : {}
|
|
7085
7264
|
};
|
|
7086
|
-
const written = !hasErrors || options.writeOnError === true ? await
|
|
7265
|
+
const written = !hasErrors || options.writeOnError === true ? await writeRenderedApplication(rendered, generatedOptions) : [];
|
|
7087
7266
|
if (!hasErrors) {
|
|
7088
7267
|
for (const [filename, content] of Object.entries(graphql.files)) {
|
|
7089
7268
|
const path = join4(options.outDir, filename);
|
|
@@ -11441,6 +11620,48 @@ function importSymbol(source, path, symbol) {
|
|
|
11441
11620
|
return `import { ${symbol} } from ${JSON.stringify(path)};
|
|
11442
11621
|
${source.text}`;
|
|
11443
11622
|
}
|
|
11623
|
+
// src/repair-plan.ts
|
|
11624
|
+
function createDiagnosticRepairPlan(diagnostics) {
|
|
11625
|
+
return diagnostics.flatMap((diagnostic) => {
|
|
11626
|
+
const fix = diagnostic.fix;
|
|
11627
|
+
if (!fix)
|
|
11628
|
+
return [];
|
|
11629
|
+
return [{
|
|
11630
|
+
code: diagnostic.code,
|
|
11631
|
+
...diagnostic.errorCode === undefined ? {} : { errorCode: diagnostic.errorCode },
|
|
11632
|
+
...diagnostic.file === undefined ? {} : { file: diagnostic.file },
|
|
11633
|
+
...diagnostic.line === undefined ? {} : { line: diagnostic.line },
|
|
11634
|
+
type: fix.type,
|
|
11635
|
+
targetFile: fix.targetFile,
|
|
11636
|
+
fix,
|
|
11637
|
+
...classifyFix(fix)
|
|
11638
|
+
}];
|
|
11639
|
+
});
|
|
11640
|
+
}
|
|
11641
|
+
function classifyFix(fix) {
|
|
11642
|
+
switch (fix.type) {
|
|
11643
|
+
case "set_command_mode":
|
|
11644
|
+
if (fix.value !== "required" && fix.value !== "none") {
|
|
11645
|
+
return { readiness: "input-required", reason: "Choose an explicit transaction or idempotency policy; no policy is inferred." };
|
|
11646
|
+
}
|
|
11647
|
+
break;
|
|
11648
|
+
case "add_command_permission":
|
|
11649
|
+
if (!fix.permission?.trim()) {
|
|
11650
|
+
return { readiness: "input-required", reason: "Choose an explicit business permission; privileges are never inferred." };
|
|
11651
|
+
}
|
|
11652
|
+
break;
|
|
11653
|
+
case "add_module_import":
|
|
11654
|
+
if (!fix.importPath || !fix.symbol) {
|
|
11655
|
+
return { readiness: "input-required", reason: "Supply the module import path and exported symbol before previewing." };
|
|
11656
|
+
}
|
|
11657
|
+
break;
|
|
11658
|
+
case "add_route_parameter_binding":
|
|
11659
|
+
break;
|
|
11660
|
+
default:
|
|
11661
|
+
return { readiness: "manual", reason: "The semantic fix executor does not implement this suggestion; edit and verify it manually." };
|
|
11662
|
+
}
|
|
11663
|
+
return { readiness: "preview", reason: "Preview with the semantic fix executor; source preconditions are checked before writing." };
|
|
11664
|
+
}
|
|
11444
11665
|
// src/watch.ts
|
|
11445
11666
|
import { existsSync as existsSync3, watch } from "node:fs";
|
|
11446
11667
|
import { dirname as dirname7, relative as relative9, resolve as resolve10, sep as sep9 } from "node:path";
|
|
@@ -11880,9 +12101,9 @@ function compilerVersion() {
|
|
|
11880
12101
|
}
|
|
11881
12102
|
function migrationDependencies() {
|
|
11882
12103
|
return {
|
|
11883
|
-
"@supacloud/app": "0.
|
|
12104
|
+
"@supacloud/app": "0.17.0",
|
|
11884
12105
|
"@supacloud/compiler": compilerVersion(),
|
|
11885
|
-
"@supacloud/elysia": "0.
|
|
12106
|
+
"@supacloud/elysia": "0.19.0",
|
|
11886
12107
|
elysia: "1.4.30",
|
|
11887
12108
|
typescript: "7.0.2"
|
|
11888
12109
|
};
|
|
@@ -11963,6 +12184,8 @@ function isDefineRouteContractCall(node, checker) {
|
|
|
11963
12184
|
return resolveSymbol(checker.getSymbolAtLocation(node.expression), checker)?.name === "defineRouteContract";
|
|
11964
12185
|
}
|
|
11965
12186
|
function isRouteDecoratorCall(node, checker) {
|
|
12187
|
+
if (node.arguments.length < 2)
|
|
12188
|
+
return false;
|
|
11966
12189
|
const name = node.expression.getText(node.getSourceFile());
|
|
11967
12190
|
if (ts12.isIdentifier(node.expression) && ROUTE_DECORATORS2.has(node.expression.text))
|
|
11968
12191
|
return true;
|
|
@@ -12247,7 +12470,7 @@ async function migrateProject(options) {
|
|
|
12247
12470
|
const before = sourceByPath.get(absoluteFile);
|
|
12248
12471
|
if (before === undefined)
|
|
12249
12472
|
continue;
|
|
12250
|
-
const result = projectResults
|
|
12473
|
+
const result = projectResults ? projectResults.results.get(absoluteFile) ?? { changed: false, content: before, replacements: 0, issues: [] } : migration.apply(before, file);
|
|
12251
12474
|
sourceByPath.set(absoluteFile, result.content);
|
|
12252
12475
|
if (result.changed && result.issues.length === 0) {
|
|
12253
12476
|
pendingWrites.set(absoluteFile, result.content);
|
|
@@ -13035,10 +13258,7 @@ function explainGraph(graph, subject) {
|
|
|
13035
13258
|
throw new Error(`No module, provider, or external token named "${subject}". Known names: ${known.join(", ") || "(none)"}`);
|
|
13036
13259
|
}
|
|
13037
13260
|
function createContextPack(graph, subject) {
|
|
13038
|
-
const subjectModule = graph
|
|
13039
|
-
if (!subjectModule) {
|
|
13040
|
-
throw new Error(`No module named "${subject}". Context packs require a module name.`);
|
|
13041
|
-
}
|
|
13261
|
+
const subjectModule = resolveContextModule(graph, subject);
|
|
13042
13262
|
const byName = new Map(graph.modules.map((module) => [module.name, module]));
|
|
13043
13263
|
const selected = new Set([subjectModule.name]);
|
|
13044
13264
|
const reverseDependents = new Map;
|
|
@@ -13086,6 +13306,7 @@ function createContextPack(graph, subject) {
|
|
|
13086
13306
|
module.file,
|
|
13087
13307
|
...module.providers.map((provider) => provider.file),
|
|
13088
13308
|
...module.controllers.map((controller) => controller.file),
|
|
13309
|
+
...graph.moduleHandlerFiles?.[module.name] ?? [],
|
|
13089
13310
|
...allAspects(module).flatMap((aspect) => aspect.file ? [aspect.file] : [])
|
|
13090
13311
|
]).concat(graphql ? [graphql.schema, ...queryDocuments] : []))].sort();
|
|
13091
13312
|
const referencedTokens = new Set;
|
|
@@ -13115,6 +13336,18 @@ function createContextPack(graph, subject) {
|
|
|
13115
13336
|
}
|
|
13116
13337
|
};
|
|
13117
13338
|
}
|
|
13339
|
+
function resolveContextModule(graph, subject) {
|
|
13340
|
+
const exact = graph.modules.find((module) => module.name === subject);
|
|
13341
|
+
if (exact)
|
|
13342
|
+
return exact;
|
|
13343
|
+
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));
|
|
13344
|
+
if (matches.length === 1)
|
|
13345
|
+
return matches[0];
|
|
13346
|
+
if (matches.length > 1) {
|
|
13347
|
+
throw new Error(`Ambiguous context target "${subject}". Select a module name: ${matches.map((module) => module.name).sort().join(", ")}`);
|
|
13348
|
+
}
|
|
13349
|
+
throw new Error(`No context target named "${subject}". Select a module name: ${graph.modules.map((module) => module.name).sort().join(", ") || "(none)"}`);
|
|
13350
|
+
}
|
|
13118
13351
|
function allAspects(module) {
|
|
13119
13352
|
return [
|
|
13120
13353
|
...module.aspects ?? [],
|
|
@@ -13451,6 +13684,7 @@ export {
|
|
|
13451
13684
|
createContextPack,
|
|
13452
13685
|
createDeliveryPlan,
|
|
13453
13686
|
createDependencyGraphCache,
|
|
13687
|
+
createDiagnosticRepairPlan,
|
|
13454
13688
|
createExecutionPlans,
|
|
13455
13689
|
createIncrementalCompiler,
|
|
13456
13690
|
createIncrementalProgramSession,
|