@luckystack/devkit 0.1.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/dist/index.js ADDED
@@ -0,0 +1,4606 @@
1
+ // src/typeMap/discovery.ts
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { ROOT_DIR } from "@luckystack/core";
5
+
6
+ // src/routingRules.ts
7
+ import { validatePagePath as corePagePath } from "@luckystack/core";
8
+ var DEFAULT_RULES = {
9
+ apiMarker: "_api",
10
+ syncMarker: "_sync",
11
+ apiVersionRegex: /_v(\d+)$/,
12
+ syncServerVersionRegex: /_server_v(\d+)$/,
13
+ syncClientVersionRegex: /_client_v(\d+)$/,
14
+ syncVersionRegex: /_(server|client)_v(\d+)$/,
15
+ ignore: () => false,
16
+ privateFolderPrefix: "_",
17
+ scaffoldIgnoredFolders: [
18
+ "_api",
19
+ "_sync",
20
+ "_function",
21
+ "_functions",
22
+ "_component",
23
+ "_components",
24
+ "_provider",
25
+ "_providers",
26
+ "_locale",
27
+ "_locales",
28
+ "_socket",
29
+ "_sockets",
30
+ "_shared",
31
+ "_server"
32
+ ]
33
+ };
34
+ var activeRules = DEFAULT_RULES;
35
+ var registerRoutingRules = (overrides2) => {
36
+ activeRules = { ...DEFAULT_RULES, ...overrides2 };
37
+ return activeRules;
38
+ };
39
+ var getRoutingRules = () => activeRules;
40
+ var apiMarkerSegment = () => `/${getRoutingRules().apiMarker}/`;
41
+ var syncMarkerSegment = () => `/${getRoutingRules().syncMarker}/`;
42
+ var isApiFileName = (fileName) => {
43
+ if (!fileName.endsWith(".ts")) return false;
44
+ const stem = fileName.slice(0, -3);
45
+ return getRoutingRules().apiVersionRegex.test(stem);
46
+ };
47
+ var isSyncServerFileName = (fileName) => {
48
+ if (!fileName.endsWith(".ts")) return false;
49
+ const stem = fileName.slice(0, -3);
50
+ return getRoutingRules().syncServerVersionRegex.test(stem);
51
+ };
52
+ var isSyncClientFileName = (fileName) => {
53
+ if (!fileName.endsWith(".ts")) return false;
54
+ const stem = fileName.slice(0, -3);
55
+ return getRoutingRules().syncClientVersionRegex.test(stem);
56
+ };
57
+ var isSyncFileName = (fileName) => {
58
+ if (!fileName.endsWith(".ts")) return false;
59
+ const stem = fileName.slice(0, -3);
60
+ return getRoutingRules().syncVersionRegex.test(stem);
61
+ };
62
+ var isRouteTestFile = (fileNameOrPath) => {
63
+ return fileNameOrPath.endsWith(".tests.ts");
64
+ };
65
+
66
+ // src/typeMap/discovery.ts
67
+ var toForwardSlashRelative = (absolute) => {
68
+ const rel = path.relative(ROOT_DIR, absolute);
69
+ return rel.replaceAll("\\", "/");
70
+ };
71
+ var walkFiles = (dir, matcher, results = []) => {
72
+ const { ignore } = getRoutingRules();
73
+ try {
74
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
75
+ for (const entry of entries) {
76
+ const fullPath = path.join(dir, entry.name);
77
+ const relativePath = toForwardSlashRelative(fullPath);
78
+ if (ignore(relativePath)) continue;
79
+ if (entry.isDirectory()) {
80
+ if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
81
+ walkFiles(fullPath, matcher, results);
82
+ continue;
83
+ }
84
+ if (entry.isFile() && matcher(fullPath, entry.name)) {
85
+ results.push(fullPath);
86
+ }
87
+ }
88
+ } catch (error) {
89
+ console.error(`[TypeMapDiscovery] Error scanning directory ${dir}:`, error);
90
+ }
91
+ return results;
92
+ };
93
+ var findAllApiFiles = (srcDir) => {
94
+ const apiSegment = apiMarkerSegment();
95
+ return walkFiles(srcDir, (fullPath, entryName) => {
96
+ const normalized = fullPath.replace(/\\/g, "/");
97
+ return isApiFileName(entryName) && normalized.includes(apiSegment);
98
+ });
99
+ };
100
+ var findAllSyncServerFiles = (srcDir) => {
101
+ const syncSegment = syncMarkerSegment();
102
+ return walkFiles(srcDir, (fullPath, entryName) => {
103
+ const normalized = fullPath.replace(/\\/g, "/");
104
+ return isSyncServerFileName(entryName) && normalized.includes(syncSegment);
105
+ });
106
+ };
107
+ var findAllSyncClientFiles = (srcDir) => {
108
+ const syncSegment = syncMarkerSegment();
109
+ return walkFiles(srcDir, (fullPath, entryName) => {
110
+ const normalized = fullPath.replace(/\\/g, "/");
111
+ return isSyncClientFileName(entryName) && normalized.includes(syncSegment);
112
+ });
113
+ };
114
+
115
+ // src/typeMap/routeMeta.ts
116
+ import path2 from "path";
117
+
118
+ // src/routeConventions.ts
119
+ var API_VERSION_TOKEN_REGEX = /_v(\d+)$/;
120
+ var SYNC_VERSION_TOKEN_REGEX = /_(server|client)_v(\d+)$/;
121
+ var ROUTE_NAMING_RULES = {
122
+ api: "<name>_v<number>.ts",
123
+ syncServer: "<name>_server_v<number>.ts",
124
+ syncClient: "<name>_client_v<number>.ts"
125
+ };
126
+ var ROUTE_NAMING_EXAMPLES = {
127
+ api: "updateUser_v1.ts",
128
+ syncServer: "updateCounter_server_v1.ts",
129
+ syncClient: "updateCounter_client_v1.ts"
130
+ };
131
+ var isVersionedApiFileName = (fileName) => isApiFileName(fileName);
132
+ var isVersionedSyncFileName = (fileName) => isSyncFileName(fileName);
133
+ var isVersionedSyncServerFileName = (fileName) => isSyncServerFileName(fileName);
134
+ var isVersionedSyncClientFileName = (fileName) => isSyncClientFileName(fileName);
135
+
136
+ // src/typeMap/routeMeta.ts
137
+ var VERSION_SUFFIX_REGEX = API_VERSION_TOKEN_REGEX;
138
+ var stripVersionSuffix = (name) => {
139
+ return name.replace(VERSION_SUFFIX_REGEX, "");
140
+ };
141
+ var extractVersionFromName = (name) => {
142
+ const match = name.match(VERSION_SUFFIX_REGEX);
143
+ if (!match) return null;
144
+ return `v${match[1]}`;
145
+ };
146
+ var extractPagePath = (filePath) => {
147
+ const normalized = filePath.replace(/\\/g, "/");
148
+ const match = normalized.match(/src\/(?:(.+?)\/)_api\//);
149
+ if (match) {
150
+ return match[1] || "system";
151
+ }
152
+ if (normalized.includes("/src/_api/")) {
153
+ return "system";
154
+ }
155
+ return "";
156
+ };
157
+ var extractApiName = (filePath) => {
158
+ const normalized = filePath.replace(/\\/g, "/");
159
+ const match = normalized.match(/_api\/(.+)\.ts$/);
160
+ const rawName = match?.[1] ?? path2.basename(filePath, ".ts");
161
+ return stripVersionSuffix(rawName);
162
+ };
163
+ var extractApiVersion = (filePath) => {
164
+ const normalized = filePath.replace(/\\/g, "/");
165
+ const match = normalized.match(/_api\/(.+)\.ts$/);
166
+ const rawName = match?.[1] ?? path2.basename(filePath, ".ts");
167
+ return extractVersionFromName(rawName) || "v1";
168
+ };
169
+ var extractSyncPagePath = (filePath) => {
170
+ const normalized = filePath.replace(/\\/g, "/");
171
+ const match = normalized.match(/src\/(?:(.+?)\/)_sync\//);
172
+ if (match) {
173
+ return match[1] || "root";
174
+ }
175
+ if (normalized.includes("/src/_sync/")) {
176
+ return "root";
177
+ }
178
+ return "";
179
+ };
180
+ var extractSyncName = (filePath) => {
181
+ const normalized = filePath.replace(/\\/g, "/");
182
+ const match = normalized.match(/_sync\/(.+)\.ts$/);
183
+ if (!match) {
184
+ const basename = path2.basename(filePath, ".ts");
185
+ const rawName2 = basename.replace(SYNC_VERSION_TOKEN_REGEX, "");
186
+ return rawName2;
187
+ }
188
+ const rawName = (match[1] ?? "").replace(SYNC_VERSION_TOKEN_REGEX, "");
189
+ return rawName;
190
+ };
191
+ var extractSyncVersion = (filePath) => {
192
+ const normalized = filePath.replace(/\\/g, "/");
193
+ const match = normalized.match(/_sync\/(.+)\.ts$/);
194
+ if (!match) {
195
+ const basename = path2.basename(filePath, ".ts");
196
+ const versionMatch2 = basename.match(SYNC_VERSION_TOKEN_REGEX);
197
+ return versionMatch2 ? `v${versionMatch2[2] ?? "1"}` : "v1";
198
+ }
199
+ const versionMatch = (match[1] ?? "").match(SYNC_VERSION_TOKEN_REGEX);
200
+ return versionMatch ? `v${versionMatch[2] ?? "1"}` : "v1";
201
+ };
202
+
203
+ // src/typeMap/apiMeta.ts
204
+ import * as ts from "typescript";
205
+ import fs2 from "fs";
206
+ import { inferHttpMethod } from "@luckystack/core";
207
+ var findExportedConst = (sourceFile, name) => {
208
+ for (const statement of sourceFile.statements) {
209
+ if (!ts.isVariableStatement(statement)) continue;
210
+ const hasExport = statement.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
211
+ if (!hasExport) continue;
212
+ for (const decl of statement.declarationList.declarations) {
213
+ if (ts.isIdentifier(decl.name) && decl.name.text === name) return decl;
214
+ }
215
+ }
216
+ return null;
217
+ };
218
+ var unwrapExpression = (node) => {
219
+ let current = node;
220
+ while (ts.isAsExpression(current) || ts.isSatisfiesExpression(current) || ts.isParenthesizedExpression(current)) {
221
+ current = current.expression;
222
+ }
223
+ return current;
224
+ };
225
+ var extractHttpMethod = (filePath, apiName) => {
226
+ try {
227
+ const content = fs2.readFileSync(filePath, "utf8");
228
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
229
+ const decl = findExportedConst(sourceFile, "httpMethod");
230
+ const initializer = decl?.initializer ? unwrapExpression(decl.initializer) : void 0;
231
+ if (initializer && ts.isStringLiteral(initializer)) {
232
+ const method = initializer.text.toUpperCase();
233
+ if (["GET", "POST", "PUT", "DELETE"].includes(method)) return method;
234
+ }
235
+ } catch (error) {
236
+ console.error(`[TypeMapGenerator] Error extracting httpMethod from ${filePath}:`, error);
237
+ }
238
+ return inferHttpMethod(apiName);
239
+ };
240
+ var extractRateLimit = (filePath) => {
241
+ try {
242
+ const content = fs2.readFileSync(filePath, "utf8");
243
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
244
+ const decl = findExportedConst(sourceFile, "rateLimit");
245
+ if (decl?.initializer) {
246
+ const initializer = unwrapExpression(decl.initializer);
247
+ if (initializer.kind === ts.SyntaxKind.FalseKeyword) return false;
248
+ if (ts.isNumericLiteral(initializer)) return Number(initializer.text);
249
+ }
250
+ } catch (error) {
251
+ console.error(`[TypeMapGenerator] Error extracting rateLimit from ${filePath}:`, error);
252
+ }
253
+ return void 0;
254
+ };
255
+ var readPrimitive = (rawNode) => {
256
+ const node = unwrapExpression(rawNode);
257
+ if (ts.isStringLiteral(node)) return node.text;
258
+ if (ts.isNumericLiteral(node)) return Number(node.text);
259
+ if (node.kind === ts.SyntaxKind.TrueKeyword) return true;
260
+ if (node.kind === ts.SyntaxKind.FalseKeyword) return false;
261
+ return void 0;
262
+ };
263
+ var parseAdditionalItem = (objectLiteral) => {
264
+ const item = {};
265
+ for (const prop of objectLiteral.properties) {
266
+ if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;
267
+ const value = readPrimitive(prop.initializer);
268
+ if (value !== void 0) item[prop.name.text] = value;
269
+ }
270
+ return item.key ? item : null;
271
+ };
272
+ var extractDocsMeta = (filePath) => {
273
+ try {
274
+ const content = fs2.readFileSync(filePath, "utf8");
275
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
276
+ const result = {};
277
+ const commentToString = (commentValue) => {
278
+ if (typeof commentValue === "string") return commentValue;
279
+ if (Array.isArray(commentValue)) {
280
+ return commentValue.map((part) => {
281
+ if (part && typeof part === "object" && "text" in part && typeof part.text === "string") {
282
+ return part.text;
283
+ }
284
+ return "";
285
+ }).join("");
286
+ }
287
+ return "";
288
+ };
289
+ const consumeTag = (tag) => {
290
+ if (tag.tagName.text !== "docs") return;
291
+ const commentText = commentToString(tag.comment).trim();
292
+ if (commentText.length === 0) return;
293
+ const spaceIdx = commentText.search(/\s/);
294
+ const subkey = (spaceIdx === -1 ? commentText : commentText.slice(0, spaceIdx)).toLowerCase();
295
+ const value = spaceIdx === -1 ? "" : commentText.slice(spaceIdx + 1).trim();
296
+ if (subkey === "owner" && value.length > 0) {
297
+ result.owner = value;
298
+ } else if (subkey === "tags" && value.length > 0) {
299
+ const tags = value.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
300
+ if (tags.length > 0) result.tags = tags;
301
+ } else if (subkey === "deprecated") {
302
+ result.deprecated = value.length > 0 ? value : true;
303
+ }
304
+ };
305
+ for (const statement of sourceFile.statements) {
306
+ for (const tag of ts.getJSDocTags(statement)) {
307
+ consumeTag(tag);
308
+ }
309
+ }
310
+ if (result.owner === void 0 && result.tags === void 0 && result.deprecated === void 0) {
311
+ return void 0;
312
+ }
313
+ return result;
314
+ } catch (error) {
315
+ console.error(`[TypeMapGenerator] Error extracting @docs metadata from ${filePath}:`, error);
316
+ return void 0;
317
+ }
318
+ };
319
+ var extractAuth = (filePath) => {
320
+ try {
321
+ const content = fs2.readFileSync(filePath, "utf8");
322
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
323
+ const decl = findExportedConst(sourceFile, "auth");
324
+ const authInitializer = decl?.initializer ? unwrapExpression(decl.initializer) : void 0;
325
+ if (!authInitializer || !ts.isObjectLiteralExpression(authInitializer)) return { login: true };
326
+ let login = true;
327
+ let additional;
328
+ for (const prop of authInitializer.properties) {
329
+ if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;
330
+ const propInit = unwrapExpression(prop.initializer);
331
+ if (prop.name.text === "login") {
332
+ login = propInit.kind === ts.SyntaxKind.TrueKeyword;
333
+ }
334
+ if (prop.name.text === "additional" && ts.isArrayLiteralExpression(propInit)) {
335
+ additional = [];
336
+ for (const element of propInit.elements) {
337
+ if (!ts.isObjectLiteralExpression(element)) continue;
338
+ const item = parseAdditionalItem(element);
339
+ if (item) additional.push(item);
340
+ }
341
+ }
342
+ }
343
+ return additional && additional.length > 0 ? { login, additional } : { login };
344
+ } catch {
345
+ }
346
+ return { login: true };
347
+ };
348
+
349
+ // src/typeMap/emitterArtifacts.ts
350
+ import fs3 from "fs";
351
+ import path3 from "path";
352
+ import * as ts3 from "typescript";
353
+ import { getGeneratedApiDocsPath, getGeneratedApiSchemasPath, getGeneratedSocketTypesPath } from "@luckystack/core";
354
+
355
+ // src/internal/mapUtils.ts
356
+ var getOrInit = (map, key, factory) => {
357
+ const existing = map.get(key);
358
+ if (existing !== void 0) return existing;
359
+ const fresh = factory();
360
+ map.set(key, fresh);
361
+ return fresh;
362
+ };
363
+ var mustGet = (map, key, label) => {
364
+ const value = map.get(key);
365
+ if (value === void 0) {
366
+ throw new Error(`[devkit] invariant violation: '${String(key)}' missing from ${label}`);
367
+ }
368
+ return value;
369
+ };
370
+
371
+ // src/typeMap/zodEmitter.ts
372
+ import * as ts2 from "typescript";
373
+ var wrapOptional = (inner) => `${inner}.optional()`;
374
+ var anyFallback = (reason) => `z.any() /* ${reason} */`;
375
+ var convertTypeNode = (node) => {
376
+ if (node.kind === ts2.SyntaxKind.StringKeyword) return "z.string()";
377
+ if (node.kind === ts2.SyntaxKind.NumberKeyword) return "z.number()";
378
+ if (node.kind === ts2.SyntaxKind.BooleanKeyword) return "z.boolean()";
379
+ if (node.kind === ts2.SyntaxKind.NullKeyword) return "z.null()";
380
+ if (node.kind === ts2.SyntaxKind.UndefinedKeyword) return "z.undefined()";
381
+ if (node.kind === ts2.SyntaxKind.AnyKeyword) return "z.any()";
382
+ if (node.kind === ts2.SyntaxKind.UnknownKeyword) return "z.unknown()";
383
+ if (node.kind === ts2.SyntaxKind.NeverKeyword) return "z.never()";
384
+ if (ts2.isLiteralTypeNode(node)) {
385
+ const literal = node.literal;
386
+ if (ts2.isStringLiteral(literal)) return `z.literal(${JSON.stringify(literal.text)})`;
387
+ if (ts2.isNumericLiteral(literal)) return `z.literal(${literal.text})`;
388
+ if (literal.kind === ts2.SyntaxKind.TrueKeyword) return "z.literal(true)";
389
+ if (literal.kind === ts2.SyntaxKind.FalseKeyword) return "z.literal(false)";
390
+ if (literal.kind === ts2.SyntaxKind.NullKeyword) return "z.null()";
391
+ return anyFallback("unknown literal");
392
+ }
393
+ if (ts2.isArrayTypeNode(node)) {
394
+ return `z.array(${convertTypeNode(node.elementType)})`;
395
+ }
396
+ if (ts2.isUnionTypeNode(node)) {
397
+ const members = node.types;
398
+ const hasUndefined = members.some((m) => m.kind === ts2.SyntaxKind.UndefinedKeyword);
399
+ const nonUndef = members.filter((m) => m.kind !== ts2.SyntaxKind.UndefinedKeyword);
400
+ if (nonUndef.length === 0) return "z.undefined()";
401
+ const firstNonUndef = nonUndef[0];
402
+ const innerSchema = nonUndef.length === 1 && firstNonUndef ? convertTypeNode(firstNonUndef) : `z.union([${nonUndef.map((m) => convertTypeNode(m)).join(", ")}])`;
403
+ return hasUndefined ? wrapOptional(innerSchema) : innerSchema;
404
+ }
405
+ if (ts2.isTypeReferenceNode(node)) {
406
+ const name = node.typeName.getText();
407
+ const args = node.typeArguments ?? [];
408
+ switch (name) {
409
+ case "Record": {
410
+ const arg0 = args[0];
411
+ const arg1 = args[1];
412
+ if (args.length === 2 && arg0 && arg1) {
413
+ return `z.record(${convertTypeNode(arg0)}, ${convertTypeNode(arg1)})`;
414
+ }
415
+ return anyFallback("Record with unexpected arity");
416
+ }
417
+ case "Partial": {
418
+ const arg0 = args[0];
419
+ return args.length === 1 && arg0 ? `${convertTypeNode(arg0)}.partial()` : anyFallback("Partial without type arg");
420
+ }
421
+ case "Array": {
422
+ const arg0 = args[0];
423
+ return args.length === 1 && arg0 ? `z.array(${convertTypeNode(arg0)})` : anyFallback("Array without element type");
424
+ }
425
+ case "Date": {
426
+ return "z.date()";
427
+ }
428
+ default: {
429
+ return anyFallback(`unresolved TypeReference '${name}'`);
430
+ }
431
+ }
432
+ }
433
+ if (ts2.isTypeLiteralNode(node)) {
434
+ const indexSignatures = node.members.filter((m) => ts2.isIndexSignatureDeclaration(m));
435
+ const propertySignatures = node.members.filter((m) => ts2.isPropertySignature(m));
436
+ if (indexSignatures.length === 1 && propertySignatures.length === 0 && indexSignatures[0]?.type.kind === ts2.SyntaxKind.NeverKeyword) {
437
+ return "z.object({}).strict()";
438
+ }
439
+ if (indexSignatures.length > 0 && propertySignatures.length === 0) {
440
+ const sig = indexSignatures[0];
441
+ const keyType = sig?.parameters[0]?.type;
442
+ const valueType = sig?.type;
443
+ if (sig && keyType && valueType) {
444
+ return `z.record(${convertTypeNode(keyType)}, ${convertTypeNode(valueType)})`;
445
+ }
446
+ }
447
+ const entries = propertySignatures.map((prop) => {
448
+ if (!ts2.isIdentifier(prop.name)) return null;
449
+ if (!prop.type) return null;
450
+ const name = prop.name.text;
451
+ let schema = convertTypeNode(prop.type);
452
+ if (prop.questionToken && !schema.endsWith(".optional()")) {
453
+ schema = wrapOptional(schema);
454
+ }
455
+ return `${JSON.stringify(name)}: ${schema}`;
456
+ }).filter((entry) => entry !== null);
457
+ return `z.object({ ${entries.join(", ")} })`;
458
+ }
459
+ if (ts2.isParenthesizedTypeNode(node)) {
460
+ return convertTypeNode(node.type);
461
+ }
462
+ if (ts2.isIntersectionTypeNode(node)) {
463
+ return anyFallback("intersection not yet supported");
464
+ }
465
+ return anyFallback(`unsupported TypeNode kind=${String(node.kind)}`);
466
+ };
467
+ var typeTextToZodSource = (typeText) => {
468
+ const trimmed = typeText.trim();
469
+ if (!trimmed) return null;
470
+ const synthetic = `type __X = ${trimmed};`;
471
+ const source = ts2.createSourceFile("__zod.ts", synthetic, ts2.ScriptTarget.Latest, true);
472
+ const statement = source.statements[0];
473
+ if (!statement || !ts2.isTypeAliasDeclaration(statement)) return null;
474
+ return convertTypeNode(statement.type);
475
+ };
476
+
477
+ // src/typeMap/emitterArtifacts.ts
478
+ var buildImportStatements = ({
479
+ namedImports: namedImports2,
480
+ defaultImports: defaultImports2
481
+ }) => {
482
+ let importStatements = "";
483
+ for (const [importPath, types] of namedImports2) {
484
+ importStatements += `import { ${[...types].join(", ")} } from "${importPath}";
485
+ `;
486
+ }
487
+ for (const [importPath, defaultName] of defaultImports2) {
488
+ importStatements += `import ${defaultName} from "${importPath}";
489
+ `;
490
+ }
491
+ return importStatements;
492
+ };
493
+ var splitVersionedKey = (value) => {
494
+ const [name, version] = value.split("@");
495
+ return { name: name ?? "", version: version ?? "v1" };
496
+ };
497
+ var writeFileIfChanged = (filePath, content) => {
498
+ if (fs3.existsSync(filePath)) {
499
+ const currentContent = fs3.readFileSync(filePath, "utf8");
500
+ if (currentContent === content) {
501
+ return false;
502
+ }
503
+ }
504
+ fs3.writeFileSync(filePath, content, "utf8");
505
+ return true;
506
+ };
507
+ var indentStr = (str, indentText) => {
508
+ return str.split("\n").map((line, i) => i === 0 ? line : indentText + line).join("\n");
509
+ };
510
+ var validateGeneratedTypeIdentifiers = (content) => {
511
+ const sourceFile = ts3.createSourceFile("apiTypes.generated.ts", content, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TS);
512
+ const knownSymbols = /* @__PURE__ */ new Set();
513
+ const referencedSymbols = /* @__PURE__ */ new Set();
514
+ const builtIns = /* @__PURE__ */ new Set([
515
+ "string",
516
+ "number",
517
+ "boolean",
518
+ "null",
519
+ "undefined",
520
+ "unknown",
521
+ "any",
522
+ "never",
523
+ "void",
524
+ "object",
525
+ "bigint",
526
+ "symbol",
527
+ "Record",
528
+ "Partial",
529
+ "Required",
530
+ "Pick",
531
+ "Omit",
532
+ "Readonly",
533
+ "Exclude",
534
+ "Extract",
535
+ "NonNullable",
536
+ "ReturnType",
537
+ "Awaited",
538
+ "Promise",
539
+ "Map",
540
+ "Set",
541
+ "WeakMap",
542
+ "WeakSet",
543
+ "Array",
544
+ "ReadonlyArray",
545
+ "Date",
546
+ "Error",
547
+ "RegExp",
548
+ "True",
549
+ "False",
550
+ "JsonPrimitive",
551
+ "JsonValue",
552
+ "JsonObject",
553
+ "JsonArray"
554
+ ]);
555
+ const addDeclaredName = (name) => {
556
+ if (!name?.text) return;
557
+ knownSymbols.add(name.text);
558
+ };
559
+ const collectTypeParams = (node) => {
560
+ if (!("typeParameters" in node)) return;
561
+ const maybeTypeParameters = node.typeParameters;
562
+ if (!maybeTypeParameters) return;
563
+ for (const typeParameter of maybeTypeParameters) {
564
+ knownSymbols.add(typeParameter.name.text);
565
+ }
566
+ };
567
+ const collectKnownSymbols = (node) => {
568
+ if (ts3.isInferTypeNode(node)) {
569
+ knownSymbols.add(node.typeParameter.name.text);
570
+ }
571
+ if (ts3.isMappedTypeNode(node)) {
572
+ knownSymbols.add(node.typeParameter.name.text);
573
+ }
574
+ if (ts3.isImportDeclaration(node)) {
575
+ const importClause = node.importClause;
576
+ if (importClause?.name) knownSymbols.add(importClause.name.text);
577
+ if (importClause?.namedBindings && ts3.isNamedImports(importClause.namedBindings)) {
578
+ for (const importElement of importClause.namedBindings.elements) {
579
+ knownSymbols.add((importElement.propertyName ?? importElement.name).text);
580
+ }
581
+ }
582
+ if (importClause?.namedBindings && ts3.isNamespaceImport(importClause.namedBindings)) {
583
+ knownSymbols.add(importClause.namedBindings.name.text);
584
+ }
585
+ }
586
+ if (ts3.isInterfaceDeclaration(node) || ts3.isTypeAliasDeclaration(node) || ts3.isClassDeclaration(node) || ts3.isEnumDeclaration(node) || ts3.isFunctionDeclaration(node)) {
587
+ addDeclaredName(node.name);
588
+ collectTypeParams(node);
589
+ }
590
+ if (ts3.isTypeReferenceNode(node)) {
591
+ const typeName = node.typeName;
592
+ if (ts3.isIdentifier(typeName)) {
593
+ referencedSymbols.add(typeName.text);
594
+ }
595
+ if (ts3.isQualifiedName(typeName) && ts3.isIdentifier(typeName.left)) {
596
+ referencedSymbols.add(typeName.left.text);
597
+ }
598
+ }
599
+ ts3.forEachChild(node, collectKnownSymbols);
600
+ };
601
+ collectKnownSymbols(sourceFile);
602
+ const unknown = [...referencedSymbols].filter((name) => !knownSymbols.has(name) && !builtIns.has(name)).toSorted();
603
+ if (unknown.length > 0) {
604
+ throw new Error(`[TypeMapGenerator] Generated type map has unresolved type identifiers: ${unknown.join(", ")}`);
605
+ }
606
+ };
607
+ var buildTypeMapArtifacts = ({
608
+ typesByPage,
609
+ syncTypesByPage,
610
+ namedImports: namedImports2,
611
+ defaultImports: defaultImports2,
612
+ functionsInterface
613
+ }) => {
614
+ const importStatements = buildImportStatements({ namedImports: namedImports2, defaultImports: defaultImports2 });
615
+ let content = `/* eslint-disable @typescript-eslint/no-unnecessary-condition */
616
+ /* eslint-disable @typescript-eslint/ban-types */
617
+
618
+ /**
619
+ * Auto-generated type map for all API and Sync endpoints.
620
+ * Enables type-safe apiRequest and syncRequest calls.
621
+ */
622
+
623
+ ${importStatements}
624
+ export interface Functions {
625
+ ${functionsInterface}
626
+ };
627
+
628
+ export type JsonPrimitive = string | number | boolean | null;
629
+ export type JsonValue = JsonPrimitive | JsonObject | JsonArray;
630
+ export interface JsonObject {
631
+ [key: string]: JsonValue | undefined;
632
+ }
633
+ export type JsonArray = JsonValue[];
634
+ export type MaybePromise<T> = T | Promise<T>;
635
+
636
+ export type StreamPayload = {
637
+ [key: string]: unknown;
638
+ };
639
+
640
+ export type ApiStreamEmitter<T extends StreamPayload = StreamPayload> = (payload?: T) => void | Promise<void>;
641
+ export type SyncServerStreamEmitter<T extends StreamPayload = StreamPayload> = (payload?: T) => void | Promise<void>;
642
+ export type SyncClientStreamEmitter<T extends StreamPayload = StreamPayload> = (payload?: T) => void | Promise<void>;
643
+ //? Broadcast \xE2\u20AC\u201D fan-out to every socket in the receiver room (cross-instance via the Redis adapter).
644
+ export type SyncBroadcastStreamEmitter<T extends StreamPayload = StreamPayload> = (payload?: T) => void;
645
+ //? Targeted \xE2\u20AC\u201D emit only to the listed session tokens (each token is its own room).
646
+ export type SyncStreamToEmitter<T extends StreamPayload = StreamPayload> = (
647
+ tokens: string | string[],
648
+ payload?: T,
649
+ ) => void;
650
+
651
+ //
652
+ // API Type Definitions
653
+ //
654
+
655
+ export type ApiResponse<T = unknown> =
656
+ | ({ status: 'success'; httpStatus?: number; APINAME?: never; [key: string]: unknown } & T)
657
+ | { status: 'error'; httpStatus?: number; errorCode: string; errorParams?: { key: string; value: string | number | boolean; }[]; APINAME?: never };
658
+
659
+ export type ApiNetworkResponse<T = unknown> =
660
+ | ({ status: 'success'; httpStatus: number; APINAME?: never; [key: string]: unknown } & T)
661
+ | { status: 'error'; httpStatus: number; message: string; errorCode: string; errorParams?: { key: string; value: string | number | boolean; }[]; APINAME?: never };
662
+
663
+ //
664
+ // API Type Map
665
+ //
666
+
667
+ type _ProjectApiTypeMap = {
668
+ `;
669
+ const sortedPages = [...typesByPage.keys()].toSorted();
670
+ const sortedSyncPages = [...syncTypesByPage.keys()].toSorted();
671
+ const docsData = { apis: {}, syncs: {} };
672
+ for (const pagePath of sortedPages) {
673
+ const apis = mustGet(typesByPage, pagePath, "typesByPage");
674
+ const grouped = /* @__PURE__ */ new Map();
675
+ docsData.apis[pagePath] = [];
676
+ for (const [apiKey, entry] of apis.entries()) {
677
+ const { name, version } = splitVersionedKey(apiKey);
678
+ if (!grouped.has(name)) grouped.set(name, []);
679
+ mustGet(grouped, name, "grouped").push({ version, entry });
680
+ }
681
+ content += ` '${pagePath}': {
682
+ `;
683
+ for (const apiName of [...grouped.keys()].toSorted()) {
684
+ content += ` '${apiName}': {
685
+ `;
686
+ for (const { version, entry } of mustGet(grouped, apiName, "grouped").toSorted((a, b) => a.version.localeCompare(b.version, void 0, { numeric: true }))) {
687
+ docsData.apis[pagePath].push({
688
+ page: pagePath,
689
+ name: apiName,
690
+ version,
691
+ method: entry.method,
692
+ input: entry.input,
693
+ output: entry.output,
694
+ stream: entry.stream,
695
+ rateLimit: entry.rateLimit,
696
+ auth: entry.auth,
697
+ path: pagePath === "root" ? `api/${apiName}/${version}` : `api/${pagePath}/${apiName}/${version}`,
698
+ ...entry.meta ? { meta: entry.meta } : {}
699
+ });
700
+ content += ` '${version}': {
701
+ `;
702
+ content += ` input: ${indentStr(entry.input, " ")};
703
+ `;
704
+ content += ` output: ${indentStr(entry.output, " ")};
705
+ `;
706
+ content += ` stream: ${indentStr(entry.stream, " ")};
707
+ `;
708
+ content += ` method: '${entry.method}';
709
+ `;
710
+ if (entry.rateLimit !== void 0) {
711
+ content += ` rateLimit: ${entry.rateLimit};
712
+ `;
713
+ }
714
+ content += ` };
715
+ `;
716
+ }
717
+ content += ` };
718
+ `;
719
+ }
720
+ content += ` };
721
+ `;
722
+ }
723
+ content += `};
724
+
725
+ export interface ApiTypeMap extends _ProjectApiTypeMap {}
726
+
727
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
728
+
729
+ export type PagePath = keyof ApiTypeMap;
730
+ export type ApiName<P extends PagePath> = keyof ApiTypeMap[P];
731
+ export type ApiVersion<P extends PagePath, N extends ApiName<P>> = keyof ApiTypeMap[P][N];
732
+ export type ApiInput<P extends PagePath, N extends ApiName<P>, V extends ApiVersion<P, N> = ApiVersion<P, N>> = ApiTypeMap[P][N][V] extends { input: infer I } ? I : never;
733
+ export type ApiOutput<P extends PagePath, N extends ApiName<P>, V extends ApiVersion<P, N> = ApiVersion<P, N>> = ApiTypeMap[P][N][V] extends { output: infer O } ? O : never;
734
+ export type ApiStream<P extends PagePath, N extends ApiName<P>, V extends ApiVersion<P, N> = ApiVersion<P, N>> = ApiTypeMap[P][N][V] extends { stream: infer S } ? S : never;
735
+ export type ApiMethod<P extends PagePath, N extends ApiName<P>, V extends ApiVersion<P, N> = ApiVersion<P, N>> = ApiTypeMap[P][N][V] extends { method: infer M } ? M : never;
736
+
737
+ export type FullApiPath<P extends PagePath, N extends ApiName<P>, V extends ApiVersion<P, N>> = \`api/\${P}/\${N & string}/\${V & string}\`;
738
+
739
+ export const apiMethodMap: Record<string, Record<string, Record<string, HttpMethod>>> = {
740
+ `;
741
+ for (const pagePath of sortedPages) {
742
+ const apis = mustGet(typesByPage, pagePath, "typesByPage");
743
+ const grouped = /* @__PURE__ */ new Map();
744
+ for (const [apiKey, entry] of apis.entries()) {
745
+ const { name, version } = splitVersionedKey(apiKey);
746
+ if (!grouped.has(name)) grouped.set(name, []);
747
+ mustGet(grouped, name, "grouped").push({ version, method: entry.method });
748
+ }
749
+ content += ` '${pagePath}': {
750
+ `;
751
+ for (const apiName of [...grouped.keys()].toSorted()) {
752
+ content += ` '${apiName}': {`;
753
+ const methods = mustGet(grouped, apiName, "grouped").toSorted((a, b) => a.version.localeCompare(b.version, void 0, { numeric: true })).map((item) => ` '${item.version}': '${item.method}'`).join(",");
754
+ content += `${methods} },
755
+ `;
756
+ }
757
+ content += ` },
758
+ `;
759
+ }
760
+ content += `};
761
+
762
+ export const getApiMethod = (pagePath: string, apiName: string, version: string): HttpMethod | undefined => {
763
+ return apiMethodMap[pagePath]?.[apiName]?.[version];
764
+ };
765
+
766
+ export interface ApiMetaEntry {
767
+ method: HttpMethod;
768
+ auth: { login: boolean; additional?: Record<string, unknown>[] };
769
+ rateLimit?: number | false;
770
+ }
771
+
772
+ export const apiMetaMap: Record<string, Record<string, Record<string, ApiMetaEntry>>> = {
773
+ `;
774
+ for (const pagePath of sortedPages) {
775
+ const apis = mustGet(typesByPage, pagePath, "typesByPage");
776
+ const grouped = /* @__PURE__ */ new Map();
777
+ for (const [apiKey, entry] of apis.entries()) {
778
+ const { name, version } = splitVersionedKey(apiKey);
779
+ if (!grouped.has(name)) grouped.set(name, []);
780
+ mustGet(grouped, name, "grouped").push({ version, entry });
781
+ }
782
+ content += ` '${pagePath}': {
783
+ `;
784
+ for (const apiName of [...grouped.keys()].toSorted()) {
785
+ content += ` '${apiName}': {
786
+ `;
787
+ for (const { version, entry } of mustGet(grouped, apiName, "grouped").toSorted((a, b) => a.version.localeCompare(b.version, void 0, { numeric: true }))) {
788
+ const auth = entry.auth && typeof entry.auth === "object" ? entry.auth : { login: true };
789
+ const rateLimitPart = entry.rateLimit === void 0 ? "" : `, rateLimit: ${entry.rateLimit === false ? "false" : String(entry.rateLimit)}`;
790
+ const additionalPart = auth.additional && auth.additional.length > 0 ? `, additional: ${JSON.stringify(auth.additional)}` : "";
791
+ content += ` '${version}': { method: '${entry.method}', auth: { login: ${auth.login ? "true" : "false"}${additionalPart} }${rateLimitPart} },
792
+ `;
793
+ }
794
+ content += ` },
795
+ `;
796
+ }
797
+ content += ` },
798
+ `;
799
+ }
800
+ content += `};
801
+
802
+ export const getApiMeta = (pagePath: string, apiName: string, version: string): ApiMetaEntry | undefined => {
803
+ return apiMetaMap[pagePath]?.[apiName]?.[version];
804
+ };
805
+
806
+ // Sync Type Definitions
807
+ //
808
+
809
+ export type SyncServerResponse<T = unknown> =
810
+ | ({ status: 'success'; [key: string]: unknown } & T)
811
+ | { status: 'error'; errorCode: string; errorParams?: { key: string; value: string | number | boolean; }[] };
812
+
813
+ export type SyncClientResponse<T = unknown> =
814
+ | ({ status: 'success'; [key: string]: unknown } & T)
815
+ | { status: 'error'; errorCode: string; errorParams?: { key: string; value: string | number | boolean; }[] };
816
+
817
+ //
818
+ // Sync Type Map
819
+ //
820
+
821
+ type _ProjectSyncTypeMap = {
822
+ `;
823
+ for (const pagePath of sortedSyncPages) {
824
+ const syncs = mustGet(syncTypesByPage, pagePath, "syncTypesByPage");
825
+ const grouped = /* @__PURE__ */ new Map();
826
+ docsData.syncs[pagePath] = [];
827
+ for (const [syncKey, entry] of syncs.entries()) {
828
+ const { name, version } = splitVersionedKey(syncKey);
829
+ if (!grouped.has(name)) grouped.set(name, []);
830
+ mustGet(grouped, name, "grouped").push({ version, entry });
831
+ }
832
+ content += ` '${pagePath}': {
833
+ `;
834
+ for (const syncName of [...grouped.keys()].toSorted()) {
835
+ content += ` '${syncName}': {
836
+ `;
837
+ for (const { version, entry } of mustGet(grouped, syncName, "grouped").toSorted((a, b) => a.version.localeCompare(b.version, void 0, { numeric: true }))) {
838
+ docsData.syncs[pagePath].push({
839
+ page: pagePath,
840
+ name: syncName,
841
+ version,
842
+ clientInput: entry.clientInput,
843
+ serverOutput: entry.serverOutput,
844
+ clientOutput: entry.clientOutput,
845
+ serverStream: entry.serverStream,
846
+ clientStream: entry.clientStream,
847
+ path: pagePath === "root" ? `sync/${syncName}/${version}` : `sync/${pagePath}/${syncName}/${version}`,
848
+ ...entry.meta ? { meta: entry.meta } : {}
849
+ });
850
+ content += ` '${version}': {
851
+ `;
852
+ content += ` clientInput: ${indentStr(entry.clientInput, " ")};
853
+ `;
854
+ content += ` serverOutput: ${indentStr(entry.serverOutput, " ")};
855
+ `;
856
+ content += ` clientOutput: ${indentStr(entry.clientOutput, " ")};
857
+ `;
858
+ content += ` serverStream: ${indentStr(entry.serverStream, " ")};
859
+ `;
860
+ content += ` clientStream: ${indentStr(entry.clientStream, " ")};
861
+ `;
862
+ content += ` };
863
+ `;
864
+ }
865
+ content += ` };
866
+ `;
867
+ }
868
+ content += ` };
869
+ `;
870
+ }
871
+ content += `};
872
+
873
+ export interface SyncTypeMap extends _ProjectSyncTypeMap {}
874
+
875
+ export type SyncPagePath = keyof SyncTypeMap;
876
+ export type SyncName<P extends SyncPagePath> = keyof SyncTypeMap[P];
877
+ export type SyncVersion<P extends SyncPagePath, N extends SyncName<P>> = keyof SyncTypeMap[P][N];
878
+ export type SyncClientInput<P extends SyncPagePath, N extends SyncName<P>, V extends SyncVersion<P, N> = SyncVersion<P, N>> = SyncTypeMap[P][N][V] extends { clientInput: infer C } ? C : never;
879
+ export type SyncServerOutput<P extends SyncPagePath, N extends SyncName<P>, V extends SyncVersion<P, N> = SyncVersion<P, N>> = SyncTypeMap[P][N][V] extends { serverOutput: infer S } ? S : never;
880
+ export type SyncClientOutput<P extends SyncPagePath, N extends SyncName<P>, V extends SyncVersion<P, N> = SyncVersion<P, N>> = SyncTypeMap[P][N][V] extends { clientOutput: infer O } ? O : never;
881
+ export type SyncServerStream<P extends SyncPagePath, N extends SyncName<P>, V extends SyncVersion<P, N> = SyncVersion<P, N>> = SyncTypeMap[P][N][V] extends { serverStream: infer S } ? S : never;
882
+ export type SyncClientStream<P extends SyncPagePath, N extends SyncName<P>, V extends SyncVersion<P, N> = SyncVersion<P, N>> = SyncTypeMap[P][N][V] extends { clientStream: infer O } ? O : never;
883
+
884
+ export type FullSyncPath<P extends SyncPagePath, N extends SyncName<P>, V extends SyncVersion<P, N>> = \`sync/\${P}/\${N & string}/\${V & string}\`;
885
+
886
+ //
887
+ // Type-level augmentation \xE2\u20AC\u201D merges the project's concrete ApiTypeMap / SyncTypeMap
888
+ // into the @luckystack/core/typemap stub-declaration module so framework code
889
+ // (apiRequest / syncRequest) sees the project routes without deep-relative
890
+ // imports. Augmenting the module that DECLARES the stubs (not the re-exporting
891
+ // barrel) is what makes the merge land for consumers installing the built dist.
892
+ //
893
+ declare module '@luckystack/core/typemap' {
894
+ interface ApiTypeMap extends _ProjectApiTypeMap {}
895
+ interface SyncTypeMap extends _ProjectSyncTypeMap {}
896
+ }
897
+ `;
898
+ validateGeneratedTypeIdentifiers(content);
899
+ const schemasContent = buildSchemasContent({ typesByPage });
900
+ return { content, docsData, schemasContent };
901
+ };
902
+ var buildSchemasContent = ({
903
+ typesByPage
904
+ }) => {
905
+ const sortedPages = [...typesByPage.keys()].toSorted();
906
+ let body = `/* eslint-disable */
907
+ //? Auto-generated Zod schemas for every API input. Driven by the same walk
908
+ //? as apiTypes.generated.ts; see @luckystack/devkit/src/typeMap/zodEmitter.ts
909
+ //? for the TS-AST \xE2\u2020\u2019 Zod converter. Types that fall outside the converter's
910
+ //? scope emit \`z.any()\` with a TODO comment.
911
+
912
+ import { z } from 'zod';
913
+
914
+ export const apiInputSchemas: Record<string, Record<string, Record<string, z.ZodTypeAny>>> = {
915
+ `;
916
+ for (const pagePath of sortedPages) {
917
+ const apis = mustGet(typesByPage, pagePath, "typesByPage");
918
+ const grouped = /* @__PURE__ */ new Map();
919
+ for (const [apiKey, entry] of apis.entries()) {
920
+ const { name, version } = splitVersionedKey(apiKey);
921
+ if (!grouped.has(name)) grouped.set(name, []);
922
+ mustGet(grouped, name, "grouped").push({ version, entry });
923
+ }
924
+ body += ` '${pagePath}': {
925
+ `;
926
+ for (const apiName of [...grouped.keys()].toSorted()) {
927
+ body += ` '${apiName}': {
928
+ `;
929
+ for (const { version, entry } of mustGet(grouped, apiName, "grouped").toSorted(
930
+ (a, b) => a.version.localeCompare(b.version, void 0, { numeric: true })
931
+ )) {
932
+ const schemaSrc = typeTextToZodSource(entry.input) ?? "z.any() /* unparseable input type */";
933
+ body += ` '${version}': ${schemaSrc},
934
+ `;
935
+ }
936
+ body += ` },
937
+ `;
938
+ }
939
+ body += ` },
940
+ `;
941
+ }
942
+ body += `};
943
+
944
+ export const getApiInputSchema = (
945
+ pagePath: string,
946
+ apiName: string,
947
+ version: string,
948
+ ): z.ZodTypeAny | undefined => {
949
+ return apiInputSchemas[pagePath]?.[apiName]?.[version];
950
+ };
951
+ `;
952
+ return body;
953
+ };
954
+ var writeTypeMapArtifacts = ({
955
+ content,
956
+ docsData,
957
+ schemasContent
958
+ }) => {
959
+ try {
960
+ const outputPath = getGeneratedSocketTypesPath();
961
+ const hasUpdatedTypeMap = writeFileIfChanged(outputPath, content);
962
+ if (hasUpdatedTypeMap) {
963
+ console.log("[TypeMapGenerator] Generated apiTypes.generated.ts");
964
+ }
965
+ if (schemasContent !== void 0) {
966
+ const hasUpdatedSchemas = writeFileIfChanged(getGeneratedApiSchemasPath(), schemasContent);
967
+ if (hasUpdatedSchemas) {
968
+ console.log("[TypeMapGenerator] Generated apiInputSchemas.generated.ts");
969
+ }
970
+ }
971
+ const docsPath = getGeneratedApiDocsPath();
972
+ const docsDir = path3.dirname(docsPath);
973
+ if (!fs3.existsSync(docsDir)) {
974
+ fs3.mkdirSync(docsDir, { recursive: true });
975
+ }
976
+ const docsContent = JSON.stringify(docsData, null, 2);
977
+ const hasUpdatedDocs = writeFileIfChanged(docsPath, docsContent);
978
+ if (hasUpdatedDocs) {
979
+ console.log("[TypeMapGenerator] Generated apiDocs.generated.json");
980
+ }
981
+ } catch (error) {
982
+ console.error("[TypeMapGenerator] Error writing type map or docs:", error);
983
+ }
984
+ };
985
+
986
+ // src/typeMap/extractors.ts
987
+ import * as ts5 from "typescript";
988
+ import path5 from "path";
989
+
990
+ // src/typeMap/tsProgram.ts
991
+ import * as ts4 from "typescript";
992
+ import path4 from "path";
993
+ import { ROOT_DIR as ROOT_DIR2 } from "@luckystack/core";
994
+ var cachedProgram = null;
995
+ var getServerProgram = () => {
996
+ if (cachedProgram) return cachedProgram;
997
+ const tsconfigPath = ts4.findConfigFile(ROOT_DIR2, ts4.sys.fileExists, "tsconfig.server.json");
998
+ if (!tsconfigPath) throw new Error("[TypeProgram] tsconfig.server.json not found");
999
+ const { config } = ts4.readConfigFile(tsconfigPath, ts4.sys.readFile);
1000
+ const { options, fileNames } = ts4.parseJsonConfigFileContent(
1001
+ config,
1002
+ ts4.sys,
1003
+ path4.dirname(tsconfigPath)
1004
+ );
1005
+ cachedProgram = ts4.createProgram(fileNames, options);
1006
+ return cachedProgram;
1007
+ };
1008
+ var invalidateProgramCache = () => {
1009
+ cachedProgram = null;
1010
+ };
1011
+ var DEPTH_LIMIT = 12;
1012
+ var JSON_TYPE_NAMES = /* @__PURE__ */ new Set([
1013
+ "Json",
1014
+ "JsonValue",
1015
+ "JsonObject",
1016
+ "JsonArray",
1017
+ "InputJsonValue",
1018
+ "InputJsonObject",
1019
+ "InputJsonArray"
1020
+ ]);
1021
+ var SKIP_EXPANSION = /* @__PURE__ */ new Set([
1022
+ "Promise",
1023
+ "Map",
1024
+ "WeakMap",
1025
+ "Set",
1026
+ "WeakSet",
1027
+ "Error",
1028
+ "Date",
1029
+ "RegExp",
1030
+ "Buffer",
1031
+ "ArrayBuffer",
1032
+ "ReadonlyArray"
1033
+ ]);
1034
+ var isJsonLikeType = (type, checker) => {
1035
+ const symbolName = type.symbol?.name || "";
1036
+ const aliasName = type.aliasSymbol?.name || "";
1037
+ if (JSON_TYPE_NAMES.has(symbolName) || JSON_TYPE_NAMES.has(aliasName)) return true;
1038
+ const rendered = checker.typeToString(type);
1039
+ return /(\bPrisma\.)?(Input)?Json(Value|Object|Array)\b/.test(rendered);
1040
+ };
1041
+ var getLiteralTypeFromExpression = (expression, checker, depth) => {
1042
+ if (ts4.isParenthesizedExpression(expression)) {
1043
+ return getLiteralTypeFromExpression(expression.expression, checker, depth);
1044
+ }
1045
+ if (ts4.isAsExpression(expression) || ts4.isTypeAssertionExpression(expression)) {
1046
+ return getLiteralTypeFromExpression(expression.expression, checker, depth);
1047
+ }
1048
+ if (expression.kind === ts4.SyntaxKind.TrueKeyword) return "true";
1049
+ if (expression.kind === ts4.SyntaxKind.FalseKeyword) return "false";
1050
+ if (expression.kind === ts4.SyntaxKind.NullKeyword) return "null";
1051
+ if (ts4.isStringLiteral(expression) || ts4.isNoSubstitutionTemplateLiteral(expression)) {
1052
+ return `'${expression.text.replaceAll("\\", "\\\\").replaceAll("'", String.raw`\'`)}'`;
1053
+ }
1054
+ if (ts4.isNumericLiteral(expression)) {
1055
+ return expression.text;
1056
+ }
1057
+ if (ts4.isPrefixUnaryExpression(expression) && expression.operator === ts4.SyntaxKind.MinusToken && ts4.isNumericLiteral(expression.operand)) {
1058
+ return `-${expression.operand.text}`;
1059
+ }
1060
+ if (ts4.isIdentifier(expression)) {
1061
+ const identifierType = checker.getTypeAtLocation(expression);
1062
+ const isLiteralType = (identifierType.flags & (ts4.TypeFlags.StringLiteral | ts4.TypeFlags.NumberLiteral | ts4.TypeFlags.BooleanLiteral | ts4.TypeFlags.Null | ts4.TypeFlags.Undefined)) !== 0;
1063
+ if (isLiteralType || identifierType.isUnion()) {
1064
+ const expanded = expandTypeDetailed(identifierType, checker, depth).text;
1065
+ if (expanded.includes("'") || /\btrue\b|\bfalse\b|\bnull\b|\bundefined\b/.test(expanded)) {
1066
+ return expanded;
1067
+ }
1068
+ }
1069
+ }
1070
+ return null;
1071
+ };
1072
+ var getLiteralTypeFromPropertySymbol = (symbol, checker, depth) => {
1073
+ const declarations = symbol.declarations ?? [];
1074
+ for (const declaration of declarations) {
1075
+ if (ts4.isPropertyAssignment(declaration)) {
1076
+ const literal = getLiteralTypeFromExpression(declaration.initializer, checker, depth);
1077
+ if (literal) return literal;
1078
+ }
1079
+ if (ts4.isShorthandPropertyAssignment(declaration)) {
1080
+ const literal = getLiteralTypeFromExpression(declaration.name, checker, depth);
1081
+ if (literal) return literal;
1082
+ }
1083
+ }
1084
+ return null;
1085
+ };
1086
+ var normalizeImportPath = (targetFilePath) => {
1087
+ const fromDir = path4.join(ROOT_DIR2, "src", "_sockets");
1088
+ const from = fromDir.replaceAll("\\", "/");
1089
+ const to = targetFilePath.replaceAll("\\", "/");
1090
+ const normalized = path4.posix.relative(from, to).replaceAll("\\", "/");
1091
+ const withoutExtension = normalized.replace(/(\.d)?\.(ts|tsx|js|jsx)$/i, "");
1092
+ if (withoutExtension.startsWith(".")) return withoutExtension;
1093
+ return `./${withoutExtension}`;
1094
+ };
1095
+ var mergeUnresolvedSymbols = (left, right) => {
1096
+ const merged = [...left];
1097
+ const seen = new Set(merged.map((symbol) => `${symbol.name}|${symbol.importPath ?? ""}|${symbol.sourceFile ?? ""}`));
1098
+ for (const symbol of right) {
1099
+ const key = `${symbol.name}|${symbol.importPath ?? ""}|${symbol.sourceFile ?? ""}`;
1100
+ if (seen.has(key)) continue;
1101
+ seen.add(key);
1102
+ merged.push(symbol);
1103
+ }
1104
+ return merged;
1105
+ };
1106
+ var collectTypeSymbolFallback = (type) => {
1107
+ const symbol = type.aliasSymbol ?? type.getSymbol();
1108
+ if (!symbol) return [];
1109
+ const name = symbol.getName();
1110
+ if (!name || name.startsWith("__")) return [];
1111
+ const declaration = symbol.declarations?.[0];
1112
+ if (!declaration) return [{ name }];
1113
+ const sourceFile = declaration.getSourceFile().fileName;
1114
+ if (!sourceFile || sourceFile.includes("/node_modules/") || sourceFile.includes("\\node_modules\\")) {
1115
+ return [{ name }];
1116
+ }
1117
+ return [{
1118
+ name,
1119
+ sourceFile,
1120
+ importPath: normalizeImportPath(sourceFile)
1121
+ }];
1122
+ };
1123
+ var expandTypeDetailed = (type, checker, depth = 0, state) => {
1124
+ const expandState = state ?? { stackTypeIds: /* @__PURE__ */ new Set() };
1125
+ const typeId = type.id;
1126
+ if (typeId !== void 0) {
1127
+ if (expandState.stackTypeIds.has(typeId)) {
1128
+ return {
1129
+ text: checker.typeToString(type),
1130
+ unresolvedSymbols: collectTypeSymbolFallback(type)
1131
+ };
1132
+ }
1133
+ expandState.stackTypeIds.add(typeId);
1134
+ }
1135
+ try {
1136
+ if (depth > DEPTH_LIMIT) {
1137
+ return {
1138
+ text: checker.typeToString(type),
1139
+ unresolvedSymbols: collectTypeSymbolFallback(type)
1140
+ };
1141
+ }
1142
+ if (isJsonLikeType(type, checker)) return { text: "JsonValue", unresolvedSymbols: [] };
1143
+ if (type.isStringLiteral()) return { text: `'${type.value.replaceAll("\\", "\\\\").replaceAll("'", String.raw`\'`)}'`, unresolvedSymbols: [] };
1144
+ if (type.isNumberLiteral()) return { text: String(type.value), unresolvedSymbols: [] };
1145
+ if (type.flags & (ts4.TypeFlags.String | ts4.TypeFlags.Number | ts4.TypeFlags.Boolean | ts4.TypeFlags.BooleanLiteral | ts4.TypeFlags.Undefined | ts4.TypeFlags.Null | ts4.TypeFlags.Any | ts4.TypeFlags.Unknown | ts4.TypeFlags.Never | ts4.TypeFlags.Void)) {
1146
+ return { text: checker.typeToString(type), unresolvedSymbols: [] };
1147
+ }
1148
+ if (type.isUnion()) {
1149
+ let unresolvedSymbols = [];
1150
+ const expandedTypes = type.types.map((innerType) => {
1151
+ const expanded = expandTypeDetailed(innerType, checker, depth + 1, expandState);
1152
+ unresolvedSymbols = mergeUnresolvedSymbols(unresolvedSymbols, expanded.unresolvedSymbols);
1153
+ return expanded.text;
1154
+ });
1155
+ return { text: expandedTypes.join(" | "), unresolvedSymbols };
1156
+ }
1157
+ if (type.isIntersection()) {
1158
+ let unresolvedSymbols = [];
1159
+ const expandedTypes = type.types.map((innerType) => {
1160
+ const expanded = expandTypeDetailed(innerType, checker, depth + 1, expandState);
1161
+ unresolvedSymbols = mergeUnresolvedSymbols(unresolvedSymbols, expanded.unresolvedSymbols);
1162
+ return expanded.text;
1163
+ });
1164
+ return { text: expandedTypes.join(" & "), unresolvedSymbols };
1165
+ }
1166
+ if (type.flags & ts4.TypeFlags.Object) {
1167
+ const objectType = type;
1168
+ if (objectType.objectFlags & ts4.ObjectFlags.Tuple) {
1169
+ const typeArgs = checker.getTypeArguments(objectType);
1170
+ let unresolvedSymbols = [];
1171
+ const tupleTypes = typeArgs.map((innerType) => {
1172
+ const expanded = expandTypeDetailed(innerType, checker, depth + 1, expandState);
1173
+ unresolvedSymbols = mergeUnresolvedSymbols(unresolvedSymbols, expanded.unresolvedSymbols);
1174
+ return expanded.text;
1175
+ });
1176
+ return { text: `[${tupleTypes.join(", ")}]`, unresolvedSymbols };
1177
+ }
1178
+ if (objectType.objectFlags & ts4.ObjectFlags.Reference) {
1179
+ const refType = objectType;
1180
+ const targetName = refType.target?.symbol?.name ?? "";
1181
+ if (targetName === "Array" || targetName === "ReadonlyArray") {
1182
+ const typeArgs = checker.getTypeArguments(refType);
1183
+ const firstArg = typeArgs[0];
1184
+ if (firstArg) {
1185
+ const expanded = expandTypeDetailed(firstArg, checker, depth + 1, expandState);
1186
+ const elementType = /\s[|&]\s/.test(expanded.text) ? `(${expanded.text})` : expanded.text;
1187
+ return {
1188
+ text: `${elementType}[]`,
1189
+ unresolvedSymbols: expanded.unresolvedSymbols
1190
+ };
1191
+ }
1192
+ }
1193
+ if (SKIP_EXPANSION.has(targetName)) {
1194
+ return { text: checker.typeToString(type), unresolvedSymbols: [] };
1195
+ }
1196
+ }
1197
+ const symbolName = type.symbol?.name || type.aliasSymbol?.name || "";
1198
+ if (SKIP_EXPANSION.has(symbolName)) {
1199
+ return { text: checker.typeToString(type), unresolvedSymbols: [] };
1200
+ }
1201
+ const props = checker.getPropertiesOfType(type);
1202
+ const indexInfos = checker.getIndexInfosOfType(type);
1203
+ if (props.length > 0 || indexInfos.length > 0) {
1204
+ const fields = [];
1205
+ let unresolvedSymbols = [];
1206
+ for (const prop of props) {
1207
+ const propType = checker.getTypeOfSymbol(prop);
1208
+ const literalType = getLiteralTypeFromPropertySymbol(prop, checker, depth + 1);
1209
+ const isOptional = (prop.flags & ts4.SymbolFlags.Optional) !== 0;
1210
+ if (literalType) {
1211
+ fields.push(`${prop.name}${isOptional ? "?" : ""}: ${literalType}`);
1212
+ continue;
1213
+ }
1214
+ const expandedProp = expandTypeDetailed(propType, checker, depth + 1, expandState);
1215
+ unresolvedSymbols = mergeUnresolvedSymbols(unresolvedSymbols, expandedProp.unresolvedSymbols);
1216
+ fields.push(`${prop.name}${isOptional ? "?" : ""}: ${expandedProp.text}`);
1217
+ }
1218
+ for (const indexInfo of indexInfos) {
1219
+ const keyType = expandTypeDetailed(indexInfo.keyType, checker, depth + 1, expandState);
1220
+ const valueType = expandTypeDetailed(indexInfo.type, checker, depth + 1, expandState);
1221
+ unresolvedSymbols = mergeUnresolvedSymbols(unresolvedSymbols, keyType.unresolvedSymbols);
1222
+ unresolvedSymbols = mergeUnresolvedSymbols(unresolvedSymbols, valueType.unresolvedSymbols);
1223
+ fields.push(`[key: ${keyType.text}]: ${valueType.text}`);
1224
+ }
1225
+ const indent = " ".repeat(depth + 1);
1226
+ const outerIndent = " ".repeat(depth);
1227
+ return {
1228
+ text: `{
1229
+ ${indent}${fields.join(`;
1230
+ ${indent}`)}
1231
+ ${outerIndent}}`,
1232
+ unresolvedSymbols
1233
+ };
1234
+ }
1235
+ return { text: "{ }", unresolvedSymbols: [] };
1236
+ }
1237
+ return {
1238
+ text: checker.typeToString(type),
1239
+ unresolvedSymbols: collectTypeSymbolFallback(type)
1240
+ };
1241
+ } finally {
1242
+ if (typeId !== void 0) {
1243
+ expandState.stackTypeIds.delete(typeId);
1244
+ }
1245
+ }
1246
+ };
1247
+ var expandType = (type, checker, depth = 0) => {
1248
+ return expandTypeDetailed(type, checker, depth).text;
1249
+ };
1250
+
1251
+ // src/typeMap/extractors.ts
1252
+ import { ROOT_DIR as ROOT_DIR3 } from "@luckystack/core";
1253
+ var TYPE_NAME_PATTERN = /\b[A-Z][A-Za-z0-9_]*\b/g;
1254
+ var KNOWN_GLOBAL_TYPE_NAMES = /* @__PURE__ */ new Set([
1255
+ "String",
1256
+ "Number",
1257
+ "Boolean",
1258
+ "Object",
1259
+ "Array",
1260
+ "ReadonlyArray",
1261
+ "Promise",
1262
+ "Map",
1263
+ "Set",
1264
+ "WeakMap",
1265
+ "WeakSet",
1266
+ "Date",
1267
+ "RegExp",
1268
+ "Error",
1269
+ "Record",
1270
+ "Partial",
1271
+ "Required",
1272
+ "Pick",
1273
+ "Omit",
1274
+ "Readonly",
1275
+ "Exclude",
1276
+ "Extract",
1277
+ "NonNullable",
1278
+ "ReturnType",
1279
+ "Awaited",
1280
+ "JsonValue",
1281
+ "JsonObject",
1282
+ "JsonArray",
1283
+ "JsonPrimitive"
1284
+ ]);
1285
+ var normalizeImportPath2 = (targetFilePath) => {
1286
+ const fromDir = path5.join(ROOT_DIR3, "src", "_sockets");
1287
+ const from = fromDir.replaceAll("\\", "/");
1288
+ const to = targetFilePath.replaceAll("\\", "/");
1289
+ const normalized = path5.posix.relative(from, to).replaceAll("\\", "/");
1290
+ const withoutExtension = normalized.replace(/(\.d)?\.(ts|tsx|js|jsx)$/i, "");
1291
+ if (withoutExtension.startsWith(".")) return withoutExtension;
1292
+ return `./${withoutExtension}`;
1293
+ };
1294
+ var mergeUnresolvedSymbols2 = (left, right) => {
1295
+ const merged = [...left];
1296
+ const seen = new Set(merged.map((symbol) => `${symbol.name}|${symbol.importPath ?? ""}|${symbol.sourceFile ?? ""}`));
1297
+ for (const symbol of right) {
1298
+ const key = `${symbol.name}|${symbol.importPath ?? ""}|${symbol.sourceFile ?? ""}`;
1299
+ if (seen.has(key)) continue;
1300
+ seen.add(key);
1301
+ merged.push(symbol);
1302
+ }
1303
+ return merged;
1304
+ };
1305
+ var collectFallbackSymbolsFromTypeText = (typeText, scopeNode, checker) => {
1306
+ const names = new Set((typeText.match(TYPE_NAME_PATTERN) ?? []).filter((name) => !KNOWN_GLOBAL_TYPE_NAMES.has(name)));
1307
+ const symbolsInScope = checker.getSymbolsInScope(
1308
+ scopeNode,
1309
+ ts5.SymbolFlags.TypeAlias | ts5.SymbolFlags.Interface | ts5.SymbolFlags.Class | ts5.SymbolFlags.Enum | ts5.SymbolFlags.Alias
1310
+ );
1311
+ const unresolvedSymbols = [];
1312
+ for (const name of names) {
1313
+ const localSymbol = symbolsInScope.find((symbol) => symbol.name === name);
1314
+ if (!localSymbol) continue;
1315
+ const targetSymbol = (localSymbol.flags & ts5.SymbolFlags.Alias) === 0 ? localSymbol : checker.getAliasedSymbol(localSymbol);
1316
+ const declaration = targetSymbol.declarations?.[0];
1317
+ if (!declaration) {
1318
+ unresolvedSymbols.push({ name });
1319
+ continue;
1320
+ }
1321
+ const sourceFile = declaration.getSourceFile().fileName;
1322
+ if (!sourceFile || sourceFile.includes("/node_modules/") || sourceFile.includes("\\node_modules\\")) {
1323
+ continue;
1324
+ }
1325
+ unresolvedSymbols.push({
1326
+ name,
1327
+ sourceFile,
1328
+ importPath: normalizeImportPath2(sourceFile)
1329
+ });
1330
+ }
1331
+ return unresolvedSymbols;
1332
+ };
1333
+ var findInterface = (sourceFile, name) => {
1334
+ for (const stmt of sourceFile.statements) {
1335
+ if (ts5.isInterfaceDeclaration(stmt) && stmt.name.text === name) return stmt;
1336
+ }
1337
+ return null;
1338
+ };
1339
+ var getInterfacePropertyType = (iface, propertyName, checker) => {
1340
+ for (const member of iface.members) {
1341
+ if (ts5.isPropertySignature(member) && member.name && ts5.isIdentifier(member.name) && member.name.text === propertyName && member.type) {
1342
+ return checker.getTypeFromTypeNode(member.type);
1343
+ }
1344
+ }
1345
+ return null;
1346
+ };
1347
+ var findMainFunction = (sourceFile) => {
1348
+ for (const stmt of sourceFile.statements) {
1349
+ if (ts5.isVariableStatement(stmt)) {
1350
+ for (const decl of stmt.declarationList.declarations) {
1351
+ if (ts5.isIdentifier(decl.name) && decl.name.text === "main" && decl.initializer && (ts5.isArrowFunction(decl.initializer) || ts5.isFunctionExpression(decl.initializer))) {
1352
+ return decl.initializer;
1353
+ }
1354
+ }
1355
+ }
1356
+ if (ts5.isFunctionDeclaration(stmt) && stmt.name?.text === "main") {
1357
+ return stmt;
1358
+ }
1359
+ }
1360
+ return null;
1361
+ };
1362
+ var collectReturnObjectTypeDetails = (funcNode, checker) => {
1363
+ const types = [];
1364
+ let unresolvedSymbols = [];
1365
+ const visit = (node) => {
1366
+ if (ts5.isReturnStatement(node) && node.expression && ts5.isObjectLiteralExpression(node.expression)) {
1367
+ const type = checker.getTypeAtLocation(node.expression);
1368
+ const expanded = expandTypeDetailed(type, checker);
1369
+ types.push(expanded.text);
1370
+ unresolvedSymbols = mergeUnresolvedSymbols2(unresolvedSymbols, expanded.unresolvedSymbols);
1371
+ unresolvedSymbols = mergeUnresolvedSymbols2(
1372
+ unresolvedSymbols,
1373
+ collectFallbackSymbolsFromTypeText(expanded.text, node.expression, checker)
1374
+ );
1375
+ }
1376
+ if (!ts5.isArrowFunction(node) && !ts5.isFunctionExpression(node) && !ts5.isFunctionDeclaration(node)) {
1377
+ ts5.forEachChild(node, visit);
1378
+ }
1379
+ };
1380
+ ts5.forEachChild(funcNode, visit);
1381
+ return { text: unionTypes(types), unresolvedSymbols };
1382
+ };
1383
+ var STREAM_EMIT_NAMES = /* @__PURE__ */ new Set(["stream", "broadcastStream", "streamTo"]);
1384
+ var collectStreamCallPayloadTypeDetails = (funcNode, checker) => {
1385
+ const types = [];
1386
+ let unresolvedSymbols = [];
1387
+ const visit = (node) => {
1388
+ if (ts5.isCallExpression(node) && ts5.isIdentifier(node.expression) && STREAM_EMIT_NAMES.has(node.expression.text)) {
1389
+ const callName = node.expression.text;
1390
+ const payloadIndex = callName === "streamTo" ? 1 : 0;
1391
+ const payloadArg = node.arguments[payloadIndex];
1392
+ if (payloadArg) {
1393
+ const argType = checker.getTypeAtLocation(payloadArg);
1394
+ const nonNullableArgType = checker.getNonNullableType(argType);
1395
+ const expanded = expandTypeDetailed(nonNullableArgType, checker);
1396
+ if (expanded.text.trim().length > 0) {
1397
+ types.push(expanded.text);
1398
+ }
1399
+ unresolvedSymbols = mergeUnresolvedSymbols2(unresolvedSymbols, expanded.unresolvedSymbols);
1400
+ unresolvedSymbols = mergeUnresolvedSymbols2(
1401
+ unresolvedSymbols,
1402
+ collectFallbackSymbolsFromTypeText(expanded.text, payloadArg, checker)
1403
+ );
1404
+ }
1405
+ }
1406
+ if (!ts5.isArrowFunction(node) && !ts5.isFunctionExpression(node) && !ts5.isFunctionDeclaration(node)) {
1407
+ ts5.forEachChild(node, visit);
1408
+ }
1409
+ };
1410
+ ts5.forEachChild(funcNode, visit);
1411
+ return { text: unionTypes(types), unresolvedSymbols };
1412
+ };
1413
+ var unionTypes = (types) => {
1414
+ const unique = [...new Set(types)];
1415
+ return unique.length > 0 ? unique.join(" | ") : "";
1416
+ };
1417
+ var getInputTypeFromFile = (filePath) => {
1418
+ return getInputTypeDetailsFromFile(filePath).text;
1419
+ };
1420
+ var getInputTypeDetailsFromFile = (filePath) => {
1421
+ const DEFAULT = "{ }";
1422
+ try {
1423
+ const program = getServerProgram();
1424
+ const sourceFile = program.getSourceFile(filePath);
1425
+ if (!sourceFile) return { text: DEFAULT, unresolvedSymbols: [] };
1426
+ const checker = program.getTypeChecker();
1427
+ const iface = findInterface(sourceFile, "ApiParams");
1428
+ if (!iface) return { text: DEFAULT, unresolvedSymbols: [] };
1429
+ const dataType = getInterfacePropertyType(iface, "data", checker);
1430
+ if (!dataType) return { text: DEFAULT, unresolvedSymbols: [] };
1431
+ const expanded = expandTypeDetailed(dataType, checker);
1432
+ return { text: expanded.text || DEFAULT, unresolvedSymbols: expanded.unresolvedSymbols };
1433
+ } catch (error) {
1434
+ console.error(`[TypeMapGenerator] Error extracting input type from ${filePath}:`, error);
1435
+ return { text: DEFAULT, unresolvedSymbols: [] };
1436
+ }
1437
+ };
1438
+ var getApiStreamPayloadTypeDetailsFromFile = (filePath) => {
1439
+ const DEFAULT = "never";
1440
+ try {
1441
+ const program = getServerProgram();
1442
+ const sourceFile = program.getSourceFile(filePath);
1443
+ if (!sourceFile) return { text: DEFAULT, unresolvedSymbols: [] };
1444
+ const checker = program.getTypeChecker();
1445
+ const mainFn = findMainFunction(sourceFile);
1446
+ if (!mainFn) return { text: DEFAULT, unresolvedSymbols: [] };
1447
+ const details = collectStreamCallPayloadTypeDetails(mainFn, checker);
1448
+ return { text: details.text || DEFAULT, unresolvedSymbols: details.unresolvedSymbols };
1449
+ } catch (error) {
1450
+ console.error(`[TypeMapGenerator] Error extracting API stream payload type from ${filePath}:`, error);
1451
+ return { text: DEFAULT, unresolvedSymbols: [] };
1452
+ }
1453
+ };
1454
+ var getOutputTypeDetailsFromFile = (filePath) => {
1455
+ const DEFAULT = "{ status: string }";
1456
+ try {
1457
+ const program = getServerProgram();
1458
+ const sourceFile = program.getSourceFile(filePath);
1459
+ if (!sourceFile) return { text: DEFAULT, unresolvedSymbols: [] };
1460
+ const checker = program.getTypeChecker();
1461
+ const mainFn = findMainFunction(sourceFile);
1462
+ if (!mainFn) return { text: DEFAULT, unresolvedSymbols: [] };
1463
+ const details = collectReturnObjectTypeDetails(mainFn, checker);
1464
+ return { text: details.text || DEFAULT, unresolvedSymbols: details.unresolvedSymbols };
1465
+ } catch (error) {
1466
+ console.error(`[TypeMapGenerator] Error extracting output type from ${filePath}:`, error);
1467
+ return { text: DEFAULT, unresolvedSymbols: [] };
1468
+ }
1469
+ };
1470
+ var getSyncClientDataType = (filePath) => {
1471
+ return getSyncClientDataTypeDetailsFromFile(filePath).text;
1472
+ };
1473
+ var getSyncClientDataTypeDetailsFromFile = (filePath) => {
1474
+ const DEFAULT = "{ }";
1475
+ try {
1476
+ const program = getServerProgram();
1477
+ const sourceFile = program.getSourceFile(filePath);
1478
+ if (!sourceFile) return { text: DEFAULT, unresolvedSymbols: [] };
1479
+ const checker = program.getTypeChecker();
1480
+ const iface = findInterface(sourceFile, "SyncParams");
1481
+ if (!iface) return { text: DEFAULT, unresolvedSymbols: [] };
1482
+ const dataType = getInterfacePropertyType(iface, "clientInput", checker) ?? getInterfacePropertyType(iface, "clientData", checker);
1483
+ if (!dataType) return { text: DEFAULT, unresolvedSymbols: [] };
1484
+ const expanded = expandTypeDetailed(dataType, checker);
1485
+ return { text: expanded.text || DEFAULT, unresolvedSymbols: expanded.unresolvedSymbols };
1486
+ } catch (error) {
1487
+ console.error(`[TypeMapGenerator] Error extracting sync clientData type from ${filePath}:`, error);
1488
+ return { text: DEFAULT, unresolvedSymbols: [] };
1489
+ }
1490
+ };
1491
+ var getSyncServerStreamPayloadTypeDetailsFromFile = (filePath) => {
1492
+ const DEFAULT = "never";
1493
+ try {
1494
+ const program = getServerProgram();
1495
+ const sourceFile = program.getSourceFile(filePath);
1496
+ if (!sourceFile) return { text: DEFAULT, unresolvedSymbols: [] };
1497
+ const checker = program.getTypeChecker();
1498
+ const mainFn = findMainFunction(sourceFile);
1499
+ if (!mainFn) return { text: DEFAULT, unresolvedSymbols: [] };
1500
+ const details = collectStreamCallPayloadTypeDetails(mainFn, checker);
1501
+ return { text: details.text || DEFAULT, unresolvedSymbols: details.unresolvedSymbols };
1502
+ } catch (error) {
1503
+ console.error(`[TypeMapGenerator] Error extracting sync server stream payload type from ${filePath}:`, error);
1504
+ return { text: DEFAULT, unresolvedSymbols: [] };
1505
+ }
1506
+ };
1507
+ var getSyncServerOutputTypeDetailsFromFile = (filePath) => {
1508
+ const DEFAULT = "{ status: string }";
1509
+ try {
1510
+ const program = getServerProgram();
1511
+ const sourceFile = program.getSourceFile(filePath);
1512
+ if (!sourceFile) return { text: DEFAULT, unresolvedSymbols: [] };
1513
+ const checker = program.getTypeChecker();
1514
+ const mainFn = findMainFunction(sourceFile);
1515
+ if (!mainFn) return { text: DEFAULT, unresolvedSymbols: [] };
1516
+ const details = collectReturnObjectTypeDetails(mainFn, checker);
1517
+ return { text: details.text || DEFAULT, unresolvedSymbols: details.unresolvedSymbols };
1518
+ } catch (error) {
1519
+ console.error(`[TypeMapGenerator] Error extracting sync serverOutput type from ${filePath}:`, error);
1520
+ return { text: DEFAULT, unresolvedSymbols: [] };
1521
+ }
1522
+ };
1523
+ var getSyncClientStreamPayloadTypeDetailsFromFile = (filePath) => {
1524
+ const DEFAULT = "never";
1525
+ try {
1526
+ const program = getServerProgram();
1527
+ const sourceFile = program.getSourceFile(filePath);
1528
+ if (!sourceFile) return { text: DEFAULT, unresolvedSymbols: [] };
1529
+ const checker = program.getTypeChecker();
1530
+ const mainFn = findMainFunction(sourceFile);
1531
+ if (!mainFn) return { text: DEFAULT, unresolvedSymbols: [] };
1532
+ const details = collectStreamCallPayloadTypeDetails(mainFn, checker);
1533
+ return { text: details.text || DEFAULT, unresolvedSymbols: details.unresolvedSymbols };
1534
+ } catch (error) {
1535
+ console.error(`[TypeMapGenerator] Error extracting sync client stream payload type from ${filePath}:`, error);
1536
+ return { text: DEFAULT, unresolvedSymbols: [] };
1537
+ }
1538
+ };
1539
+ var getSyncClientOutputTypeDetailsFromFile = (filePath) => {
1540
+ const DEFAULT = "{ }";
1541
+ try {
1542
+ const program = getServerProgram();
1543
+ const sourceFile = program.getSourceFile(filePath);
1544
+ if (!sourceFile) return { text: DEFAULT, unresolvedSymbols: [] };
1545
+ const checker = program.getTypeChecker();
1546
+ const mainFn = findMainFunction(sourceFile);
1547
+ if (!mainFn) return { text: DEFAULT, unresolvedSymbols: [] };
1548
+ const details = collectReturnObjectTypeDetails(mainFn, checker);
1549
+ return { text: details.text || DEFAULT, unresolvedSymbols: details.unresolvedSymbols };
1550
+ } catch (error) {
1551
+ console.error(`[TypeMapGenerator] Error extracting sync clientOutput type from ${filePath}:`, error);
1552
+ return { text: DEFAULT, unresolvedSymbols: [] };
1553
+ }
1554
+ };
1555
+
1556
+ // src/typeMap/functionsMeta.ts
1557
+ import * as ts7 from "typescript";
1558
+ import fs4 from "fs";
1559
+ import path7 from "path";
1560
+
1561
+ // src/typeMap/typeContext.ts
1562
+ import * as ts6 from "typescript";
1563
+ import path6 from "path";
1564
+ import { getGeneratedSocketTypesPath as getGeneratedSocketTypesPath2 } from "@luckystack/core";
1565
+ var toGeneratedImportPath = (source, filePath) => {
1566
+ if (!source.startsWith(".")) return source;
1567
+ const outputDir = path6.dirname(getGeneratedSocketTypesPath2());
1568
+ const absoluteSource = path6.resolve(path6.dirname(filePath), source);
1569
+ let relPath = path6.relative(outputDir, absoluteSource).replaceAll("\\", "/");
1570
+ relPath = relPath.replace(/\.tsx?$/, "");
1571
+ if (!relPath.startsWith(".")) relPath = `./${relPath}`;
1572
+ return relPath;
1573
+ };
1574
+ var parseFileTypeContext = (content) => {
1575
+ const availableExports = /* @__PURE__ */ new Set();
1576
+ const fileImports = /* @__PURE__ */ new Map();
1577
+ const sourceFile = ts6.createSourceFile(
1578
+ "__temp__.ts",
1579
+ content,
1580
+ ts6.ScriptTarget.Latest,
1581
+ true
1582
+ );
1583
+ for (const statement of sourceFile.statements) {
1584
+ if (ts6.isInterfaceDeclaration(statement) || ts6.isTypeAliasDeclaration(statement) || ts6.isClassDeclaration(statement) || ts6.isEnumDeclaration(statement)) {
1585
+ const hasExport = statement.modifiers?.some((m) => m.kind === ts6.SyntaxKind.ExportKeyword);
1586
+ if (hasExport && statement.name) {
1587
+ availableExports.add(statement.name.text);
1588
+ }
1589
+ continue;
1590
+ }
1591
+ if (!ts6.isImportDeclaration(statement)) continue;
1592
+ const moduleSpecifier = statement.moduleSpecifier;
1593
+ if (!ts6.isStringLiteral(moduleSpecifier)) continue;
1594
+ const source = moduleSpecifier.text;
1595
+ const importClause = statement.importClause;
1596
+ if (!importClause) continue;
1597
+ if (importClause.name) {
1598
+ fileImports.set(importClause.name.text, { source, isDefault: true });
1599
+ }
1600
+ const namedBindings = importClause.namedBindings;
1601
+ if (!namedBindings) continue;
1602
+ if (ts6.isNamespaceImport(namedBindings)) {
1603
+ fileImports.set(namedBindings.name.text, { source, isDefault: true });
1604
+ continue;
1605
+ }
1606
+ if (ts6.isNamedImports(namedBindings)) {
1607
+ for (const specifier of namedBindings.elements) {
1608
+ const localName = specifier.name.text;
1609
+ const originalName = specifier.propertyName?.text ?? localName;
1610
+ fileImports.set(localName, { source, isDefault: false, originalName });
1611
+ }
1612
+ }
1613
+ }
1614
+ return { availableExports, fileImports };
1615
+ };
1616
+ var sanitizeTypeAndCollectImports = ({
1617
+ type,
1618
+ filePath,
1619
+ availableExports,
1620
+ fileImports,
1621
+ collectors,
1622
+ knownGenerics = /* @__PURE__ */ new Set()
1623
+ }) => {
1624
+ const { namedImports: namedImports2, defaultImports: defaultImports2 } = collectors;
1625
+ return type.replaceAll(/\b([A-Z][a-zA-Z0-9_]*)(<[^>]+>)?(\[\])?\b/g, (match, typeName, _generics, isArray) => {
1626
+ const builtins = ["Promise", "Date", "Function", "Array", "Record", "Partial", "Pick", "Omit", "Error", "Map", "Set", "Buffer", "Uint8Array", "Object"];
1627
+ const existingImports = ["SessionLayout"];
1628
+ if (builtins.includes(typeName) || existingImports.includes(typeName) || knownGenerics.has(typeName)) {
1629
+ return match;
1630
+ }
1631
+ const importConfig = fileImports.get(typeName);
1632
+ if (importConfig) {
1633
+ const isInternal = importConfig.source.startsWith(".") || importConfig.source.startsWith("/") || importConfig.source.startsWith("src/") || importConfig.source.startsWith("shared/") || importConfig.source.startsWith("server/");
1634
+ if (!isInternal) {
1635
+ const importPath = toGeneratedImportPath(importConfig.source, filePath);
1636
+ if (importConfig.isDefault) {
1637
+ if (!defaultImports2.has(importPath) || defaultImports2.get(importPath) === typeName) {
1638
+ defaultImports2.set(importPath, typeName);
1639
+ return match;
1640
+ }
1641
+ } else {
1642
+ getOrInit(namedImports2, importPath, () => /* @__PURE__ */ new Set()).add(importConfig.originalName ?? typeName);
1643
+ return match;
1644
+ }
1645
+ }
1646
+ }
1647
+ if (availableExports.has(typeName)) {
1648
+ }
1649
+ return `any${isArray || ""}`;
1650
+ });
1651
+ };
1652
+
1653
+ // src/typeMap/functionsMeta.ts
1654
+ import { getGeneratedSocketTypesPath as getGeneratedSocketTypesPath3, getServerFunctionDirs } from "@luckystack/core";
1655
+ var stripDefaultValues = (params) => {
1656
+ return params.replaceAll(/\s*=(?!>)[^,)]+/g, "");
1657
+ };
1658
+ var stripLineComments = (value) => {
1659
+ const scanner = ts7.createScanner(
1660
+ ts7.ScriptTarget.Latest,
1661
+ /* skipTrivia */
1662
+ false,
1663
+ ts7.LanguageVariant.Standard,
1664
+ value
1665
+ );
1666
+ let result = "";
1667
+ let token = scanner.scan();
1668
+ while (token !== ts7.SyntaxKind.EndOfFileToken) {
1669
+ result += token === ts7.SyntaxKind.SingleLineCommentTrivia ? " " : scanner.getTokenText();
1670
+ token = scanner.scan();
1671
+ }
1672
+ return result;
1673
+ };
1674
+ var normalizeInlineType = (value) => {
1675
+ return stripLineComments(value).replaceAll(/\s+/g, " ").trim();
1676
+ };
1677
+ var simplifyInferredType = (value) => {
1678
+ if (/\bPrismaClient\b/.test(value)) return "PrismaClient";
1679
+ if (/\bRedis\b/.test(value)) return "Redis";
1680
+ return value;
1681
+ };
1682
+ var getGeneratedFileDir = () => path7.dirname(getGeneratedSocketTypesPath3());
1683
+ var relativizeModuleSpecifier = (specifier, sourceFilePath) => {
1684
+ if (!specifier.startsWith("./") && !specifier.startsWith("../")) {
1685
+ return specifier;
1686
+ }
1687
+ const absolute = path7.resolve(path7.dirname(sourceFilePath), specifier);
1688
+ const rel = path7.relative(getGeneratedFileDir(), absolute);
1689
+ const normalized = rel.split(path7.sep).join("/");
1690
+ return normalized.startsWith(".") ? normalized : `./${normalized}`;
1691
+ };
1692
+ var findProgramVariableDeclaration = (sourceFile, exportName) => {
1693
+ for (const statement of sourceFile.statements) {
1694
+ if (!ts7.isVariableStatement(statement)) continue;
1695
+ for (const declaration of statement.declarationList.declarations) {
1696
+ if (ts7.isIdentifier(declaration.name) && declaration.name.text === exportName) {
1697
+ return declaration;
1698
+ }
1699
+ }
1700
+ }
1701
+ return null;
1702
+ };
1703
+ var findProgramTypeDeclaration = (sourceFile, typeName) => {
1704
+ for (const statement of sourceFile.statements) {
1705
+ if (ts7.isInterfaceDeclaration(statement) && statement.name.text === typeName) return statement;
1706
+ if (ts7.isTypeAliasDeclaration(statement) && statement.name.text === typeName) return statement;
1707
+ if (ts7.isClassDeclaration(statement) && statement.name?.text === typeName) return statement;
1708
+ if (ts7.isEnumDeclaration(statement) && statement.name.text === typeName) return statement;
1709
+ }
1710
+ return null;
1711
+ };
1712
+ var resolveLocalExportedTypes = ({
1713
+ type,
1714
+ availableExports,
1715
+ checker,
1716
+ programSource
1717
+ }) => {
1718
+ if (availableExports.size === 0) return type;
1719
+ let resolved = type;
1720
+ for (const exportName of availableExports) {
1721
+ const hasReference = new RegExp(String.raw`\b${exportName}\b`).test(resolved);
1722
+ if (!hasReference) continue;
1723
+ const declaration = findProgramTypeDeclaration(programSource, exportName);
1724
+ if (!declaration) continue;
1725
+ const declarationNode = ts7.isClassDeclaration(declaration) ? declaration.name ?? declaration : ts7.isTypeAliasDeclaration(declaration) ? declaration.type : declaration;
1726
+ const declarationType = checker.getTypeAtLocation(declarationNode);
1727
+ const expanded = normalizeInlineType(expandType(declarationType, checker));
1728
+ resolved = resolved.replaceAll(new RegExp(String.raw`\b${exportName}\b`, "g"), expanded);
1729
+ }
1730
+ return resolved;
1731
+ };
1732
+ var findSourceVariableDeclaration = (sourceFile, exportName) => {
1733
+ for (const statement of sourceFile.statements) {
1734
+ if (!ts7.isVariableStatement(statement)) continue;
1735
+ for (const declaration of statement.declarationList.declarations) {
1736
+ if (ts7.isIdentifier(declaration.name) && declaration.name.text === exportName) {
1737
+ return declaration;
1738
+ }
1739
+ }
1740
+ }
1741
+ return null;
1742
+ };
1743
+ var inferValueTypeForExport = ({
1744
+ exportName,
1745
+ declaration,
1746
+ rawContent,
1747
+ filePath,
1748
+ availableExports,
1749
+ fileImports,
1750
+ collectors,
1751
+ checker,
1752
+ programSource
1753
+ }) => {
1754
+ const resolvedChecker = checker ?? getServerProgram().getTypeChecker();
1755
+ const resolvedSource = programSource ?? getServerProgram().getSourceFile(filePath);
1756
+ if (declaration.type) {
1757
+ const rawType = normalizeInlineType(rawContent.slice(declaration.type.pos, declaration.type.end).trim());
1758
+ const localResolvedType = resolvedSource ? resolveLocalExportedTypes({
1759
+ type: rawType,
1760
+ availableExports,
1761
+ checker: resolvedChecker,
1762
+ programSource: resolvedSource
1763
+ }) : rawType;
1764
+ return sanitizeTypeAndCollectImports({
1765
+ type: localResolvedType,
1766
+ filePath,
1767
+ availableExports,
1768
+ fileImports,
1769
+ collectors
1770
+ });
1771
+ }
1772
+ try {
1773
+ if (!resolvedSource) return "any";
1774
+ const programDeclaration = findProgramVariableDeclaration(resolvedSource, exportName);
1775
+ if (!programDeclaration) return "any";
1776
+ const inferred = resolvedChecker.typeToString(resolvedChecker.getTypeAtLocation(programDeclaration.name));
1777
+ const simplified = simplifyInferredType(normalizeInlineType(inferred));
1778
+ const localResolvedType = resolveLocalExportedTypes({
1779
+ type: simplified,
1780
+ availableExports,
1781
+ checker: resolvedChecker,
1782
+ programSource: resolvedSource
1783
+ });
1784
+ return sanitizeTypeAndCollectImports({
1785
+ type: localResolvedType,
1786
+ filePath,
1787
+ availableExports,
1788
+ fileImports,
1789
+ collectors
1790
+ });
1791
+ } catch {
1792
+ return "any";
1793
+ }
1794
+ };
1795
+ var extractSignatureFromNode = (node, rawContent, filePath, availableExports, fileImports, collectors, checker, programSource) => {
1796
+ const knownGenerics = /* @__PURE__ */ new Set();
1797
+ if (node.typeParameters) {
1798
+ for (const typeParam of node.typeParameters) {
1799
+ knownGenerics.add(typeParam.name.text);
1800
+ }
1801
+ }
1802
+ const isAsync = node.modifiers?.some((m) => m.kind === ts7.SyntaxKind.AsyncKeyword) ?? false;
1803
+ const genericsClause = node.typeParameters ? `<${normalizeInlineType(rawContent.slice(node.typeParameters.pos, node.typeParameters.end))}>` : "";
1804
+ const rawParams = node.parameters.map((p) => normalizeInlineType(rawContent.slice(p.pos, p.end).trim())).join(", ");
1805
+ const cleanParams = normalizeInlineType(stripDefaultValues(`(${rawParams})`));
1806
+ const localResolvedParams = checker && programSource ? resolveLocalExportedTypes({
1807
+ type: cleanParams,
1808
+ availableExports,
1809
+ checker,
1810
+ programSource
1811
+ }) : cleanParams;
1812
+ const sanitizedParams = sanitizeTypeAndCollectImports({
1813
+ type: localResolvedParams,
1814
+ filePath,
1815
+ availableExports,
1816
+ fileImports,
1817
+ knownGenerics,
1818
+ collectors
1819
+ });
1820
+ let returnTypeStr = isAsync ? "Promise<unknown>" : "unknown";
1821
+ if (node.type) {
1822
+ const rawReturnType = normalizeInlineType(rawContent.slice(node.type.pos, node.type.end).trim());
1823
+ const localResolvedReturnType = checker && programSource ? resolveLocalExportedTypes({
1824
+ type: rawReturnType,
1825
+ availableExports,
1826
+ checker,
1827
+ programSource
1828
+ }) : rawReturnType;
1829
+ returnTypeStr = sanitizeTypeAndCollectImports({
1830
+ type: localResolvedReturnType,
1831
+ filePath,
1832
+ availableExports,
1833
+ fileImports,
1834
+ knownGenerics,
1835
+ collectors
1836
+ });
1837
+ if (isAsync && !returnTypeStr.startsWith("Promise")) {
1838
+ returnTypeStr = `Promise<${returnTypeStr}>`;
1839
+ }
1840
+ }
1841
+ return `${genericsClause}${sanitizedParams} => ${returnTypeStr}`;
1842
+ };
1843
+ var findSignatureForExport = (name, sourceFile, rawContent, filePath, availableExports, fileImports, collectors, checker, programSource) => {
1844
+ for (const statement of sourceFile.statements) {
1845
+ if (ts7.isVariableStatement(statement)) {
1846
+ for (const decl of statement.declarationList.declarations) {
1847
+ if (!ts7.isIdentifier(decl.name) || decl.name.text !== name || !decl.initializer) continue;
1848
+ if (ts7.isArrowFunction(decl.initializer) || ts7.isFunctionExpression(decl.initializer)) {
1849
+ return extractSignatureFromNode(decl.initializer, rawContent, filePath, availableExports, fileImports, collectors, checker, programSource);
1850
+ }
1851
+ }
1852
+ }
1853
+ if (ts7.isFunctionDeclaration(statement) && statement.name?.text === name) {
1854
+ return extractSignatureFromNode(statement, rawContent, filePath, availableExports, fileImports, collectors, checker, programSource);
1855
+ }
1856
+ }
1857
+ return "any";
1858
+ };
1859
+ var parseFunctionFile = (fullPath, collectors) => {
1860
+ try {
1861
+ const rawContent = fs4.readFileSync(fullPath, "utf8");
1862
+ const sourceFile = ts7.createSourceFile(fullPath, rawContent, ts7.ScriptTarget.Latest, true);
1863
+ const { availableExports, fileImports } = parseFileTypeContext(rawContent);
1864
+ const program = getServerProgram();
1865
+ const checker = program.getTypeChecker();
1866
+ const programSource = program.getSourceFile(fullPath);
1867
+ const exports = /* @__PURE__ */ new Map();
1868
+ let defaultExportName = null;
1869
+ let wildcardReExport = null;
1870
+ for (const statement of sourceFile.statements) {
1871
+ const hasExport = statement.modifiers?.some((m) => m.kind === ts7.SyntaxKind.ExportKeyword);
1872
+ if (ts7.isVariableStatement(statement) && hasExport) {
1873
+ for (const decl of statement.declarationList.declarations) {
1874
+ if (ts7.isIdentifier(decl.name)) {
1875
+ const exportName = decl.name.text;
1876
+ if (decl.initializer && (ts7.isArrowFunction(decl.initializer) || ts7.isFunctionExpression(decl.initializer))) {
1877
+ exports.set(exportName, findSignatureForExport(exportName, sourceFile, rawContent, fullPath, availableExports, fileImports, collectors, checker, programSource ?? void 0));
1878
+ continue;
1879
+ }
1880
+ exports.set(exportName, inferValueTypeForExport({
1881
+ exportName,
1882
+ declaration: decl,
1883
+ rawContent,
1884
+ filePath: fullPath,
1885
+ availableExports,
1886
+ fileImports,
1887
+ collectors,
1888
+ checker,
1889
+ programSource: programSource ?? void 0
1890
+ }));
1891
+ }
1892
+ }
1893
+ }
1894
+ if (ts7.isFunctionDeclaration(statement) && hasExport && statement.name) {
1895
+ exports.set(statement.name.text, findSignatureForExport(statement.name.text, sourceFile, rawContent, fullPath, availableExports, fileImports, collectors, checker, programSource ?? void 0));
1896
+ }
1897
+ if (ts7.isExportDeclaration(statement) && statement.exportClause && ts7.isNamedExports(statement.exportClause)) {
1898
+ const moduleSpecifier = statement.moduleSpecifier && ts7.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : null;
1899
+ for (const specifier of statement.exportClause.elements) {
1900
+ const exportName = specifier.name.text;
1901
+ const originalName = specifier.propertyName ? specifier.propertyName.text : exportName;
1902
+ if (moduleSpecifier) {
1903
+ const resolvedSpecifier = relativizeModuleSpecifier(moduleSpecifier, fullPath);
1904
+ exports.set(exportName, `typeof import('${resolvedSpecifier}')['${originalName}']`);
1905
+ continue;
1906
+ }
1907
+ const signature = findSignatureForExport(originalName, sourceFile, rawContent, fullPath, availableExports, fileImports, collectors, checker, programSource ?? void 0);
1908
+ if (signature !== "any") {
1909
+ exports.set(exportName, signature);
1910
+ continue;
1911
+ }
1912
+ const exportDeclaration = findSourceVariableDeclaration(sourceFile, originalName);
1913
+ if (exportDeclaration && ts7.isIdentifier(exportDeclaration.name)) {
1914
+ exports.set(exportName, inferValueTypeForExport({
1915
+ exportName: originalName,
1916
+ declaration: exportDeclaration,
1917
+ rawContent,
1918
+ filePath: fullPath,
1919
+ availableExports,
1920
+ fileImports,
1921
+ collectors,
1922
+ checker,
1923
+ programSource: programSource ?? void 0
1924
+ }));
1925
+ continue;
1926
+ }
1927
+ exports.set(exportName, "any");
1928
+ }
1929
+ }
1930
+ if (ts7.isExportAssignment(statement) && !statement.isExportEquals && ts7.isIdentifier(statement.expression)) {
1931
+ defaultExportName = statement.expression.text;
1932
+ }
1933
+ if (ts7.isExportDeclaration(statement) && !statement.exportClause && statement.moduleSpecifier && ts7.isStringLiteral(statement.moduleSpecifier)) {
1934
+ wildcardReExport = relativizeModuleSpecifier(statement.moduleSpecifier.text, fullPath);
1935
+ }
1936
+ }
1937
+ return { kind: "file", exports, defaultExportName, wildcardReExport, sourcePath: fullPath };
1938
+ } catch (error) {
1939
+ console.error(`[TypeMapGenerator] Error parsing functions file ${fullPath}:`, error);
1940
+ return null;
1941
+ }
1942
+ };
1943
+ var walkDirToIR = (dir, collectors) => {
1944
+ if (!fs4.existsSync(dir)) return null;
1945
+ const entries = fs4.readdirSync(dir, { withFileTypes: true });
1946
+ const children = /* @__PURE__ */ new Map();
1947
+ for (const entry of entries) {
1948
+ const fullPath = path7.join(dir, entry.name);
1949
+ if (entry.isDirectory()) {
1950
+ const subDir = walkDirToIR(fullPath, collectors);
1951
+ if (subDir && subDir.children.size > 0) {
1952
+ children.set(entry.name, subDir);
1953
+ }
1954
+ continue;
1955
+ }
1956
+ if (!entry.isFile() || !entry.name.endsWith(".ts")) continue;
1957
+ const fileName = entry.name.replace(".ts", "");
1958
+ const parsed = parseFunctionFile(fullPath, collectors);
1959
+ if (parsed && (parsed.exports.size > 0 || parsed.defaultExportName !== null || parsed.wildcardReExport !== null)) {
1960
+ children.set(fileName, parsed);
1961
+ }
1962
+ }
1963
+ return { kind: "dir", children, sourcePath: dir };
1964
+ };
1965
+ var formatConflict = (keyPath, a, b) => {
1966
+ const dottedKey = keyPath.join(".");
1967
+ return `[function-injection] Conflict at \`functions.${dottedKey}\`: defined in both \`${a.sourcePath}\` and \`${b.sourcePath}\`. Delete one \u2014 \`shared/\` is the canonical location for framework re-exports.`;
1968
+ };
1969
+ var mergeIR = (target, source, prefix = []) => {
1970
+ for (const [name, sourceChild] of source.children) {
1971
+ const targetChild = target.children.get(name);
1972
+ if (!targetChild) {
1973
+ target.children.set(name, sourceChild);
1974
+ continue;
1975
+ }
1976
+ const keyPath = [...prefix, name];
1977
+ if (targetChild.kind !== sourceChild.kind) {
1978
+ throw new Error(formatConflict(keyPath, targetChild, sourceChild));
1979
+ }
1980
+ if (targetChild.kind === "file" || sourceChild.kind === "file") {
1981
+ throw new Error(formatConflict(keyPath, targetChild, sourceChild));
1982
+ }
1983
+ mergeIR(targetChild, sourceChild, keyPath);
1984
+ }
1985
+ };
1986
+ var serializeIRDir = (dir, indent) => {
1987
+ let output = "";
1988
+ for (const [name, child] of dir.children) {
1989
+ if (child.kind === "dir") {
1990
+ const subOutput = serializeIRDir(child, `${indent} `);
1991
+ if (subOutput.trim()) {
1992
+ output += `${indent}${name}: {
1993
+ ${subOutput}${indent}};
1994
+ `;
1995
+ }
1996
+ continue;
1997
+ }
1998
+ const exportsCopy = new Map(child.exports);
1999
+ const defaultExportName = child.defaultExportName;
2000
+ const defaultSig = defaultExportName ? exportsCopy.get(defaultExportName) : void 0;
2001
+ if (defaultSig && defaultExportName) exportsCopy.delete(defaultExportName);
2002
+ if (child.wildcardReExport && exportsCopy.size === 0 && !defaultSig) {
2003
+ output += `${indent}${name}: typeof import('${child.wildcardReExport}');
2004
+ `;
2005
+ continue;
2006
+ }
2007
+ if (!defaultSig && exportsCopy.size === 1 && exportsCopy.has("default")) {
2008
+ const reExportSig = exportsCopy.get("default");
2009
+ if (reExportSig) {
2010
+ exportsCopy.delete("default");
2011
+ exportsCopy.set(name, reExportSig);
2012
+ }
2013
+ }
2014
+ let fileOutput = "";
2015
+ for (const [exportName, sig] of exportsCopy) {
2016
+ fileOutput += `${indent} ${exportName}: ${sig};
2017
+ `;
2018
+ }
2019
+ if (defaultSig && !fileOutput.trim()) {
2020
+ fileOutput += `${indent} ${name}: ${defaultSig};
2021
+ `;
2022
+ }
2023
+ if (fileOutput) {
2024
+ output += `${indent}${name}: {
2025
+ ${fileOutput}${indent}};
2026
+ `;
2027
+ }
2028
+ }
2029
+ return output;
2030
+ };
2031
+ var generateServerFunctions = (collectors) => {
2032
+ const dirs = getServerFunctionDirs();
2033
+ if (dirs.length === 0) return "";
2034
+ const merged = { kind: "dir", children: /* @__PURE__ */ new Map(), sourcePath: "<merged>" };
2035
+ for (const dir of dirs) {
2036
+ const ir = walkDirToIR(dir, collectors);
2037
+ if (!ir) continue;
2038
+ mergeIR(merged, ir);
2039
+ }
2040
+ return serializeIRDir(merged, " ");
2041
+ };
2042
+
2043
+ // src/typeMapGenerator.ts
2044
+ import { getSrcDir } from "@luckystack/core";
2045
+
2046
+ // src/routeNamingValidation.ts
2047
+ import fs5 from "fs";
2048
+ import path8 from "path";
2049
+ import { ROOT_DIR as ROOT_DIR4, validatePagePath } from "@luckystack/core";
2050
+ var normalizePath = (value) => {
2051
+ return value.replaceAll("\\", "/");
2052
+ };
2053
+ var toRel = (absolute) => path8.relative(ROOT_DIR4, absolute).replaceAll("\\", "/");
2054
+ var walkRouteFiles = (dir, results = []) => {
2055
+ const entries = fs5.readdirSync(dir, { withFileTypes: true });
2056
+ const apiSeg = apiMarkerSegment();
2057
+ const syncSeg = syncMarkerSegment();
2058
+ const { ignore } = getRoutingRules();
2059
+ for (const entry of entries) {
2060
+ const fullPath = path8.join(dir, entry.name);
2061
+ const normalizedFullPath = normalizePath(fullPath);
2062
+ if (ignore(toRel(fullPath))) continue;
2063
+ if (entry.isDirectory()) {
2064
+ if (entry.name.startsWith(".") || entry.name === "node_modules") {
2065
+ continue;
2066
+ }
2067
+ walkRouteFiles(fullPath, results);
2068
+ continue;
2069
+ }
2070
+ if (!entry.isFile() || !entry.name.endsWith(".ts")) {
2071
+ continue;
2072
+ }
2073
+ if (isRouteTestFile(entry.name)) {
2074
+ continue;
2075
+ }
2076
+ if (normalizedFullPath.includes(apiSeg) || normalizedFullPath.includes(syncSeg)) {
2077
+ results.push(fullPath);
2078
+ }
2079
+ }
2080
+ return results;
2081
+ };
2082
+ var getFileRouteToken = ({
2083
+ normalizedFilePath,
2084
+ marker
2085
+ }) => {
2086
+ const markerIndex = normalizedFilePath.indexOf(marker);
2087
+ if (markerIndex === -1) {
2088
+ return "";
2089
+ }
2090
+ const tokenStart = markerIndex + marker.length;
2091
+ return normalizedFilePath.slice(tokenStart, normalizedFilePath.length - ".ts".length);
2092
+ };
2093
+ var validateRouteFilePath = (filePath) => {
2094
+ const issues = [];
2095
+ const normalizedFilePath = normalizePath(path8.resolve(filePath));
2096
+ const fileName = path8.basename(filePath);
2097
+ const apiSeg = apiMarkerSegment();
2098
+ const syncSeg = syncMarkerSegment();
2099
+ if (normalizedFilePath.includes(apiSeg)) {
2100
+ const apiRouteToken = getFileRouteToken({ normalizedFilePath, marker: apiSeg });
2101
+ if (apiRouteToken.includes("/")) {
2102
+ issues.push({
2103
+ kind: "api",
2104
+ filePath: normalizedFilePath,
2105
+ reason: 'API route token cannot contain nested path segments ("/").',
2106
+ expected: ROUTE_NAMING_RULES.api
2107
+ });
2108
+ }
2109
+ if (!isVersionedApiFileName(fileName)) {
2110
+ issues.push({
2111
+ kind: "api",
2112
+ filePath: normalizedFilePath,
2113
+ reason: "API filename does not match versioned naming.",
2114
+ expected: ROUTE_NAMING_RULES.api
2115
+ });
2116
+ }
2117
+ }
2118
+ if (normalizedFilePath.includes(syncSeg)) {
2119
+ const syncRouteToken = getFileRouteToken({ normalizedFilePath, marker: syncSeg });
2120
+ if (syncRouteToken.includes("/")) {
2121
+ issues.push({
2122
+ kind: "sync",
2123
+ filePath: normalizedFilePath,
2124
+ reason: 'Sync route token cannot contain nested path segments ("/").',
2125
+ expected: `${ROUTE_NAMING_RULES.syncServer} or ${ROUTE_NAMING_RULES.syncClient}`
2126
+ });
2127
+ }
2128
+ if (!isVersionedSyncFileName(fileName)) {
2129
+ issues.push({
2130
+ kind: "sync",
2131
+ filePath: normalizedFilePath,
2132
+ reason: "Sync filename does not match versioned naming.",
2133
+ expected: `${ROUTE_NAMING_RULES.syncServer} or ${ROUTE_NAMING_RULES.syncClient}`
2134
+ });
2135
+ }
2136
+ }
2137
+ return issues;
2138
+ };
2139
+ var resolveApiRouteKey = ({
2140
+ srcDir,
2141
+ filePath
2142
+ }) => {
2143
+ const rules2 = getRoutingRules();
2144
+ const relativePath = normalizePath(path8.relative(srcDir, filePath));
2145
+ const segments = relativePath.split("/");
2146
+ const apiIndex = segments.indexOf(rules2.apiMarker);
2147
+ if (apiIndex === -1 || apiIndex === segments.length - 1) {
2148
+ return null;
2149
+ }
2150
+ const pageLocation = segments.slice(0, apiIndex).join("/");
2151
+ const apiFilePath = segments.slice(apiIndex + 1).join("/");
2152
+ const rawApiName = apiFilePath.replace(/\.ts$/, "");
2153
+ const versionMatch = rawApiName.match(rules2.apiVersionRegex);
2154
+ if (!versionMatch) {
2155
+ return null;
2156
+ }
2157
+ const version = `v${versionMatch[1]}`;
2158
+ const apiName = rawApiName.replace(rules2.apiVersionRegex, "");
2159
+ const mappedPageLocation = pageLocation || "system";
2160
+ return `api/${mappedPageLocation}/${apiName}/${version}`;
2161
+ };
2162
+ var resolveSyncRouteKey = ({
2163
+ srcDir,
2164
+ filePath
2165
+ }) => {
2166
+ const rules2 = getRoutingRules();
2167
+ const relativePath = normalizePath(path8.relative(srcDir, filePath));
2168
+ const segments = relativePath.split("/");
2169
+ const syncIndex = segments.indexOf(rules2.syncMarker);
2170
+ if (syncIndex === -1 || syncIndex === segments.length - 1) {
2171
+ return null;
2172
+ }
2173
+ const pageLocation = segments.slice(0, syncIndex).join("/");
2174
+ const syncFilePath = segments.slice(syncIndex + 1).join("/");
2175
+ const rawSyncName = syncFilePath.replace(/\.ts$/, "");
2176
+ const syncMatch = rawSyncName.match(rules2.syncVersionRegex);
2177
+ if (!syncMatch) {
2178
+ return null;
2179
+ }
2180
+ const kind = syncMatch[1];
2181
+ const version = `v${syncMatch[2]}`;
2182
+ const syncName = rawSyncName.replace(rules2.syncVersionRegex, "");
2183
+ const routeBaseKey = pageLocation ? `sync/${pageLocation}/${syncName}/${version}` : `sync/${syncName}/${version}`;
2184
+ return `${routeBaseKey}_${kind}`;
2185
+ };
2186
+ var collectInvalidRouteNamingIssues = (srcDir) => {
2187
+ const allRouteFiles = walkRouteFiles(srcDir);
2188
+ const issues = [];
2189
+ for (const filePath of allRouteFiles) {
2190
+ issues.push(...validateRouteFilePath(filePath));
2191
+ }
2192
+ return issues.toSorted((a, b) => a.filePath.localeCompare(b.filePath));
2193
+ };
2194
+ var collectDuplicateNormalizedRouteKeyIssues = (srcDir) => {
2195
+ const allRouteFiles = walkRouteFiles(srcDir);
2196
+ const routeKeyToFilePaths = /* @__PURE__ */ new Map();
2197
+ const routeKeyKinds = /* @__PURE__ */ new Map();
2198
+ const apiSeg = apiMarkerSegment();
2199
+ const syncSeg = syncMarkerSegment();
2200
+ for (const filePath of allRouteFiles) {
2201
+ const normalizedFilePath = normalizePath(path8.resolve(filePath));
2202
+ if (normalizedFilePath.includes(apiSeg)) {
2203
+ const routeKey = resolveApiRouteKey({ srcDir, filePath });
2204
+ if (!routeKey) {
2205
+ continue;
2206
+ }
2207
+ getOrInit(routeKeyToFilePaths, routeKey, () => []).push(normalizedFilePath);
2208
+ routeKeyKinds.set(routeKey, "api");
2209
+ continue;
2210
+ }
2211
+ if (normalizedFilePath.includes(syncSeg)) {
2212
+ const routeKey = resolveSyncRouteKey({ srcDir, filePath });
2213
+ if (!routeKey) {
2214
+ continue;
2215
+ }
2216
+ getOrInit(routeKeyToFilePaths, routeKey, () => []).push(normalizedFilePath);
2217
+ routeKeyKinds.set(routeKey, "sync");
2218
+ }
2219
+ }
2220
+ const issues = [];
2221
+ for (const [routeKey, filePaths] of routeKeyToFilePaths.entries()) {
2222
+ if (filePaths.length < 2) {
2223
+ continue;
2224
+ }
2225
+ issues.push({
2226
+ kind: routeKeyKinds.get(routeKey) ?? "api",
2227
+ routeKey,
2228
+ filePaths: filePaths.toSorted((a, b) => a.localeCompare(b))
2229
+ });
2230
+ }
2231
+ return issues.toSorted((a, b) => a.routeKey.localeCompare(b.routeKey));
2232
+ };
2233
+ var formatRouteNamingIssues = ({
2234
+ issues,
2235
+ context
2236
+ }) => {
2237
+ const plural = issues.length === 1 ? "" : "s";
2238
+ const header = `[RouteNaming] Found ${issues.length} invalid API/sync route file${plural} while ${context}.`;
2239
+ const details = issues.map((issue, index) => {
2240
+ return `${index + 1}. [${issue.kind.toUpperCase()}] ${issue.filePath}
2241
+ reason: ${issue.reason}
2242
+ expected: ${issue.expected}`;
2243
+ }).join("\n");
2244
+ return `${header}
2245
+ ${details}`;
2246
+ };
2247
+ var formatDuplicateRouteKeyIssues = ({
2248
+ issues,
2249
+ context
2250
+ }) => {
2251
+ const plural = issues.length === 1 ? "" : "s";
2252
+ const header = `[RouteNaming] Found ${issues.length} duplicate normalized route key${plural} while ${context}.`;
2253
+ const details = issues.map((issue, index) => {
2254
+ const fileList = issue.filePaths.map((filePath) => ` - ${filePath}`).join("\n");
2255
+ return `${index + 1}. [${issue.kind.toUpperCase()}] ${issue.routeKey}
2256
+ files:
2257
+ ${fileList}`;
2258
+ }).join("\n");
2259
+ return `${header}
2260
+ ${details}`;
2261
+ };
2262
+ var assertValidRouteNaming = ({
2263
+ srcDir,
2264
+ context
2265
+ }) => {
2266
+ const issues = collectInvalidRouteNamingIssues(srcDir);
2267
+ if (issues.length === 0) {
2268
+ return;
2269
+ }
2270
+ throw new Error(formatRouteNamingIssues({ issues, context }));
2271
+ };
2272
+ var assertNoDuplicateNormalizedRouteKeys = ({
2273
+ srcDir,
2274
+ context
2275
+ }) => {
2276
+ const issues = collectDuplicateNormalizedRouteKeyIssues(srcDir);
2277
+ if (issues.length === 0) {
2278
+ return;
2279
+ }
2280
+ throw new Error(formatDuplicateRouteKeyIssues({ issues, context }));
2281
+ };
2282
+ var walkPageFiles = (dir, results = []) => {
2283
+ let entries;
2284
+ try {
2285
+ entries = fs5.readdirSync(dir, { withFileTypes: true });
2286
+ } catch {
2287
+ return results;
2288
+ }
2289
+ for (const entry of entries) {
2290
+ const fullPath = path8.join(dir, entry.name);
2291
+ if (entry.isDirectory()) {
2292
+ if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
2293
+ walkPageFiles(fullPath, results);
2294
+ continue;
2295
+ }
2296
+ if (!entry.isFile()) continue;
2297
+ if (entry.name === "page.tsx" || entry.name === "page.jsx") {
2298
+ results.push(fullPath);
2299
+ }
2300
+ }
2301
+ return results;
2302
+ };
2303
+ var collectDuplicatePageRoutes = (srcDir) => {
2304
+ const pageFiles = walkPageFiles(srcDir);
2305
+ const routeToFiles = /* @__PURE__ */ new Map();
2306
+ for (const absoluteFilePath of pageFiles) {
2307
+ const relative = normalizePath(path8.relative(srcDir, absoluteFilePath));
2308
+ const result = validatePagePath(relative);
2309
+ if (!result.valid || !result.route) continue;
2310
+ const list = routeToFiles.get(result.route) ?? [];
2311
+ list.push(normalizePath(path8.relative(ROOT_DIR4, absoluteFilePath)));
2312
+ routeToFiles.set(result.route, list);
2313
+ }
2314
+ const issues = [];
2315
+ for (const [route, filePaths] of routeToFiles) {
2316
+ if (filePaths.length > 1) {
2317
+ issues.push({ route, filePaths });
2318
+ }
2319
+ }
2320
+ return issues;
2321
+ };
2322
+ var formatDuplicatePageRouteIssues = ({
2323
+ issues,
2324
+ context
2325
+ }) => {
2326
+ const plural = issues.length === 1 ? "" : "s";
2327
+ const header = `[RouteNaming] Found ${issues.length} duplicate page route${plural} while ${context}.`;
2328
+ const details = issues.map((issue, index) => {
2329
+ const fileList = issue.filePaths.map((filePath) => ` - ${filePath}`).join("\n");
2330
+ return `${index + 1}. ${issue.route}
2331
+ files:
2332
+ ${fileList}
2333
+ fix: rename or move one of the files so only one page.tsx resolves to "${issue.route}". Remember that "_<folder>" segments are stripped from the URL (invisible-parent rule).`;
2334
+ }).join("\n");
2335
+ return `${header}
2336
+ ${details}`;
2337
+ };
2338
+ var assertNoDuplicatePageRoutes = ({
2339
+ srcDir,
2340
+ context
2341
+ }) => {
2342
+ const issues = collectDuplicatePageRoutes(srcDir);
2343
+ if (issues.length === 0) return;
2344
+ throw new Error(formatDuplicatePageRouteIssues({ issues, context }));
2345
+ };
2346
+
2347
+ // src/typeMapGenerator.ts
2348
+ var namedImports = /* @__PURE__ */ new Map();
2349
+ var defaultImports = /* @__PURE__ */ new Map();
2350
+ var generateTypeMapFile = (options = {}) => {
2351
+ const { quiet = false } = options;
2352
+ assertValidRouteNaming({
2353
+ srcDir: getSrcDir(),
2354
+ context: "generating API/sync type maps"
2355
+ });
2356
+ assertNoDuplicateNormalizedRouteKeys({
2357
+ srcDir: getSrcDir(),
2358
+ context: "generating API/sync type maps"
2359
+ });
2360
+ assertNoDuplicatePageRoutes({
2361
+ srcDir: getSrcDir(),
2362
+ context: "generating API/sync type maps"
2363
+ });
2364
+ invalidateProgramCache();
2365
+ namedImports.clear();
2366
+ defaultImports.clear();
2367
+ const apiFiles = findAllApiFiles(getSrcDir());
2368
+ const typesByPage = /* @__PURE__ */ new Map();
2369
+ const unresolvedTypeAliases = /* @__PURE__ */ new Set();
2370
+ if (!quiet) {
2371
+ console.log(" \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
2372
+ console.log(" \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
2373
+ console.log(`[TypeMapGenerator] Found ${apiFiles.length} API files`, "cyan");
2374
+ }
2375
+ for (const filePath of apiFiles) {
2376
+ const pagePath = extractPagePath(filePath);
2377
+ const apiName = extractApiName(filePath);
2378
+ const apiVersion = extractApiVersion(filePath);
2379
+ if (!pagePath || !apiName) continue;
2380
+ const inputTypeResult = getInputTypeDetailsFromFile(filePath);
2381
+ const outputTypeResult = getOutputTypeDetailsFromFile(filePath);
2382
+ const streamTypeResult = getApiStreamPayloadTypeDetailsFromFile(filePath);
2383
+ const inputType = inputTypeResult.text;
2384
+ const outputType = outputTypeResult.text;
2385
+ const streamType = streamTypeResult.text;
2386
+ const httpMethod = extractHttpMethod(filePath, apiName);
2387
+ const rateLimit = extractRateLimit(filePath);
2388
+ const auth = extractAuth(filePath);
2389
+ const meta = extractDocsMeta(filePath);
2390
+ for (const symbol of [...inputTypeResult.unresolvedSymbols, ...outputTypeResult.unresolvedSymbols, ...streamTypeResult.unresolvedSymbols]) {
2391
+ if (!symbol.importPath) {
2392
+ unresolvedTypeAliases.add(symbol.name);
2393
+ console.error(`[TypeMapGenerator] Unresolved API type (${pagePath}/${apiName}/${apiVersion}): ${symbol.name}`);
2394
+ continue;
2395
+ }
2396
+ getOrInit(namedImports, symbol.importPath, () => /* @__PURE__ */ new Set()).add(symbol.name);
2397
+ }
2398
+ if (!quiet) {
2399
+ console.log(`[TypeMapGenerator] API: ${pagePath}/${apiName}/${apiVersion} (${httpMethod}${rateLimit === void 0 ? "" : `, rateLimit: ${rateLimit}`})`);
2400
+ }
2401
+ getOrInit(typesByPage, pagePath, () => /* @__PURE__ */ new Map()).set(`${apiName}@${apiVersion}`, { input: inputType, output: outputType, stream: streamType, method: httpMethod, rateLimit, auth, version: apiVersion, ...meta ? { meta } : {} });
2402
+ }
2403
+ const syncServerFiles = findAllSyncServerFiles(getSrcDir());
2404
+ const syncClientFiles = findAllSyncClientFiles(getSrcDir());
2405
+ const syncTypesByPage = /* @__PURE__ */ new Map();
2406
+ if (!quiet) {
2407
+ console.log(" \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
2408
+ console.log(" \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
2409
+ console.log(`[TypeMapGenerator] Found ${syncServerFiles.length} Sync server files, ${syncClientFiles.length} Sync client files`, "cyan");
2410
+ }
2411
+ const allSyncs = /* @__PURE__ */ new Map();
2412
+ for (const serverFile of syncServerFiles) {
2413
+ const pagePath = extractSyncPagePath(serverFile);
2414
+ const syncName = extractSyncName(serverFile);
2415
+ const syncVersion = extractSyncVersion(serverFile);
2416
+ if (!pagePath || !syncName) continue;
2417
+ const key = `${pagePath}/${syncName}/${syncVersion}`;
2418
+ const existing = allSyncs.get(key) ?? { pagePath, syncName };
2419
+ existing.serverFile = serverFile;
2420
+ allSyncs.set(key, existing);
2421
+ }
2422
+ for (const clientFile of syncClientFiles) {
2423
+ const pagePath = extractSyncPagePath(clientFile);
2424
+ const syncName = extractSyncName(clientFile);
2425
+ const syncVersion = extractSyncVersion(clientFile);
2426
+ if (!pagePath || !syncName) continue;
2427
+ const key = `${pagePath}/${syncName}/${syncVersion}`;
2428
+ const existing = allSyncs.get(key) ?? { pagePath, syncName };
2429
+ existing.clientFile = clientFile;
2430
+ allSyncs.set(key, existing);
2431
+ }
2432
+ for (const [, { pagePath, syncName, serverFile, clientFile }] of allSyncs) {
2433
+ const syncVersion = extractSyncVersion(serverFile ?? clientFile ?? "");
2434
+ const clientInputTypeResult = serverFile ? getSyncClientDataTypeDetailsFromFile(serverFile) : clientFile ? getSyncClientDataTypeDetailsFromFile(clientFile) : { text: "{ }", unresolvedSymbols: [] };
2435
+ const serverOutputTypeResult = serverFile ? getSyncServerOutputTypeDetailsFromFile(serverFile) : { text: "{ }", unresolvedSymbols: [] };
2436
+ const clientOutputTypeResult = clientFile ? getSyncClientOutputTypeDetailsFromFile(clientFile) : { text: "{ }", unresolvedSymbols: [] };
2437
+ const serverStreamTypeResult = serverFile ? getSyncServerStreamPayloadTypeDetailsFromFile(serverFile) : { text: "never", unresolvedSymbols: [] };
2438
+ const clientStreamTypeResult = clientFile ? getSyncClientStreamPayloadTypeDetailsFromFile(clientFile) : { text: "never", unresolvedSymbols: [] };
2439
+ const clientInputType = clientInputTypeResult.text;
2440
+ const serverOutputType = serverOutputTypeResult.text;
2441
+ const clientOutputType = clientOutputTypeResult.text;
2442
+ const serverStreamType = serverStreamTypeResult.text;
2443
+ const clientStreamType = clientStreamTypeResult.text;
2444
+ const allSyncUnresolvedSymbols = [
2445
+ ...clientInputTypeResult.unresolvedSymbols,
2446
+ ...serverOutputTypeResult.unresolvedSymbols,
2447
+ ...clientOutputTypeResult.unresolvedSymbols,
2448
+ ...serverStreamTypeResult.unresolvedSymbols,
2449
+ ...clientStreamTypeResult.unresolvedSymbols
2450
+ ];
2451
+ for (const symbol of allSyncUnresolvedSymbols) {
2452
+ if (!symbol.importPath) {
2453
+ unresolvedTypeAliases.add(symbol.name);
2454
+ console.error(`[TypeMapGenerator] Unresolved Sync type (${pagePath}/${syncName}/${syncVersion}): ${symbol.name}`);
2455
+ continue;
2456
+ }
2457
+ getOrInit(namedImports, symbol.importPath, () => /* @__PURE__ */ new Set()).add(symbol.name);
2458
+ }
2459
+ if (!quiet) {
2460
+ console.log(`[TypeMapGenerator] Sync: ${pagePath}/${syncName}/${syncVersion} (server: ${!!serverFile}, client: ${!!clientFile})`);
2461
+ }
2462
+ const syncMeta = serverFile ? extractDocsMeta(serverFile) : clientFile ? extractDocsMeta(clientFile) : void 0;
2463
+ getOrInit(syncTypesByPage, pagePath, () => /* @__PURE__ */ new Map()).set(`${syncName}@${syncVersion}`, {
2464
+ clientInput: clientInputType,
2465
+ serverOutput: serverOutputType,
2466
+ clientOutput: clientOutputType,
2467
+ serverStream: serverStreamType,
2468
+ clientStream: clientStreamType,
2469
+ version: syncVersion,
2470
+ ...syncMeta ? { meta: syncMeta } : {}
2471
+ });
2472
+ }
2473
+ const functionsInterface = generateServerFunctions({ namedImports, defaultImports });
2474
+ if (unresolvedTypeAliases.size > 0) {
2475
+ const unresolvedList = [...unresolvedTypeAliases].toSorted().join(", ");
2476
+ throw new Error(`[TypeMapGenerator] Aborting generation because unresolved type symbols were found: ${unresolvedList}`);
2477
+ }
2478
+ const { content, docsData, schemasContent } = buildTypeMapArtifacts({
2479
+ typesByPage,
2480
+ syncTypesByPage,
2481
+ namedImports,
2482
+ defaultImports,
2483
+ functionsInterface
2484
+ });
2485
+ writeTypeMapArtifacts({ content, docsData, schemasContent });
2486
+ };
2487
+
2488
+ // src/templateRegistry.ts
2489
+ var BUILT_IN_TEMPLATE_KINDS = [
2490
+ "api",
2491
+ "sync_server",
2492
+ "sync_client_paired",
2493
+ "sync_client_standalone",
2494
+ "page_plain",
2495
+ "page_dashboard"
2496
+ ];
2497
+ var BUILT_IN_TEMPLATE_FILENAMES = {
2498
+ api: "api.template.ts",
2499
+ sync_server: "sync_server.template.ts",
2500
+ sync_client_paired: "sync_client_paired.template.ts",
2501
+ sync_client_standalone: "sync_client_standalone.template.ts",
2502
+ page_plain: "page_plain.template.tsx",
2503
+ page_dashboard: "page_dashboard.template.tsx"
2504
+ };
2505
+ var overrides = /* @__PURE__ */ new Map();
2506
+ var rules = [];
2507
+ var insertionCounter = 0;
2508
+ var DEFAULT_DASHBOARD_PATH_PATTERN = /\/(admin|dashboard|settings|billing|account|profile)(\/|$)/;
2509
+ var registerTemplate = (kind, content) => {
2510
+ overrides.set(kind, content);
2511
+ };
2512
+ var getRegisteredTemplate = (kind) => {
2513
+ return overrides.get(kind) ?? null;
2514
+ };
2515
+ var clearTemplateOverrides = () => {
2516
+ overrides.clear();
2517
+ };
2518
+ var listRegisteredTemplateKinds = () => {
2519
+ return [...overrides.keys()];
2520
+ };
2521
+ var registerTemplateRule = (rule) => {
2522
+ rules.push({ ...rule, order: insertionCounter++ });
2523
+ };
2524
+ var registerTemplateKind = (kind, options) => {
2525
+ registerTemplateRule({ kind, match: options.match, priority: options.priority ?? 100 });
2526
+ if (typeof options.content === "string") {
2527
+ registerTemplate(kind, options.content);
2528
+ }
2529
+ };
2530
+ var clearTemplateRules = () => {
2531
+ rules.length = 0;
2532
+ };
2533
+ var getTemplateRules = () => {
2534
+ return rules.toSorted((a, b) => b.priority - a.priority || b.order - a.order).map(({ kind, match, priority }) => ({ kind, match, priority }));
2535
+ };
2536
+ var resolveTemplateKind = (ctx) => {
2537
+ for (const rule of getTemplateRules()) {
2538
+ if (rule.match(ctx)) return rule.kind;
2539
+ }
2540
+ return null;
2541
+ };
2542
+ var defaultsRegistered = false;
2543
+ var registerDefaultTemplateRules = () => {
2544
+ if (defaultsRegistered) return;
2545
+ defaultsRegistered = true;
2546
+ registerTemplateRule({ kind: "api", priority: 10, match: (c) => c.fileKind === "api" });
2547
+ registerTemplateRule({ kind: "sync_server", priority: 10, match: (c) => c.fileKind === "sync_server" });
2548
+ registerTemplateRule({ kind: "sync_client_paired", priority: 10, match: (c) => c.fileKind === "sync_client" && c.hasPairedServer });
2549
+ registerTemplateRule({ kind: "sync_client_standalone", priority: 10, match: (c) => c.fileKind === "sync_client" && !c.hasPairedServer });
2550
+ registerTemplateRule({
2551
+ kind: "page_dashboard",
2552
+ priority: 10,
2553
+ match: (c) => c.fileKind === "page" && DEFAULT_DASHBOARD_PATH_PATTERN.test(c.filePath.replaceAll("\\", "/").toLowerCase())
2554
+ });
2555
+ registerTemplateRule({ kind: "page_plain", priority: 0, match: (c) => c.fileKind === "page" });
2556
+ };
2557
+ registerDefaultTemplateRules();
2558
+
2559
+ // src/loader.ts
2560
+ import fs6 from "fs";
2561
+ import path9 from "path";
2562
+ import { pathToFileURL } from "url";
2563
+ import { tryCatch, getServerFunctionDirs as getServerFunctionDirs2, getSrcDir as getSrcDir2 } from "@luckystack/core";
2564
+
2565
+ // src/runtimeTypeResolver.ts
2566
+ import * as ts8 from "typescript";
2567
+ var MAX_DEPTH = 20;
2568
+ var unresolvedPrefix = "__RUNTIME_UNRESOLVED__::";
2569
+ var PRIMITIVE_TYPES = /* @__PURE__ */ new Set([
2570
+ "string",
2571
+ "number",
2572
+ "boolean",
2573
+ "true",
2574
+ "false",
2575
+ "null",
2576
+ "undefined",
2577
+ "any",
2578
+ "unknown",
2579
+ "void",
2580
+ "never"
2581
+ ]);
2582
+ var SKIP_EXPANSION2 = /* @__PURE__ */ new Set([
2583
+ "Date",
2584
+ "Promise",
2585
+ "Array",
2586
+ "Record",
2587
+ "Partial",
2588
+ "Required",
2589
+ "Pick",
2590
+ "Omit",
2591
+ "Function",
2592
+ "Map",
2593
+ "Set",
2594
+ "Buffer",
2595
+ "Uint8Array",
2596
+ "Object",
2597
+ "WeakMap",
2598
+ "WeakSet"
2599
+ ]);
2600
+ var toUnresolved = (message) => `${unresolvedPrefix}${message}`;
2601
+ var isUnresolvedTypeMarker = (value) => value.startsWith(unresolvedPrefix);
2602
+ var getUnresolvedTypeMessage = (value) => value.slice(unresolvedPrefix.length).trim();
2603
+ var resolvedTypeCache = /* @__PURE__ */ new Map();
2604
+ var clearRuntimeTypeResolverCache = () => {
2605
+ resolvedTypeCache.clear();
2606
+ };
2607
+ var splitTopLevel = (value, splitter) => {
2608
+ const items = [];
2609
+ let depthParen = 0;
2610
+ let depthBrace = 0;
2611
+ let depthBracket = 0;
2612
+ let depthAngle = 0;
2613
+ let token = "";
2614
+ for (const char of value) {
2615
+ if (char === "(") depthParen += 1;
2616
+ if (char === ")") depthParen -= 1;
2617
+ if (char === "{") depthBrace += 1;
2618
+ if (char === "}") depthBrace -= 1;
2619
+ if (char === "[") depthBracket += 1;
2620
+ if (char === "]") depthBracket -= 1;
2621
+ if (char === "<") depthAngle += 1;
2622
+ if (char === ">") depthAngle -= 1;
2623
+ if (char === splitter && depthParen === 0 && depthBrace === 0 && depthBracket === 0 && depthAngle === 0) {
2624
+ if (token.trim()) items.push(token.trim());
2625
+ token = "";
2626
+ continue;
2627
+ }
2628
+ token += char;
2629
+ }
2630
+ if (token.trim()) items.push(token.trim());
2631
+ return items;
2632
+ };
2633
+ var parseTypeNode = (typeText) => {
2634
+ const synthetic = `type __RT = ${typeText};`;
2635
+ const source = ts8.createSourceFile("__runtime_type.ts", synthetic, ts8.ScriptTarget.Latest, true);
2636
+ const statement = source.statements[0];
2637
+ if (!statement || !ts8.isTypeAliasDeclaration(statement)) return null;
2638
+ return statement.type;
2639
+ };
2640
+ var parseObjectFields = (typeText) => {
2641
+ const clean = typeText.trim();
2642
+ if (!clean.startsWith("{") || !clean.endsWith("}")) {
2643
+ return { fields: [], indexSignatures: [] };
2644
+ }
2645
+ const typeNode = parseTypeNode(clean);
2646
+ if (!typeNode || !ts8.isTypeLiteralNode(typeNode)) {
2647
+ return { fields: [], indexSignatures: [] };
2648
+ }
2649
+ const fields = [];
2650
+ const indexSignatures = [];
2651
+ for (const member of typeNode.members) {
2652
+ if (ts8.isPropertySignature(member) && member.type) {
2653
+ let key = null;
2654
+ if (ts8.isIdentifier(member.name)) {
2655
+ key = member.name.text;
2656
+ } else if (ts8.isStringLiteral(member.name)) {
2657
+ key = member.name.text;
2658
+ }
2659
+ if (key === null) continue;
2660
+ fields.push({
2661
+ key,
2662
+ optional: Boolean(member.questionToken),
2663
+ type: member.type.getText().trim()
2664
+ });
2665
+ continue;
2666
+ }
2667
+ if (ts8.isIndexSignatureDeclaration(member)) {
2668
+ const param = member.parameters[0];
2669
+ if (!param || !ts8.isIdentifier(param.name) || !param.type) continue;
2670
+ indexSignatures.push({
2671
+ keyName: param.name.text,
2672
+ keyType: param.type.getText().trim(),
2673
+ type: member.type.getText().trim()
2674
+ });
2675
+ }
2676
+ }
2677
+ return { fields, indexSignatures };
2678
+ };
2679
+ var serializeObjectFields = ({
2680
+ fields,
2681
+ indexSignatures
2682
+ }) => {
2683
+ const segments = [];
2684
+ for (const field of fields) {
2685
+ segments.push(`${field.key}${field.optional ? "?" : ""}: ${field.type}`);
2686
+ }
2687
+ for (const indexSignature of indexSignatures) {
2688
+ segments.push(`[${indexSignature.keyName}: ${indexSignature.keyType}]: ${indexSignature.type}`);
2689
+ }
2690
+ if (segments.length === 0) return "{ }";
2691
+ return `{ ${segments.join("; ")} }`;
2692
+ };
2693
+ var extractStringLiteralKey = (node) => {
2694
+ if (!ts8.isLiteralTypeNode(node)) return null;
2695
+ const literal = node.literal;
2696
+ if (!ts8.isStringLiteral(literal)) return null;
2697
+ return literal.text;
2698
+ };
2699
+ var parseLiteralUnionKeys = (value) => {
2700
+ const trimmed = value.trim();
2701
+ if (!trimmed) return null;
2702
+ const typeNode = parseTypeNode(trimmed);
2703
+ if (!typeNode) return null;
2704
+ if (ts8.isUnionTypeNode(typeNode)) {
2705
+ const keys = [];
2706
+ for (const member of typeNode.types) {
2707
+ const key = extractStringLiteralKey(member);
2708
+ if (key === null) return null;
2709
+ keys.push(key);
2710
+ }
2711
+ return keys.length > 0 ? keys : null;
2712
+ }
2713
+ const singleKey = extractStringLiteralKey(typeNode);
2714
+ return singleKey === null ? null : [singleKey];
2715
+ };
2716
+ var resolveIdentifier = (identifier, filePath) => {
2717
+ if (PRIMITIVE_TYPES.has(identifier.toLowerCase())) return identifier.toLowerCase();
2718
+ if (SKIP_EXPANSION2.has(identifier)) return identifier;
2719
+ try {
2720
+ const program = getServerProgram();
2721
+ const sourceFile = program.getSourceFile(filePath);
2722
+ if (!sourceFile) return toUnresolved(`unresolved type ${identifier}`);
2723
+ const checker = program.getTypeChecker();
2724
+ for (const stmt of sourceFile.statements) {
2725
+ if ((ts8.isInterfaceDeclaration(stmt) || ts8.isTypeAliasDeclaration(stmt) || ts8.isEnumDeclaration(stmt)) && stmt.name.text === identifier) {
2726
+ const symbol = checker.getSymbolAtLocation(stmt.name);
2727
+ if (symbol) {
2728
+ return expandType(checker.getDeclaredTypeOfSymbol(symbol), checker);
2729
+ }
2730
+ }
2731
+ if (ts8.isImportDeclaration(stmt) && stmt.importClause) {
2732
+ const { namedBindings, name: defaultName } = stmt.importClause;
2733
+ if (namedBindings && ts8.isNamedImports(namedBindings)) {
2734
+ for (const specifier of namedBindings.elements) {
2735
+ if (specifier.name.text === identifier) {
2736
+ const symbol = checker.getSymbolAtLocation(specifier.name);
2737
+ if (symbol) {
2738
+ const target = symbol.flags & ts8.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
2739
+ return expandType(checker.getDeclaredTypeOfSymbol(target), checker);
2740
+ }
2741
+ }
2742
+ }
2743
+ }
2744
+ if (defaultName?.text === identifier) {
2745
+ const symbol = checker.getSymbolAtLocation(defaultName);
2746
+ if (symbol) {
2747
+ const target = symbol.flags & ts8.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
2748
+ return expandType(checker.getDeclaredTypeOfSymbol(target), checker);
2749
+ }
2750
+ }
2751
+ }
2752
+ }
2753
+ return toUnresolved(`unresolved type ${identifier}`);
2754
+ } catch {
2755
+ return toUnresolved(`unresolved type ${identifier}`);
2756
+ }
2757
+ };
2758
+ var applyUtilityType = ({
2759
+ utilityName,
2760
+ utilityArgs,
2761
+ filePath,
2762
+ depth,
2763
+ state
2764
+ }) => {
2765
+ if (utilityName === "Partial" || utilityName === "Required") {
2766
+ const arg0 = utilityArgs[0];
2767
+ if (utilityArgs.length !== 1 || arg0 === void 0) return toUnresolved(`unresolved utility ${utilityName}<...>`);
2768
+ const target = resolveExpression(arg0, filePath, depth + 1, state);
2769
+ if (isUnresolvedTypeMarker(target)) return target;
2770
+ const parsed = parseObjectFields(target);
2771
+ const fields = parsed.fields;
2772
+ if (fields.length === 0) return toUnresolved(`unresolved utility ${utilityName}<${arg0}>`);
2773
+ return serializeObjectFields({
2774
+ fields: fields.map((f) => ({ ...f, optional: utilityName === "Partial" })),
2775
+ indexSignatures: parsed.indexSignatures
2776
+ });
2777
+ }
2778
+ if (utilityName === "Pick" || utilityName === "Omit") {
2779
+ const arg0 = utilityArgs[0];
2780
+ const arg1 = utilityArgs[1];
2781
+ if (utilityArgs.length !== 2 || arg0 === void 0 || arg1 === void 0) return toUnresolved(`unresolved utility ${utilityName}<...>`);
2782
+ const target = resolveExpression(arg0, filePath, depth + 1, state);
2783
+ if (isUnresolvedTypeMarker(target)) return target;
2784
+ const keys = parseLiteralUnionKeys(arg1);
2785
+ if (!keys) return toUnresolved(`unresolved utility ${utilityName}<${utilityArgs.join(", ")}>`);
2786
+ const parsed = parseObjectFields(target);
2787
+ const fields = parsed.fields;
2788
+ if (fields.length === 0) return toUnresolved(`unresolved utility ${utilityName}<${utilityArgs.join(", ")}>`);
2789
+ const keySet = new Set(keys);
2790
+ const filtered = fields.filter((f) => utilityName === "Pick" ? keySet.has(f.key) : !keySet.has(f.key));
2791
+ return serializeObjectFields({ fields: filtered, indexSignatures: parsed.indexSignatures });
2792
+ }
2793
+ if (utilityName === "Record") {
2794
+ const arg0 = utilityArgs[0];
2795
+ const arg1 = utilityArgs[1];
2796
+ if (utilityArgs.length !== 2 || arg0 === void 0 || arg1 === void 0) return toUnresolved("unresolved utility Record<...>");
2797
+ const resolvedKey = resolveExpression(arg0, filePath, depth + 1, state);
2798
+ const resolvedValue = resolveExpression(arg1, filePath, depth + 1, state);
2799
+ if (isUnresolvedTypeMarker(resolvedKey)) return resolvedKey;
2800
+ if (isUnresolvedTypeMarker(resolvedValue)) return resolvedValue;
2801
+ const keys = parseLiteralUnionKeys(resolvedKey);
2802
+ if (!keys) return `Record<${resolvedKey}, ${resolvedValue}>`;
2803
+ return serializeObjectFields({
2804
+ fields: keys.map((key) => ({ key, optional: false, type: resolvedValue })),
2805
+ indexSignatures: []
2806
+ });
2807
+ }
2808
+ return toUnresolved(`unresolved utility ${utilityName}<${utilityArgs.join(", ")}>`);
2809
+ };
2810
+ var resolveExpression = (typeText, filePath, depth, state) => {
2811
+ const type = typeText.trim();
2812
+ if (!type) return type;
2813
+ const visitKey = `${filePath}::${type}`;
2814
+ if (state.stack.has(visitKey)) return toUnresolved(`cyclic type reference ${type}`);
2815
+ if (depth > MAX_DEPTH) return toUnresolved(`resolution depth exceeded for ${type}`);
2816
+ state.stack.add(visitKey);
2817
+ let result;
2818
+ if (isUnresolvedTypeMarker(type)) {
2819
+ result = type;
2820
+ } else if (type.startsWith("(") && type.endsWith(")")) {
2821
+ const inner = resolveExpression(type.slice(1, -1), filePath, depth + 1, state);
2822
+ result = isUnresolvedTypeMarker(inner) ? inner : `(${inner})`;
2823
+ } else {
2824
+ const unionParts = splitTopLevel(type, "|");
2825
+ if (unionParts.length > 1) {
2826
+ const resolved = unionParts.map((p) => resolveExpression(p, filePath, depth + 1, state));
2827
+ const unresolved = resolved.find((item) => isUnresolvedTypeMarker(item));
2828
+ result = unresolved ?? resolved.join(" | ");
2829
+ } else {
2830
+ const intersectionParts = splitTopLevel(type, "&");
2831
+ if (intersectionParts.length > 1) {
2832
+ const resolved = intersectionParts.map((p) => resolveExpression(p, filePath, depth + 1, state));
2833
+ const unresolved = resolved.find((item) => isUnresolvedTypeMarker(item));
2834
+ result = unresolved ?? resolved.join(" & ");
2835
+ } else if (type.endsWith("[]")) {
2836
+ const inner = resolveExpression(type.slice(0, -2), filePath, depth + 1, state);
2837
+ result = isUnresolvedTypeMarker(inner) ? inner : `${inner}[]`;
2838
+ } else if (type.startsWith("{") && type.endsWith("}")) {
2839
+ const parsed = parseObjectFields(type);
2840
+ const fields = parsed.fields;
2841
+ const indexSignatures = parsed.indexSignatures;
2842
+ const resolvedFields = [];
2843
+ const resolvedIndexSignatures = [];
2844
+ let hadError;
2845
+ if (fields.length === 0 && indexSignatures.length === 0) {
2846
+ result = type;
2847
+ state.stack.delete(visitKey);
2848
+ return result;
2849
+ }
2850
+ for (const field of fields) {
2851
+ const resolvedType = resolveExpression(field.type, filePath, depth + 1, state);
2852
+ if (isUnresolvedTypeMarker(resolvedType)) {
2853
+ hadError = resolvedType;
2854
+ break;
2855
+ }
2856
+ resolvedFields.push({ ...field, type: resolvedType });
2857
+ }
2858
+ for (const indexSignature of indexSignatures) {
2859
+ const resolvedKeyType = resolveExpression(indexSignature.keyType, filePath, depth + 1, state);
2860
+ if (isUnresolvedTypeMarker(resolvedKeyType)) {
2861
+ hadError = resolvedKeyType;
2862
+ break;
2863
+ }
2864
+ const resolvedValueType = resolveExpression(indexSignature.type, filePath, depth + 1, state);
2865
+ if (isUnresolvedTypeMarker(resolvedValueType)) {
2866
+ hadError = resolvedValueType;
2867
+ break;
2868
+ }
2869
+ resolvedIndexSignatures.push({
2870
+ ...indexSignature,
2871
+ keyType: resolvedKeyType,
2872
+ type: resolvedValueType
2873
+ });
2874
+ }
2875
+ result = hadError ?? serializeObjectFields({
2876
+ fields: resolvedFields,
2877
+ indexSignatures: resolvedIndexSignatures
2878
+ });
2879
+ } else {
2880
+ const typeNode = parseTypeNode(type);
2881
+ const referenceInfo = typeNode && ts8.isTypeReferenceNode(typeNode) && ts8.isIdentifier(typeNode.typeName) ? { name: typeNode.typeName.text, args: typeNode.typeArguments } : null;
2882
+ if (referenceInfo?.args && referenceInfo.args.length > 0) {
2883
+ const { name: genericName, args: argNodes } = referenceInfo;
2884
+ const args = argNodes.map((arg) => arg.getText().trim());
2885
+ const firstArg = args[0];
2886
+ if (genericName === "Array" && args.length === 1 && firstArg !== void 0) {
2887
+ const inner = resolveExpression(firstArg, filePath, depth + 1, state);
2888
+ result = isUnresolvedTypeMarker(inner) ? inner : `${inner}[]`;
2889
+ } else if (["Partial", "Required", "Pick", "Omit", "Record"].includes(genericName)) {
2890
+ result = applyUtilityType({ utilityName: genericName, utilityArgs: args, filePath, depth, state });
2891
+ } else {
2892
+ const resolvedArgs = args.map((a) => resolveExpression(a, filePath, depth + 1, state));
2893
+ const unresolved = resolvedArgs.find((item) => isUnresolvedTypeMarker(item));
2894
+ result = unresolved ?? `${genericName}<${resolvedArgs.join(", ")}>`;
2895
+ }
2896
+ } else if (referenceInfo && (!referenceInfo.args || referenceInfo.args.length === 0)) {
2897
+ result = resolveIdentifier(referenceInfo.name, filePath);
2898
+ } else {
2899
+ result = type;
2900
+ }
2901
+ }
2902
+ }
2903
+ }
2904
+ state.stack.delete(visitKey);
2905
+ return result;
2906
+ };
2907
+ var resolveRuntimeTypeText = ({
2908
+ typeText,
2909
+ filePath
2910
+ }) => {
2911
+ const cleanType = typeText.trim();
2912
+ if (!cleanType || !filePath) {
2913
+ return { status: "success", typeText: cleanType };
2914
+ }
2915
+ const cacheKey = `${filePath}::${cleanType}`;
2916
+ const cached = resolvedTypeCache.get(cacheKey);
2917
+ if (cached) {
2918
+ return cached;
2919
+ }
2920
+ const resolved = resolveExpression(cleanType, filePath, 0, { stack: /* @__PURE__ */ new Set() });
2921
+ const result = isUnresolvedTypeMarker(resolved) ? { status: "error", message: getUnresolvedTypeMessage(resolved) } : { status: "success", typeText: resolved };
2922
+ resolvedTypeCache.set(cacheKey, result);
2923
+ return result;
2924
+ };
2925
+
2926
+ // src/loader.ts
2927
+ var devApis = {};
2928
+ var devSyncs = {};
2929
+ var devFunctions = {};
2930
+ var normalizePath2 = (value) => value.replaceAll("\\", "/");
2931
+ var mapApiPageLocation = (pageLocation) => {
2932
+ return pageLocation ? pageLocation : "system";
2933
+ };
2934
+ var resolveApiRouteMetaFromPath = (filePath) => {
2935
+ const absolutePath = path9.resolve(filePath);
2936
+ const normalizedAbsolutePath = normalizePath2(absolutePath);
2937
+ const normalizedSrcDir = normalizePath2(getSrcDir2());
2938
+ if (!normalizedAbsolutePath.startsWith(normalizedSrcDir) || !normalizedAbsolutePath.endsWith(".ts")) {
2939
+ return null;
2940
+ }
2941
+ const rules2 = getRoutingRules();
2942
+ const relativePath = normalizePath2(path9.relative(getSrcDir2(), absolutePath));
2943
+ const segments = relativePath.split("/");
2944
+ const apiIndex = segments.indexOf(rules2.apiMarker);
2945
+ if (apiIndex === -1 || apiIndex === segments.length - 1) {
2946
+ return null;
2947
+ }
2948
+ const pageLocation = segments.slice(0, apiIndex).join("/");
2949
+ const apiFilePath = segments.slice(apiIndex + 1).join("/");
2950
+ const rawApiName = apiFilePath.replace(/\.ts$/, "");
2951
+ const versionMatch = rawApiName.match(rules2.apiVersionRegex);
2952
+ if (!versionMatch) {
2953
+ return null;
2954
+ }
2955
+ const version = `v${versionMatch[1]}`;
2956
+ const apiName = rawApiName.replace(rules2.apiVersionRegex, "");
2957
+ const mappedPageLocation = mapApiPageLocation(pageLocation);
2958
+ const routeKey = `api/${mappedPageLocation}/${apiName}/${version}`;
2959
+ return { routeKey, absolutePath };
2960
+ };
2961
+ var resolveSyncRouteMetaFromPath = (filePath) => {
2962
+ const absolutePath = path9.resolve(filePath);
2963
+ const normalizedAbsolutePath = normalizePath2(absolutePath);
2964
+ const normalizedSrcDir = normalizePath2(getSrcDir2());
2965
+ if (!normalizedAbsolutePath.startsWith(normalizedSrcDir) || !normalizedAbsolutePath.endsWith(".ts")) {
2966
+ return null;
2967
+ }
2968
+ const rules2 = getRoutingRules();
2969
+ const relativePath = normalizePath2(path9.relative(getSrcDir2(), absolutePath));
2970
+ const segments = relativePath.split("/");
2971
+ const syncIndex = segments.indexOf(rules2.syncMarker);
2972
+ if (syncIndex === -1 || syncIndex === segments.length - 1) {
2973
+ return null;
2974
+ }
2975
+ const pageLocation = segments.slice(0, syncIndex).join("/");
2976
+ const syncFilePath = segments.slice(syncIndex + 1).join("/");
2977
+ const rawSyncName = syncFilePath.replace(/\.ts$/, "");
2978
+ const match = rawSyncName.match(rules2.syncVersionRegex);
2979
+ if (!match) {
2980
+ return null;
2981
+ }
2982
+ const kind = match[1];
2983
+ const version = `v${match[2]}`;
2984
+ const syncName = rawSyncName.replace(rules2.syncVersionRegex, "");
2985
+ const routeBaseKey = pageLocation ? `sync/${pageLocation}/${syncName}/${version}` : `sync/${syncName}/${version}`;
2986
+ return {
2987
+ routeKey: `${routeBaseKey}_${kind}`,
2988
+ kind,
2989
+ absolutePath
2990
+ };
2991
+ };
2992
+ var initializeAll = async () => {
2993
+ assertValidRouteNaming({
2994
+ srcDir: getSrcDir2(),
2995
+ context: "starting dev server (npm run server)"
2996
+ });
2997
+ const pageRouteIssues = collectDuplicatePageRoutes(getSrcDir2());
2998
+ if (pageRouteIssues.length > 0) {
2999
+ console.warn(formatDuplicatePageRouteIssues({
3000
+ issues: pageRouteIssues,
3001
+ context: "starting dev server (npm run server)"
3002
+ }));
3003
+ }
3004
+ await Promise.all([initializeApis(), initializeSyncs(), initializeFunctions()]);
3005
+ };
3006
+ var importFile = async (absolutePath) => {
3007
+ const url = `${pathToFileURL(absolutePath).href}?v=${Date.now()}`;
3008
+ return import(url);
3009
+ };
3010
+ var collectTsFiles = (dir, relativeTo = "") => {
3011
+ const results = [];
3012
+ const entries = fs6.readdirSync(dir);
3013
+ for (const entry of entries) {
3014
+ const entryPath = path9.join(dir, entry);
3015
+ const relPath = relativeTo ? `${relativeTo}/${entry}` : entry;
3016
+ if (fs6.statSync(entryPath).isDirectory()) {
3017
+ results.push(...collectTsFiles(entryPath, relPath));
3018
+ } else if (entry.endsWith(".ts") && !isRouteTestFile(entry)) {
3019
+ results.push(relPath);
3020
+ }
3021
+ }
3022
+ return results;
3023
+ };
3024
+ var isMergeable = (value) => {
3025
+ return typeof value === "object" && value !== null || typeof value === "function";
3026
+ };
3027
+ var resolveFunctionModule = (loadedModule, fileName) => {
3028
+ if (!loadedModule || typeof loadedModule !== "object" || !("default" in loadedModule)) {
3029
+ return isMergeable(loadedModule) ? loadedModule : {};
3030
+ }
3031
+ const moduleRecord = loadedModule;
3032
+ const { default: defaultExport, ...namedExports } = moduleRecord;
3033
+ const filteredNamedExports = Object.fromEntries(
3034
+ Object.entries(namedExports).filter(([key]) => key !== "__esModule")
3035
+ );
3036
+ if (Object.keys(filteredNamedExports).length > 0) {
3037
+ return filteredNamedExports;
3038
+ }
3039
+ if (defaultExport !== void 0) {
3040
+ return { [fileName]: defaultExport };
3041
+ }
3042
+ return {};
3043
+ };
3044
+ var initializeApis = async () => {
3045
+ for (const key of Object.keys(devApis)) delete devApis[key];
3046
+ clearRuntimeTypeResolverCache();
3047
+ const srcFolder = fs6.readdirSync(getSrcDir2());
3048
+ for (const file of srcFolder) {
3049
+ await scanApiFolder(file);
3050
+ }
3051
+ };
3052
+ var upsertApiFromFile = async (filePath) => {
3053
+ const routeMeta = resolveApiRouteMetaFromPath(filePath);
3054
+ if (!routeMeta) {
3055
+ const normalized = normalizePath2(path9.resolve(filePath));
3056
+ if (normalized.includes("/_api/") && normalized.endsWith(".ts") && !isRouteTestFile(normalized)) {
3057
+ console.log(
3058
+ `[loader][api] invalid filename: ${normalized}. Expected <name>_v<number>.ts. File will not be loaded.`,
3059
+ "red"
3060
+ );
3061
+ }
3062
+ return;
3063
+ }
3064
+ invalidateProgramCache();
3065
+ clearRuntimeTypeResolverCache();
3066
+ const [err, module] = await tryCatch(async () => importFile(routeMeta.absolutePath));
3067
+ if (err) {
3068
+ console.log(`[loader][api] failed to import ${routeMeta.routeKey} from ${routeMeta.absolutePath}:`, err, "red");
3069
+ return;
3070
+ }
3071
+ const resolvedModule = module?.default ? { ...module.default, ...module } : module;
3072
+ const { auth = {}, main, rateLimit, httpMethod, schema, validation, errorFormatter } = resolvedModule;
3073
+ if (!main || typeof main !== "function") {
3074
+ delete devApis[routeMeta.routeKey];
3075
+ return;
3076
+ }
3077
+ const inputType = getInputTypeFromFile(routeMeta.absolutePath);
3078
+ devApis[routeMeta.routeKey] = {
3079
+ main,
3080
+ auth: {
3081
+ login: auth.login || false,
3082
+ additional: auth.additional || []
3083
+ },
3084
+ rateLimit,
3085
+ httpMethod,
3086
+ schema,
3087
+ inputType,
3088
+ inputTypeFilePath: routeMeta.absolutePath,
3089
+ validation,
3090
+ errorFormatter
3091
+ };
3092
+ };
3093
+ var removeApiFromFile = (filePath) => {
3094
+ const routeMeta = resolveApiRouteMetaFromPath(filePath);
3095
+ if (!routeMeta) {
3096
+ return;
3097
+ }
3098
+ invalidateProgramCache();
3099
+ clearRuntimeTypeResolverCache();
3100
+ delete devApis[routeMeta.routeKey];
3101
+ };
3102
+ var scanApiFolder = async (file, basePath = "") => {
3103
+ const fullPath = path9.join(getSrcDir2(), basePath, file);
3104
+ if (!fs6.statSync(fullPath).isDirectory()) return;
3105
+ if (!file.toLowerCase().endsWith("api")) {
3106
+ const subFolders = fs6.readdirSync(fullPath);
3107
+ for (const sub of subFolders) {
3108
+ await scanApiFolder(sub, path9.join(basePath, file));
3109
+ }
3110
+ return;
3111
+ }
3112
+ const pageLocation = basePath.replaceAll("\\", "/");
3113
+ const mappedPageLocation = mapApiPageLocation(pageLocation);
3114
+ const tsFiles = collectTsFiles(fullPath);
3115
+ const apiRules = getRoutingRules();
3116
+ for (const relFile of tsFiles) {
3117
+ const rawApiName = relFile.replace(/\.ts$/, "").replaceAll("\\", "/");
3118
+ const versionMatch = rawApiName.match(apiRules.apiVersionRegex);
3119
+ if (!versionMatch) {
3120
+ console.log(
3121
+ `[loader][api] invalid filename: ${path9.join(fullPath, relFile)}. Expected <name>_v<number>.ts. File will not be loaded.`,
3122
+ "red"
3123
+ );
3124
+ continue;
3125
+ }
3126
+ const version = `v${versionMatch[1]}`;
3127
+ const apiName = rawApiName.replace(apiRules.apiVersionRegex, "");
3128
+ const routeKey = `api/${mappedPageLocation}/${apiName}/${version}`;
3129
+ const modulePath = path9.resolve(path9.join(fullPath, relFile));
3130
+ const [err, module] = await tryCatch(async () => importFile(modulePath));
3131
+ if (err) {
3132
+ console.log(`[loader][api] failed to import ${routeKey} from ${modulePath}:`, err, "red");
3133
+ continue;
3134
+ }
3135
+ const resolvedModule = module?.default ? { ...module.default, ...module } : module;
3136
+ const { auth = {}, main, rateLimit, httpMethod, schema, validation, errorFormatter } = resolvedModule;
3137
+ if (!main || typeof main !== "function") continue;
3138
+ const inputType = getInputTypeFromFile(modulePath);
3139
+ devApis[routeKey] = {
3140
+ main,
3141
+ auth: {
3142
+ login: auth.login || false,
3143
+ additional: auth.additional || []
3144
+ },
3145
+ rateLimit,
3146
+ httpMethod,
3147
+ schema,
3148
+ inputType,
3149
+ inputTypeFilePath: modulePath,
3150
+ validation,
3151
+ errorFormatter
3152
+ };
3153
+ }
3154
+ };
3155
+ var initializeSyncs = async () => {
3156
+ for (const key of Object.keys(devSyncs)) delete devSyncs[key];
3157
+ clearRuntimeTypeResolverCache();
3158
+ const srcFolder = fs6.readdirSync(getSrcDir2());
3159
+ for (const file of srcFolder) {
3160
+ await scanSyncFolder(file);
3161
+ }
3162
+ };
3163
+ var upsertSyncFromFile = async (filePath) => {
3164
+ const routeMeta = resolveSyncRouteMetaFromPath(filePath);
3165
+ if (!routeMeta) {
3166
+ const normalized = normalizePath2(path9.resolve(filePath));
3167
+ if (normalized.includes(`/${getRoutingRules().syncMarker}/`) && normalized.endsWith(".ts") && !isRouteTestFile(normalized)) {
3168
+ console.log(
3169
+ `[loader][sync] invalid filename: ${normalized}. Expected <name>_server_v<number>.ts or <name>_client_v<number>.ts. File will not be loaded.`,
3170
+ "red"
3171
+ );
3172
+ }
3173
+ return;
3174
+ }
3175
+ invalidateProgramCache();
3176
+ clearRuntimeTypeResolverCache();
3177
+ const [err, module] = await tryCatch(async () => importFile(routeMeta.absolutePath));
3178
+ if (err) {
3179
+ console.log(`[loader][sync] failed to import ${routeMeta.absolutePath}:`, err, "red");
3180
+ return;
3181
+ }
3182
+ const resolvedSyncModule = module?.default ? { ...module.default, ...module } : module;
3183
+ if (routeMeta.kind === "server") {
3184
+ if (!resolvedSyncModule.main || typeof resolvedSyncModule.main !== "function") {
3185
+ delete devSyncs[routeMeta.routeKey];
3186
+ return;
3187
+ }
3188
+ const inputType = getSyncClientDataType(routeMeta.absolutePath);
3189
+ devSyncs[routeMeta.routeKey] = {
3190
+ main: resolvedSyncModule.main,
3191
+ auth: resolvedSyncModule.auth || {},
3192
+ inputType,
3193
+ inputTypeFilePath: routeMeta.absolutePath
3194
+ };
3195
+ return;
3196
+ }
3197
+ if (!resolvedSyncModule.main || typeof resolvedSyncModule.main !== "function") {
3198
+ delete devSyncs[routeMeta.routeKey];
3199
+ return;
3200
+ }
3201
+ devSyncs[routeMeta.routeKey] = resolvedSyncModule.main;
3202
+ };
3203
+ var removeSyncFromFile = (filePath) => {
3204
+ const routeMeta = resolveSyncRouteMetaFromPath(filePath);
3205
+ if (!routeMeta) {
3206
+ return;
3207
+ }
3208
+ invalidateProgramCache();
3209
+ clearRuntimeTypeResolverCache();
3210
+ delete devSyncs[routeMeta.routeKey];
3211
+ };
3212
+ var scanSyncFolder = async (file, basePath = "") => {
3213
+ const fullPath = path9.join(getSrcDir2(), basePath, file);
3214
+ if (!fs6.statSync(fullPath).isDirectory()) return;
3215
+ if (!file.toLowerCase().endsWith("sync")) {
3216
+ const subFolders = fs6.readdirSync(fullPath);
3217
+ for (const sub of subFolders) {
3218
+ await scanSyncFolder(sub, path9.join(basePath, file));
3219
+ }
3220
+ return;
3221
+ }
3222
+ const pageLocation = basePath.replaceAll("\\", "/");
3223
+ const tsFiles = collectTsFiles(fullPath);
3224
+ const syncRules = getRoutingRules();
3225
+ for (const relFile of tsFiles) {
3226
+ const rawSyncFileName = relFile.replace(/\.ts$/, "").replaceAll("\\", "/");
3227
+ const syncMatch = rawSyncFileName.match(syncRules.syncVersionRegex);
3228
+ if (!syncMatch) {
3229
+ console.log(
3230
+ `[loader][sync] invalid filename: ${path9.join(fullPath, relFile)}. Expected <name>_server_v<number>.ts or <name>_client_v<number>.ts. File will not be loaded.`,
3231
+ "red"
3232
+ );
3233
+ continue;
3234
+ }
3235
+ const kind = syncMatch[1];
3236
+ const version = `v${syncMatch[2]}`;
3237
+ const syncName = rawSyncFileName.replace(syncRules.syncVersionRegex, "");
3238
+ const routeBaseKey = pageLocation ? `sync/${pageLocation}/${syncName}/${version}` : `sync/${syncName}/${version}`;
3239
+ const filePath = path9.resolve(path9.join(fullPath, relFile));
3240
+ const [fileError, fileResult] = await tryCatch(async () => importFile(filePath));
3241
+ if (fileError) {
3242
+ console.log(`[loader][sync] failed to import ${filePath}:`, fileError, "red");
3243
+ continue;
3244
+ }
3245
+ const resolvedSyncModule = fileResult?.default ? { ...fileResult.default, ...fileResult } : fileResult;
3246
+ const inputType = getSyncClientDataType(filePath);
3247
+ if (kind === "server") {
3248
+ devSyncs[`${routeBaseKey}_server`] = {
3249
+ main: resolvedSyncModule.main,
3250
+ auth: resolvedSyncModule.auth || {},
3251
+ inputType,
3252
+ inputTypeFilePath: filePath
3253
+ };
3254
+ } else {
3255
+ devSyncs[`${routeBaseKey}_client`] = resolvedSyncModule.main;
3256
+ }
3257
+ }
3258
+ };
3259
+ var functionClaimMap = /* @__PURE__ */ new Map();
3260
+ var initializeFunctions = async () => {
3261
+ for (const key of Object.keys(devFunctions)) delete devFunctions[key];
3262
+ functionClaimMap.clear();
3263
+ const dirs = getServerFunctionDirs2();
3264
+ for (const dir of dirs) {
3265
+ if (fs6.existsSync(dir)) {
3266
+ await scanFunctionsFolder(dir, dir);
3267
+ }
3268
+ }
3269
+ };
3270
+ var scanFunctionsFolder = async (dir, rootDir, basePath = []) => {
3271
+ const entries = fs6.readdirSync(dir);
3272
+ for (const entry of entries) {
3273
+ const fullPath = path9.join(dir, entry);
3274
+ const stat = fs6.statSync(fullPath);
3275
+ if (stat.isDirectory()) {
3276
+ await scanFunctionsFolder(fullPath, rootDir, [...basePath, entry]);
3277
+ continue;
3278
+ }
3279
+ if (!entry.endsWith(".ts")) {
3280
+ continue;
3281
+ }
3282
+ const [err, module] = await tryCatch(async () => importFile(fullPath));
3283
+ if (err) {
3284
+ console.log(`[loader][function] failed to import ${fullPath}:`, err, "red");
3285
+ continue;
3286
+ }
3287
+ const fileName = entry.replace(".ts", "");
3288
+ const resolvedFunctionModule = resolveFunctionModule(module, fileName);
3289
+ if (!isMergeable(resolvedFunctionModule)) continue;
3290
+ const keyPath = [...basePath, fileName].join(".");
3291
+ const previousRoot = functionClaimMap.get(keyPath);
3292
+ if (previousRoot !== void 0 && previousRoot !== rootDir) {
3293
+ console.log(
3294
+ `[loader][function] Conflict at \`functions.${keyPath}\`: defined in both \`${previousRoot}\` and \`${rootDir}\`. Skipping the second copy; fix the duplicate (delete one \u2014 \`shared/\` is the canonical location for framework re-exports).`,
3295
+ "red"
3296
+ );
3297
+ continue;
3298
+ }
3299
+ functionClaimMap.set(keyPath, rootDir);
3300
+ let target = devFunctions;
3301
+ for (const part of basePath) {
3302
+ const existing = target[part];
3303
+ if (!existing || typeof existing !== "object") {
3304
+ target[part] = {};
3305
+ }
3306
+ target = target[part];
3307
+ }
3308
+ const existingAtFileName = target[fileName];
3309
+ if (existingAtFileName !== void 0 && isMergeable(resolvedFunctionModule) && isMergeable(existingAtFileName)) {
3310
+ Object.assign(resolvedFunctionModule, existingAtFileName);
3311
+ }
3312
+ target[fileName] = resolvedFunctionModule;
3313
+ }
3314
+ };
3315
+
3316
+ // src/hotReload.ts
3317
+ import { watch } from "chokidar";
3318
+ import fs9 from "fs";
3319
+ import path12 from "path";
3320
+
3321
+ // src/templateInjector.ts
3322
+ import fs7 from "fs";
3323
+ import path10 from "path";
3324
+ import { fileURLToPath, pathToFileURL as pathToFileURL2 } from "url";
3325
+ import { ROOT_DIR as ROOT_DIR5, getGeneratedSocketTypesPath as getGeneratedSocketTypesPath4, getSrcDir as getSrcDir3, validatePagePath as validatePagePath2 } from "@luckystack/core";
3326
+ var __filename = fileURLToPath(import.meta.url);
3327
+ var __dirname = path10.dirname(__filename);
3328
+ var templatesDir = path10.join(__dirname, "templates");
3329
+ var getConsumerTemplatesDir = () => path10.join(ROOT_DIR5, ".luckystack", "templates");
3330
+ var templateContentFilenames = (kind) => {
3331
+ const builtIn = BUILT_IN_TEMPLATE_FILENAMES[kind];
3332
+ if (builtIn) return [builtIn];
3333
+ return [`${kind}.template.tsx`, `${kind}.template.ts`];
3334
+ };
3335
+ var resolveTemplateContent = (kind) => {
3336
+ const consumerDir = getConsumerTemplatesDir();
3337
+ for (const fileName of templateContentFilenames(kind)) {
3338
+ const consumerFile = path10.join(consumerDir, fileName);
3339
+ if (fs7.existsSync(consumerFile)) {
3340
+ try {
3341
+ return fs7.readFileSync(consumerFile, "utf8");
3342
+ } catch (error) {
3343
+ console.error(`[TemplateInjector] Could not read consumer template: ${consumerFile}`, error);
3344
+ }
3345
+ }
3346
+ }
3347
+ const override = getRegisteredTemplate(kind);
3348
+ if (override !== null) return override;
3349
+ const builtInName = BUILT_IN_TEMPLATE_FILENAMES[kind];
3350
+ if (builtInName) {
3351
+ const bundled = path10.join(templatesDir, builtInName);
3352
+ try {
3353
+ return fs7.readFileSync(bundled, "utf8");
3354
+ } catch (error) {
3355
+ console.error(`[TemplateInjector] Could not read bundled template: ${bundled}`, error);
3356
+ return null;
3357
+ }
3358
+ }
3359
+ console.warn(`[TemplateInjector] No content for custom template kind "${kind}" \u2014 add .luckystack/templates/${kind}.template.tsx or call registerTemplate('${kind}', ...).`);
3360
+ return null;
3361
+ };
3362
+ var consumerTemplateConfig = null;
3363
+ var ensureConsumerTemplateConfigLoaded = () => {
3364
+ consumerTemplateConfig ??= (async () => {
3365
+ const dir = getConsumerTemplatesDir();
3366
+ for (const fileName of ["templateRules.ts", "templateRules.mjs", "templateRules.js"]) {
3367
+ const rulesFile = path10.join(dir, fileName);
3368
+ if (!fs7.existsSync(rulesFile)) continue;
3369
+ try {
3370
+ await import(pathToFileURL2(rulesFile).href);
3371
+ } catch (error) {
3372
+ console.error(`[TemplateInjector] Failed to load consumer template rules: ${rulesFile}`, error);
3373
+ }
3374
+ return;
3375
+ }
3376
+ })();
3377
+ return consumerTemplateConfig;
3378
+ };
3379
+ var isEmptyFile = (filePath) => {
3380
+ try {
3381
+ const content = fs7.readFileSync(filePath, "utf8");
3382
+ return content.trim().length === 0;
3383
+ } catch {
3384
+ return false;
3385
+ }
3386
+ };
3387
+ var isCommentOnlyFile = (filePath) => {
3388
+ try {
3389
+ const content = fs7.readFileSync(filePath, "utf8");
3390
+ const withoutBlockComments = content.replaceAll(/\/\*[\s\S]*?\*\//g, "");
3391
+ const withoutLineComments = withoutBlockComments.replaceAll(/(^|\s)\/\/.*$/gm, "$1");
3392
+ return withoutLineComments.trim().length === 0;
3393
+ } catch {
3394
+ return false;
3395
+ }
3396
+ };
3397
+ var isInApiFolder = (filePath) => {
3398
+ const normalized = filePath.replaceAll("\\", "/");
3399
+ return normalized.includes("/_api/") && filePath.endsWith(".ts") && !isRouteTestFile(filePath);
3400
+ };
3401
+ var isInSyncFolder = (filePath) => {
3402
+ const normalized = filePath.replaceAll("\\", "/");
3403
+ return normalized.includes("/_sync/") && filePath.endsWith(".ts") && !isRouteTestFile(filePath);
3404
+ };
3405
+ var isPageFile = (filePath) => {
3406
+ const normalized = filePath.replaceAll("\\", "/");
3407
+ const filename = normalized.split("/").pop() ?? "";
3408
+ return filename === "page.tsx" || filename === "page.jsx";
3409
+ };
3410
+ var isSyncServerFile = (filePath) => {
3411
+ return isVersionedSyncServerFileName(filePath);
3412
+ };
3413
+ var isSyncClientFile = (filePath) => {
3414
+ return isVersionedSyncClientFileName(filePath);
3415
+ };
3416
+ var isVersionedApiFile = (filePath) => {
3417
+ return isVersionedApiFileName(filePath);
3418
+ };
3419
+ var isVersionedSyncFile = (filePath) => {
3420
+ return isVersionedSyncFileName(filePath);
3421
+ };
3422
+ var getFileName = (filePath) => {
3423
+ return path10.basename(filePath.replace(/\\/g, "/"));
3424
+ };
3425
+ var stripTsExtension = (fileName) => {
3426
+ return fileName.replace(/\.ts$/, "");
3427
+ };
3428
+ var toApiBaseName = (fileName) => {
3429
+ const withoutExtension = stripTsExtension(fileName);
3430
+ const withoutVersion = withoutExtension.replace(/_v\d+$/, "");
3431
+ return withoutVersion || "myApi";
3432
+ };
3433
+ var toSyncBaseName = (fileName) => {
3434
+ const withoutExtension = stripTsExtension(fileName);
3435
+ const withoutVersionedKind = withoutExtension.replace(/_(?:server|client)_v\d+$/, "");
3436
+ const withoutKindOnly = withoutVersionedKind.replace(/_(?:server|client)$/, "");
3437
+ const withoutVersionOnly = withoutKindOnly.replace(/_v\d+$/, "");
3438
+ return withoutVersionOnly || "mySync";
3439
+ };
3440
+ var getApiFilenameReason = (fileName) => {
3441
+ const withoutExtension = stripTsExtension(fileName);
3442
+ if (/_v\d+$/.test(withoutExtension)) {
3443
+ return "The filename already looks versioned.";
3444
+ }
3445
+ if (/_v\d+/.test(withoutExtension)) {
3446
+ return "The version token must be at the end of the filename.";
3447
+ }
3448
+ if (/_server|_client/.test(withoutExtension)) {
3449
+ return "API files do not use _server/_client suffixes.";
3450
+ }
3451
+ return "Missing required version suffix.";
3452
+ };
3453
+ var getSyncFilenameReason = (fileName) => {
3454
+ const withoutExtension = stripTsExtension(fileName);
3455
+ if (/_(?:server|client)_v\d+$/.test(withoutExtension)) {
3456
+ return "The filename already looks versioned.";
3457
+ }
3458
+ if (/_(?:server|client)$/.test(withoutExtension)) {
3459
+ return "Sync files with _server/_client must include a version suffix like _v1.";
3460
+ }
3461
+ if (/_v\d+$/.test(withoutExtension)) {
3462
+ return "Sync files with versions must also include _server or _client.";
3463
+ }
3464
+ return "Missing required sync kind and version suffix.";
3465
+ };
3466
+ var getRouteFilenameValidationMessage = (filePath) => {
3467
+ const normalized = filePath.replace(/\\/g, "/");
3468
+ const fileName = getFileName(normalized);
3469
+ if (isInApiFolder(normalized) && !isVersionedApiFile(normalized)) {
3470
+ const base = toApiBaseName(fileName);
3471
+ return [
3472
+ `Invalid API filename: ${fileName}`,
3473
+ `Reason: ${getApiFilenameReason(fileName)}`,
3474
+ `Expected: ${ROUTE_NAMING_RULES.api}`,
3475
+ `Example: ${base}_v1.ts (for example ${ROUTE_NAMING_EXAMPLES.api})`,
3476
+ "This file is ignored by route loading and type generation until it is renamed."
3477
+ ].join(" ");
3478
+ }
3479
+ if (isInSyncFolder(normalized) && !isVersionedSyncFile(normalized)) {
3480
+ const base = toSyncBaseName(fileName);
3481
+ return [
3482
+ `Invalid sync filename: ${fileName}`,
3483
+ `Reason: ${getSyncFilenameReason(fileName)}`,
3484
+ `Expected: ${ROUTE_NAMING_RULES.syncServer} or ${ROUTE_NAMING_RULES.syncClient}`,
3485
+ `Examples: ${base}_server_v1.ts, ${base}_client_v1.ts (for example ${ROUTE_NAMING_EXAMPLES.syncServer})`,
3486
+ "This file is ignored by route loading and type generation until it is renamed."
3487
+ ].join(" ");
3488
+ }
3489
+ return null;
3490
+ };
3491
+ var getInvalidVersionMessage = (filePath) => {
3492
+ const validationMessage = getRouteFilenameValidationMessage(filePath) || "Invalid route filename.";
3493
+ return `// ${validationMessage}
3494
+ `;
3495
+ };
3496
+ var computeSrcRelativePath = (filePath) => {
3497
+ try {
3498
+ const srcDir = getSrcDir3();
3499
+ const absolute = path10.resolve(filePath);
3500
+ const normalizedSrc = srcDir.replaceAll("\\", "/");
3501
+ const normalizedAbs = absolute.replaceAll("\\", "/");
3502
+ if (!normalizedAbs.startsWith(`${normalizedSrc}/`)) return null;
3503
+ return normalizedAbs.slice(normalizedSrc.length + 1);
3504
+ } catch {
3505
+ return null;
3506
+ }
3507
+ };
3508
+ var getInvalidPagePlacementMessage = (_filePath, reason, srcRelative) => {
3509
+ const lines = [
3510
+ "//? --- LUCKYSTACK PLACEMENT WARNING ---",
3511
+ "//? This page.tsx is in a location the file-based router will not route.",
3512
+ `//? Reason: ${reason}`,
3513
+ `//? Path: ${srcRelative}`,
3514
+ "//?",
3515
+ "//? Common fixes:",
3516
+ "//? - Move the file up so a visible (non-underscore) folder segment remains:",
3517
+ "//? e.g. src/_marketing/page.tsx -> src/_marketing/landing/page.tsx",
3518
+ "//? (the new file routes at /landing \u2014 `_marketing` stays invisible).",
3519
+ "//? - Or move the file OUT of a reserved framework folder (_api, _sync,",
3520
+ "//? _components, _functions, _shared, _providers, _locales, _sockets, _server).",
3521
+ "//?",
3522
+ "//? Delete the file or move it to fix; the dev server will re-inject a",
3523
+ "//? real page template once the placement is valid.",
3524
+ "",
3525
+ //? Named export instead of `export {};` so the file satisfies
3526
+ //? `unicorn/require-module-specifiers` (and stays a valid ES module
3527
+ //? without dragging in side-effect-only semantics). Reads back as a
3528
+ //? cheap runtime tag for tooling that wants to detect the warning.
3529
+ "export const __luckystackPlacementWarning = true;",
3530
+ ""
3531
+ ];
3532
+ return lines.join("\n");
3533
+ };
3534
+ var getPairedSyncFile = (filePath) => {
3535
+ const normalized = filePath.replaceAll("\\", "/");
3536
+ if (isSyncServerFile(normalized)) {
3537
+ return normalized.replace(/_server_v(\d+)\.ts$/, "_client_v$1.ts");
3538
+ }
3539
+ if (isSyncClientFile(normalized)) {
3540
+ return normalized.replace(/_client_v(\d+)\.ts$/, "_server_v$1.ts");
3541
+ }
3542
+ return null;
3543
+ };
3544
+ var hasPairedFile = (filePath) => {
3545
+ const pairedPath = getPairedSyncFile(filePath);
3546
+ if (!pairedPath) return false;
3547
+ return fs7.existsSync(pairedPath);
3548
+ };
3549
+ var extractSyncPagePath2 = (filePath) => {
3550
+ const normalized = filePath.replaceAll("\\", "/");
3551
+ const match = /src\/(.+?)\/_sync\//.exec(normalized);
3552
+ return match?.[1] ?? "";
3553
+ };
3554
+ var extractSyncName2 = (filePath) => {
3555
+ const normalized = filePath.replaceAll("\\", "/");
3556
+ const match = /_sync\/(.+)\.ts$/.exec(normalized);
3557
+ if (!match) {
3558
+ const basename = path10.basename(filePath, ".ts");
3559
+ return basename.replace(/_server_v\d+$/, "").replace(/_client_v\d+$/, "");
3560
+ }
3561
+ return (match[1] ?? "").replace(/_server_v\d+$/, "").replace(/_client_v\d+$/, "");
3562
+ };
3563
+ var extractClientInputFromFile = (filePath) => {
3564
+ try {
3565
+ const content = fs7.readFileSync(filePath, "utf8");
3566
+ const syncParamsMatch = /interface\s+SyncParams\s*\{/.exec(content);
3567
+ if (!syncParamsMatch) return null;
3568
+ const clientInputMatch = /clientInput\s*:\s*\{/.exec(content);
3569
+ if (!clientInputMatch) return null;
3570
+ const startIndex = content.indexOf("{", clientInputMatch.index);
3571
+ let depth = 0;
3572
+ let endIndex = startIndex;
3573
+ for (let i = startIndex; i < content.length; i++) {
3574
+ if (content[i] === "{") depth++;
3575
+ else if (content[i] === "}") depth--;
3576
+ if (depth === 0) {
3577
+ endIndex = i;
3578
+ break;
3579
+ }
3580
+ }
3581
+ return content.substring(startIndex, endIndex + 1);
3582
+ } catch (error) {
3583
+ console.error(`[TemplateInjector] Error extracting clientInput from ${filePath}:`, error);
3584
+ return null;
3585
+ }
3586
+ };
3587
+ var extractClientInputFromGeneratedTypes = (pagePath, syncName) => {
3588
+ try {
3589
+ const generatedTypesPath = getGeneratedSocketTypesPath4();
3590
+ const content = fs7.readFileSync(generatedTypesPath, "utf8");
3591
+ const escapeRegex = (value) => value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
3592
+ const escapedPagePath = escapeRegex(pagePath);
3593
+ const escapedSyncName = escapeRegex(syncName);
3594
+ const pageBlockRegex = new RegExp(String.raw`'${escapedPagePath}'\s*:\s*\{([\s\S]*?)\n\s{2}\};`, "m");
3595
+ const pageBlockMatch = content.match(pageBlockRegex);
3596
+ if (!pageBlockMatch?.[1]) {
3597
+ console.log(`[TemplateInjector] Could not find page block for ${pagePath}`);
3598
+ return null;
3599
+ }
3600
+ const pageBlock = pageBlockMatch[1];
3601
+ const syncEntryPattern = new RegExp(String.raw`'${escapedSyncName}':\s*\{\s*clientInput:\s*`);
3602
+ const match = pageBlock.match(syncEntryPattern);
3603
+ if (!match || typeof match.index !== "number") {
3604
+ console.log(`[TemplateInjector] Could not find sync entry for ${pagePath}/${syncName}`);
3605
+ return null;
3606
+ }
3607
+ const pageStart = content.indexOf(pageBlock);
3608
+ const globalMatchIndex = pageStart + match.index;
3609
+ const searchStart = globalMatchIndex + match[0].length;
3610
+ const braceStart = content.indexOf("{", searchStart - 1);
3611
+ if (braceStart === -1) return null;
3612
+ let depth = 0;
3613
+ let endIndex = braceStart;
3614
+ for (let i = braceStart; i < content.length; i++) {
3615
+ if (content[i] === "{") depth++;
3616
+ else if (content[i] === "}") depth--;
3617
+ if (depth === 0) {
3618
+ endIndex = i;
3619
+ break;
3620
+ }
3621
+ }
3622
+ const extracted = content.substring(braceStart, endIndex + 1);
3623
+ console.log(`[TemplateInjector] Extracted clientInput types: ${extracted}`);
3624
+ return extracted;
3625
+ } catch (error) {
3626
+ console.error(`[TemplateInjector] Error extracting clientInput from generated types:`, error);
3627
+ return null;
3628
+ }
3629
+ };
3630
+ var calculateRelativePath = (filePath) => {
3631
+ const normalized = filePath.replaceAll("\\", "/");
3632
+ const srcIndex = normalized.indexOf("src/");
3633
+ if (srcIndex === -1) {
3634
+ console.warn(`[TemplateInjector] Could not find /src/ in path: ${filePath}`);
3635
+ return "../../../";
3636
+ }
3637
+ const relativePath = normalized.slice(Math.max(0, srcIndex + 4));
3638
+ const segments = relativePath.split("/").filter((s) => s.length > 0).length;
3639
+ return "../".repeat(segments);
3640
+ };
3641
+ var getTemplate = (filePath) => {
3642
+ let fileKind;
3643
+ let hasPairedServer = false;
3644
+ let srcRelativePath = null;
3645
+ if (isInApiFolder(filePath)) {
3646
+ fileKind = "api";
3647
+ } else if (isInSyncFolder(filePath)) {
3648
+ if (isSyncServerFile(filePath)) {
3649
+ fileKind = "sync_server";
3650
+ } else if (isSyncClientFile(filePath)) {
3651
+ fileKind = "sync_client";
3652
+ hasPairedServer = hasPairedFile(filePath);
3653
+ } else {
3654
+ console.log(`[TemplateInjector] Unknown sync file type: ${filePath}`);
3655
+ return null;
3656
+ }
3657
+ } else if (isPageFile(filePath)) {
3658
+ fileKind = "page";
3659
+ srcRelativePath = computeSrcRelativePath(filePath);
3660
+ if (srcRelativePath !== null) {
3661
+ const placement = validatePagePath2(srcRelativePath);
3662
+ if (!placement.valid) {
3663
+ return getInvalidPagePlacementMessage(filePath, placement.reason ?? "invalid placement", srcRelativePath);
3664
+ }
3665
+ }
3666
+ } else {
3667
+ return null;
3668
+ }
3669
+ const ctx = { filePath, fileKind, hasPairedServer, srcRelativePath };
3670
+ const templateKind = resolveTemplateKind(ctx);
3671
+ if (!templateKind) {
3672
+ console.log(`[TemplateInjector] No template rule matched for ${filePath} (fileKind=${fileKind})`);
3673
+ return null;
3674
+ }
3675
+ let content = resolveTemplateContent(templateKind);
3676
+ if (content === null) return null;
3677
+ const relPath = calculateRelativePath(filePath);
3678
+ const pragmaPattern = /\/\/\s*@ts-(?:ignore|expect-error).*\r?\n(.*)\{\{REL_PATH\}\}/g;
3679
+ content = content.replaceAll(pragmaPattern, (_, prefix) => `${prefix}${relPath}`);
3680
+ content = content.replaceAll("{{REL_PATH}}", relPath);
3681
+ if (fileKind === "sync_client" || fileKind === "sync_server") {
3682
+ const pagePath = extractSyncPagePath2(filePath);
3683
+ const syncName = extractSyncName2(filePath);
3684
+ content = content.replaceAll("{{PAGE_PATH}}", pagePath);
3685
+ content = content.replaceAll("{{SYNC_NAME}}", syncName);
3686
+ }
3687
+ return content;
3688
+ };
3689
+ var injectTemplate = async (filePath) => {
3690
+ await ensureConsumerTemplateConfigLoaded();
3691
+ if (getRouteFilenameValidationMessage(filePath)) {
3692
+ fs7.writeFileSync(filePath, getInvalidVersionMessage(filePath), "utf-8");
3693
+ console.log(`[TemplateInjector] Invalid route filename, injected guidance: ${filePath}`);
3694
+ return true;
3695
+ }
3696
+ const template = getTemplate(filePath);
3697
+ if (!template) {
3698
+ return false;
3699
+ }
3700
+ try {
3701
+ fs7.writeFileSync(filePath, template, "utf-8");
3702
+ console.log(`[TemplateInjector] Injected template into: ${filePath}`);
3703
+ return true;
3704
+ } catch (error) {
3705
+ console.error(`[TemplateInjector] Failed to inject template: ${filePath}`, error);
3706
+ return false;
3707
+ }
3708
+ };
3709
+ var shouldInjectTemplate = (filePath) => {
3710
+ const { disableTemplateInjection } = getRoutingRules();
3711
+ if (disableTemplateInjection && disableTemplateInjection(filePath)) {
3712
+ return false;
3713
+ }
3714
+ if (!(isInApiFolder(filePath) || isInSyncFolder(filePath) || isPageFile(filePath))) {
3715
+ return false;
3716
+ }
3717
+ return isEmptyFile(filePath) || isCommentOnlyFile(filePath);
3718
+ };
3719
+ var updateClientFileForPairedServer = async (clientFilePath) => {
3720
+ try {
3721
+ const pagePath = extractSyncPagePath2(clientFilePath);
3722
+ const syncName = extractSyncName2(clientFilePath);
3723
+ let content = fs7.readFileSync(clientFilePath, "utf8");
3724
+ if (!content.includes("SyncClientInput")) {
3725
+ content = content.replace(
3726
+ /import \{([^}]+)\} from ['"]([^'"]*apiTypes\.generated)['"]/,
3727
+ (_match, imports, path13) => {
3728
+ return `import {${imports}, SyncClientInput, SyncServerOutput } from '${path13}'`;
3729
+ }
3730
+ );
3731
+ }
3732
+ if (!content.includes("type PagePath")) {
3733
+ const importEndMatch = content.match(/import .+?;[\r\n]+/g);
3734
+ if (importEndMatch) {
3735
+ const lastImport = importEndMatch.at(-1);
3736
+ if (!lastImport) {
3737
+ return false;
3738
+ }
3739
+ const lastImportEnd = content.lastIndexOf(lastImport) + lastImport.length;
3740
+ const typeAliases = `
3741
+ // Types are imported from the generated file based on the _server.ts definition
3742
+ type PagePath = '${pagePath}';
3743
+ type SyncName = '${syncName}';
3744
+ `;
3745
+ content = content.slice(0, lastImportEnd) + typeAliases + content.slice(lastImportEnd);
3746
+ }
3747
+ }
3748
+ content = content.replace(
3749
+ /^(\s*)clientInput:\s*\{[^}]*\}/m,
3750
+ "$1clientInput: SyncClientInput<PagePath, SyncName>"
3751
+ );
3752
+ if (!content.includes("serverOutput:")) {
3753
+ content = content.replace(
3754
+ /^(\s*)(clientInput:\s*SyncClientInput<PagePath, SyncName>);?\s*$/m,
3755
+ "$1$2;\n$1serverOutput: SyncServerOutput<PagePath, SyncName>;"
3756
+ );
3757
+ }
3758
+ if (content.includes("main") && !/\{\s*[^}]*serverOutput[^}]*\}\s*:\s*SyncParams/.test(content)) {
3759
+ content = content.replace(
3760
+ /\{\s*([^}]*?clientInput)([^}]*)\}\s*:\s*SyncParams/,
3761
+ "{ $1, serverOutput$2 }: SyncParams"
3762
+ );
3763
+ }
3764
+ fs7.writeFileSync(clientFilePath, content, "utf-8");
3765
+ console.log(`[TemplateInjector] Updated client file to use paired types (preserved code): ${clientFilePath}`);
3766
+ return true;
3767
+ } catch (error) {
3768
+ console.error(`[TemplateInjector] Failed to update client file: ${clientFilePath}`, error);
3769
+ return false;
3770
+ }
3771
+ };
3772
+ var updateClientFileForDeletedServer = async (clientFilePath, clientInputTypes) => {
3773
+ try {
3774
+ let content = fs7.readFileSync(clientFilePath, "utf8");
3775
+ content = content.replace(
3776
+ /^(\s*)clientInput:\s*SyncClientInput<[^>]+>/m,
3777
+ `$1clientInput: ${clientInputTypes}`
3778
+ );
3779
+ content = content.replace(
3780
+ /^(\s*)clientInput:\s*\{[^}]*\}/m,
3781
+ `$1clientInput: ${clientInputTypes}`
3782
+ );
3783
+ content = content.replace(
3784
+ /^[ \t]*serverOutput:\s*SyncServerOutput<[^>]+>;?\s*\r?\n?/m,
3785
+ ""
3786
+ );
3787
+ content = content.replace(
3788
+ /^[ \t]*serverOutput:\s*\{[^}]*\};?\s*\r?\n?/m,
3789
+ ""
3790
+ );
3791
+ content = content.replaceAll(/,\s*serverOutput(?=\s*[,}])/g, "");
3792
+ content = content.replaceAll(/serverOutput\s*,\s*/g, "");
3793
+ content = content.replaceAll(/,\s*SyncClientInput(?=\s*[,}])/g, "");
3794
+ content = content.replaceAll(/,\s*SyncServerOutput(?=\s*[,}])/g, "");
3795
+ content = content.replaceAll(/\/\/\s*Types are imported.*\n?/g, "");
3796
+ content = content.replaceAll(/type PagePath = '[^']*';\s*\n?/g, "");
3797
+ content = content.replaceAll(/type SyncName = '[^']*';\s*\n?/g, "");
3798
+ content = content.replaceAll(/\n{3,}/g, "\n\n");
3799
+ fs7.writeFileSync(clientFilePath, content, "utf-8");
3800
+ console.log(`[TemplateInjector] Updated client file for deleted server (preserved code): ${clientFilePath}`);
3801
+ return true;
3802
+ } catch (error) {
3803
+ console.error(`[TemplateInjector] Failed to update client file: ${clientFilePath}`, error);
3804
+ return false;
3805
+ }
3806
+ };
3807
+ var injectServerTemplateWithClientInput = async (serverFilePath, clientInputTypes) => {
3808
+ try {
3809
+ await ensureConsumerTemplateConfigLoaded();
3810
+ const relPath = calculateRelativePath(serverFilePath);
3811
+ let content = resolveTemplateContent("sync_server");
3812
+ if (content === null) {
3813
+ console.error(`[TemplateInjector] No sync_server template content available for: ${serverFilePath}`);
3814
+ return false;
3815
+ }
3816
+ const pragmaPattern = /\/\/\s*@ts-(?:ignore|expect-error).*\r?\n(.*)\{\{REL_PATH\}\}/g;
3817
+ content = content.replaceAll(pragmaPattern, (_, prefix) => {
3818
+ return `${prefix}${relPath}`;
3819
+ });
3820
+ content = content.replaceAll("{{REL_PATH}}", relPath);
3821
+ content = content.replace(
3822
+ /clientInput:\s*\{[^}]*\}/s,
3823
+ `clientInput: ${clientInputTypes}`
3824
+ );
3825
+ fs7.writeFileSync(serverFilePath, content, "utf-8");
3826
+ console.log(`[TemplateInjector] Injected server template with clientInput: ${serverFilePath}`);
3827
+ return true;
3828
+ } catch (error) {
3829
+ console.error(`[TemplateInjector] Failed to inject server template: ${serverFilePath}`, error);
3830
+ return false;
3831
+ }
3832
+ };
3833
+
3834
+ // src/importDependencyGraph.ts
3835
+ import fs8 from "fs";
3836
+ import path11 from "path";
3837
+ import { ROOT_DIR as ROOT_DIR6, getServerFunctionDirs as getServerFunctionDirs3, getSharedDir, getSrcDir as getSrcDir4 } from "@luckystack/core";
3838
+ var SUPPORTED_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"];
3839
+ var normalizePath3 = (value) => path11.resolve(value).replaceAll("\\", "/");
3840
+ var isSupportedScriptFile = (value) => {
3841
+ return SUPPORTED_EXTENSIONS.some((extension) => value.endsWith(extension));
3842
+ };
3843
+ var existsFile = (value) => {
3844
+ try {
3845
+ return fs8.existsSync(value) && fs8.statSync(value).isFile();
3846
+ } catch {
3847
+ return false;
3848
+ }
3849
+ };
3850
+ var existsDirectory = (value) => {
3851
+ try {
3852
+ return fs8.existsSync(value) && fs8.statSync(value).isDirectory();
3853
+ } catch {
3854
+ return false;
3855
+ }
3856
+ };
3857
+ var collectScriptFiles = (dir, output) => {
3858
+ if (!existsDirectory(dir)) {
3859
+ return;
3860
+ }
3861
+ const entries = fs8.readdirSync(dir);
3862
+ for (const entry of entries) {
3863
+ const fullPath = path11.join(dir, entry);
3864
+ if (existsDirectory(fullPath)) {
3865
+ collectScriptFiles(fullPath, output);
3866
+ continue;
3867
+ }
3868
+ const normalizedPath = normalizePath3(fullPath);
3869
+ if (isSupportedScriptFile(normalizedPath)) {
3870
+ output.add(normalizedPath);
3871
+ }
3872
+ }
3873
+ };
3874
+ var specifiersCache = /* @__PURE__ */ new Map();
3875
+ var scopedFilesCache = null;
3876
+ var SCOPED_FILES_TTL_MS = 1e3;
3877
+ var extractImportSpecifiers = (filePath) => {
3878
+ let mtimeMs = 0;
3879
+ try {
3880
+ mtimeMs = fs8.statSync(filePath).mtimeMs;
3881
+ } catch {
3882
+ specifiersCache.delete(filePath);
3883
+ return [];
3884
+ }
3885
+ const cached = specifiersCache.get(filePath);
3886
+ if (cached?.mtimeMs === mtimeMs) {
3887
+ return cached.specifiers;
3888
+ }
3889
+ try {
3890
+ const source = fs8.readFileSync(filePath, "utf8");
3891
+ const specifiers = /* @__PURE__ */ new Set();
3892
+ const importExportRegex = /(?:import|export)\s+(?:[^'"\n]*?\s+from\s+)?['"]([^'"\n]+)['"]/g;
3893
+ const dynamicImportRegex = /import\(\s*['"]([^'"\n]+)['"]\s*\)/g;
3894
+ let match = null;
3895
+ while ((match = importExportRegex.exec(source)) !== null) {
3896
+ if (match[1]) specifiers.add(match[1]);
3897
+ }
3898
+ while ((match = dynamicImportRegex.exec(source)) !== null) {
3899
+ if (match[1]) specifiers.add(match[1]);
3900
+ }
3901
+ const result = [...specifiers];
3902
+ specifiersCache.set(filePath, { mtimeMs, specifiers: result });
3903
+ return result;
3904
+ } catch {
3905
+ specifiersCache.delete(filePath);
3906
+ return [];
3907
+ }
3908
+ };
3909
+ var invalidateGraphForFile = (absolutePath) => {
3910
+ specifiersCache.delete(normalizePath3(absolutePath));
3911
+ scopedFilesCache = null;
3912
+ };
3913
+ var tryResolveWithExtensions = (basePath) => {
3914
+ if (existsFile(basePath)) {
3915
+ return normalizePath3(basePath);
3916
+ }
3917
+ for (const extension of SUPPORTED_EXTENSIONS) {
3918
+ const withExtension = `${basePath}${extension}`;
3919
+ if (existsFile(withExtension)) {
3920
+ return normalizePath3(withExtension);
3921
+ }
3922
+ }
3923
+ if (existsDirectory(basePath)) {
3924
+ for (const extension of SUPPORTED_EXTENSIONS) {
3925
+ const indexFile = path11.join(basePath, `index${extension}`);
3926
+ if (existsFile(indexFile)) {
3927
+ return normalizePath3(indexFile);
3928
+ }
3929
+ }
3930
+ }
3931
+ return null;
3932
+ };
3933
+ var resolveImportToFile = (importerPath, specifier) => {
3934
+ if (!specifier || specifier.startsWith("node:")) {
3935
+ return null;
3936
+ }
3937
+ if (specifier === "config") {
3938
+ return tryResolveWithExtensions(path11.join(ROOT_DIR6, "config"));
3939
+ }
3940
+ if (specifier.startsWith("./") || specifier.startsWith("../")) {
3941
+ const relativeBase = path11.resolve(path11.dirname(importerPath), specifier);
3942
+ return tryResolveWithExtensions(relativeBase);
3943
+ }
3944
+ if (specifier.startsWith("src/") || specifier.startsWith("@/")) {
3945
+ const sourcePath = specifier.startsWith("@/") ? specifier.slice(2) : specifier.slice(4);
3946
+ return tryResolveWithExtensions(path11.join(getSrcDir4(), sourcePath));
3947
+ }
3948
+ if (specifier.startsWith("shared/")) {
3949
+ return tryResolveWithExtensions(path11.join(getSharedDir(), specifier.slice(7)));
3950
+ }
3951
+ return null;
3952
+ };
3953
+ var isRouteFile = (filePath) => {
3954
+ return filePath.includes("/src/") && (filePath.includes("/_api/") || filePath.includes("/_sync/"));
3955
+ };
3956
+ var collectScopedFiles = () => {
3957
+ const now = Date.now();
3958
+ if (scopedFilesCache && scopedFilesCache.expiresAt > now) {
3959
+ return scopedFilesCache.files;
3960
+ }
3961
+ const files = /* @__PURE__ */ new Set();
3962
+ collectScriptFiles(getSrcDir4(), files);
3963
+ collectScriptFiles(getSharedDir(), files);
3964
+ for (const dir of getServerFunctionDirs3()) {
3965
+ collectScriptFiles(dir, files);
3966
+ }
3967
+ const configFile = tryResolveWithExtensions(path11.join(ROOT_DIR6, "config"));
3968
+ if (configFile) {
3969
+ files.add(configFile);
3970
+ }
3971
+ scopedFilesCache = { files, expiresAt: now + SCOPED_FILES_TTL_MS };
3972
+ return files;
3973
+ };
3974
+ var findDependentRouteFiles = (changedFilePath) => {
3975
+ const scopedFiles = collectScopedFiles();
3976
+ const changedAbsolutePath = normalizePath3(changedFilePath);
3977
+ const reverseDependencies = /* @__PURE__ */ new Map();
3978
+ for (const filePath of scopedFiles) {
3979
+ const specifiers = extractImportSpecifiers(filePath);
3980
+ for (const specifier of specifiers) {
3981
+ const resolvedImport = resolveImportToFile(filePath, specifier);
3982
+ if (!resolvedImport || !scopedFiles.has(resolvedImport)) {
3983
+ continue;
3984
+ }
3985
+ getOrInit(reverseDependencies, resolvedImport, () => /* @__PURE__ */ new Set()).add(filePath);
3986
+ }
3987
+ }
3988
+ const affectedRoutes = /* @__PURE__ */ new Set();
3989
+ const visited = /* @__PURE__ */ new Set();
3990
+ const queue = [changedAbsolutePath];
3991
+ while (queue.length > 0) {
3992
+ const current = queue.shift();
3993
+ if (current === void 0) break;
3994
+ if (visited.has(current)) {
3995
+ continue;
3996
+ }
3997
+ visited.add(current);
3998
+ const importers = reverseDependencies.get(current);
3999
+ if (!importers) {
4000
+ continue;
4001
+ }
4002
+ for (const importer of importers) {
4003
+ if (isRouteFile(importer)) {
4004
+ affectedRoutes.add(importer);
4005
+ }
4006
+ if (!visited.has(importer)) {
4007
+ queue.push(importer);
4008
+ }
4009
+ }
4010
+ }
4011
+ return affectedRoutes;
4012
+ };
4013
+
4014
+ // src/hotReload.ts
4015
+ import { tryCatch as tryCatch2, getProjectConfig, getLocaleReloader } from "@luckystack/core";
4016
+ var setupWatchers = () => {
4017
+ const isDevMode = process.env.NODE_ENV !== "production";
4018
+ if (!isDevMode) return;
4019
+ const apiMarkerSlash = `/${getRoutingRules().apiMarker}/`;
4020
+ const syncMarkerSlash = `/${getRoutingRules().syncMarker}/`;
4021
+ const apiMarkerNoLead = `${getRoutingRules().apiMarker}/`;
4022
+ const syncMarkerNoLead = `${getRoutingRules().syncMarker}/`;
4023
+ const pathsCfg = getProjectConfig().paths;
4024
+ const srcSegment = `/${pathsCfg.srcDir.replaceAll("\\", "/")}/`;
4025
+ const sharedSegment = `/${pathsCfg.sharedDir.replaceAll("\\", "/")}/`;
4026
+ const serverFunctionsSegments = (pathsCfg.serverFunctionDirs ?? [pathsCfg.serverFunctionsDir]).map(
4027
+ (dir) => `/${dir.replaceAll("\\", "/")}/`
4028
+ );
4029
+ const isInServerFunctionsDir = (normalizedPath) => serverFunctionsSegments.some((segment) => normalizedPath.includes(segment));
4030
+ const localesSegment = `${srcSegment}_locales/`;
4031
+ const reloadTimers = /* @__PURE__ */ new Map();
4032
+ const pendingApiUpserts = /* @__PURE__ */ new Set();
4033
+ const pendingApiDeletes = /* @__PURE__ */ new Set();
4034
+ const pendingSyncUpserts = /* @__PURE__ */ new Set();
4035
+ const pendingSyncDeletes = /* @__PURE__ */ new Set();
4036
+ const normalizeFsPath = (value) => path12.resolve(value).replaceAll("\\", "/");
4037
+ const typeMapQueue = { pending: false, running: false };
4038
+ const runTypeMapRegeneration = () => {
4039
+ typeMapQueue.running = true;
4040
+ typeMapQueue.pending = false;
4041
+ const startedAt = Date.now();
4042
+ setImmediate(() => {
4043
+ void (async () => {
4044
+ const [err] = await tryCatch2(() => {
4045
+ generateTypeMapFile({ quiet: true });
4046
+ });
4047
+ if (err) {
4048
+ console.log(`[HotReload] type map regeneration failed: ${String(err)}`, "red");
4049
+ } else {
4050
+ console.log(`[HotReload] type map ready in ${Date.now() - startedAt}ms`, "green");
4051
+ }
4052
+ typeMapQueue.running = false;
4053
+ if (typeMapQueue.pending) {
4054
+ runTypeMapRegeneration();
4055
+ }
4056
+ })();
4057
+ });
4058
+ };
4059
+ const requestTypeMapRegeneration = () => {
4060
+ if (typeMapQueue.running) {
4061
+ typeMapQueue.pending = true;
4062
+ return;
4063
+ }
4064
+ runTypeMapRegeneration();
4065
+ };
4066
+ const isRouteDependencyFile = (normalizedPath) => {
4067
+ if (!normalizedPath.endsWith(".ts") && !normalizedPath.endsWith(".tsx")) {
4068
+ return false;
4069
+ }
4070
+ if (!normalizedPath.includes(srcSegment)) {
4071
+ return false;
4072
+ }
4073
+ if (normalizedPath.includes(apiMarkerSlash) || normalizedPath.includes(syncMarkerSlash)) {
4074
+ return false;
4075
+ }
4076
+ if (isGeneratedPath(normalizedPath)) {
4077
+ return false;
4078
+ }
4079
+ return true;
4080
+ };
4081
+ const isSharedDependencyFile = (normalizedPath) => {
4082
+ if (!(normalizedPath.endsWith(".ts") || normalizedPath.endsWith(".tsx"))) return false;
4083
+ if (normalizedPath.includes(sharedSegment)) return true;
4084
+ return isInServerFunctionsDir(normalizedPath);
4085
+ };
4086
+ const enqueueAffectedRoutesFromDependency = (changedPath) => {
4087
+ const affectedRoutes = findDependentRouteFiles(changedPath);
4088
+ if (affectedRoutes.size === 0) {
4089
+ console.log(`[HotReload] No API/Sync routes depend on: ${changedPath}`, "yellow");
4090
+ return;
4091
+ }
4092
+ let queuedApiCount = 0;
4093
+ let queuedSyncCount = 0;
4094
+ for (const routePath of affectedRoutes) {
4095
+ if (routePath.includes(apiMarkerSlash)) {
4096
+ pendingApiDeletes.delete(routePath);
4097
+ pendingApiUpserts.add(routePath);
4098
+ queuedApiCount += 1;
4099
+ } else if (routePath.includes(syncMarkerSlash)) {
4100
+ pendingSyncDeletes.delete(routePath);
4101
+ pendingSyncUpserts.add(routePath);
4102
+ queuedSyncCount += 1;
4103
+ }
4104
+ }
4105
+ console.log(
4106
+ `[HotReload] Dependency changed: ${changedPath} -> queued ${queuedApiCount} API and ${queuedSyncCount} Sync route reloads`,
4107
+ "blue"
4108
+ );
4109
+ if (queuedApiCount > 0) {
4110
+ scheduleReload("api", async () => {
4111
+ await processPendingApiChanges();
4112
+ });
4113
+ }
4114
+ if (queuedSyncCount > 0) {
4115
+ scheduleReload("sync", async () => {
4116
+ await processPendingSyncChanges();
4117
+ });
4118
+ }
4119
+ };
4120
+ const isGeneratedPath = (normalizedPath) => {
4121
+ return normalizedPath.includes("apiTypes.generated.ts") || normalizedPath.includes("apiDocs.generated.json");
4122
+ };
4123
+ const isTypeMapRelevantFile = (normalizedPath) => {
4124
+ if (isGeneratedPath(normalizedPath)) return false;
4125
+ if (!(normalizedPath.endsWith(".ts") || normalizedPath.endsWith(".tsx"))) return false;
4126
+ if (normalizedPath.includes(apiMarkerSlash) || normalizedPath.includes(syncMarkerSlash)) return false;
4127
+ return normalizedPath.includes(srcSegment) || normalizedPath.endsWith("/config.ts") || normalizedPath.includes(sharedSegment);
4128
+ };
4129
+ const isLocaleFile = (normalizedPath) => {
4130
+ return normalizedPath.includes(localesSegment) && normalizedPath.endsWith(".json");
4131
+ };
4132
+ const scheduleReload = (key, task, delay = getProjectConfig().dev.hotReloadDebounceMs) => {
4133
+ const activeTimer = reloadTimers.get(key);
4134
+ if (activeTimer) {
4135
+ clearTimeout(activeTimer);
4136
+ }
4137
+ const timer = setTimeout(() => {
4138
+ reloadTimers.delete(key);
4139
+ void task();
4140
+ }, delay);
4141
+ reloadTimers.set(key, timer);
4142
+ };
4143
+ const handleAdd = async (path13) => {
4144
+ const normalizedPath = normalizeFsPath(path13);
4145
+ invalidateGraphForFile(normalizedPath);
4146
+ const routeValidationMessage = getRouteFilenameValidationMessage(normalizedPath);
4147
+ if (routeValidationMessage) {
4148
+ if (shouldInjectTemplate(path13)) {
4149
+ const injected = await injectTemplate(path13);
4150
+ if (injected) {
4151
+ return;
4152
+ }
4153
+ }
4154
+ console.log(`[HotReload] ${routeValidationMessage}`, "yellow");
4155
+ return;
4156
+ }
4157
+ if (shouldInjectTemplate(path13)) {
4158
+ if (isSyncServerFile(normalizedPath)) {
4159
+ const clientPath = getPairedSyncFile(normalizedPath);
4160
+ if (clientPath && fs9.existsSync(clientPath) && !isEmptyFile(clientPath)) {
4161
+ const clientInputTypes = extractClientInputFromFile(clientPath);
4162
+ if (clientInputTypes) {
4163
+ await injectServerTemplateWithClientInput(path13, clientInputTypes);
4164
+ requestTypeMapRegeneration();
4165
+ await updateClientFileForPairedServer(clientPath);
4166
+ await upsertSyncFromFile(path13);
4167
+ await upsertSyncFromFile(clientPath);
4168
+ return;
4169
+ }
4170
+ }
4171
+ }
4172
+ const injected = await injectTemplate(path13);
4173
+ if (injected) {
4174
+ return;
4175
+ }
4176
+ }
4177
+ if (normalizedPath.includes(apiMarkerNoLead)) {
4178
+ pendingApiDeletes.delete(normalizedPath);
4179
+ pendingApiUpserts.add(normalizedPath);
4180
+ scheduleReload("api", async () => {
4181
+ await processPendingApiChanges({ regenerateTypeMap: true });
4182
+ });
4183
+ return;
4184
+ }
4185
+ if (normalizedPath.includes(syncMarkerNoLead)) {
4186
+ pendingSyncDeletes.delete(normalizedPath);
4187
+ pendingSyncUpserts.add(normalizedPath);
4188
+ scheduleReload("sync", async () => {
4189
+ await processPendingSyncChanges({ regenerateTypeMap: true });
4190
+ });
4191
+ return;
4192
+ }
4193
+ handleChange(path13);
4194
+ };
4195
+ const processPendingApiChanges = async ({ regenerateTypeMap = false } = {}) => {
4196
+ const deletePaths = [...pendingApiDeletes];
4197
+ const upsertPaths = [...pendingApiUpserts];
4198
+ pendingApiDeletes.clear();
4199
+ pendingApiUpserts.clear();
4200
+ if (regenerateTypeMap) {
4201
+ console.log(`[HotReload] API routes changed (add/delete), scheduling type map regeneration`, "blue");
4202
+ requestTypeMapRegeneration();
4203
+ }
4204
+ for (const deletePath of deletePaths) {
4205
+ removeApiFromFile(deletePath);
4206
+ console.log(`[HotReload] API removed: ${deletePath}`, "yellow");
4207
+ }
4208
+ for (const upsertPath of upsertPaths) {
4209
+ await upsertApiFromFile(upsertPath);
4210
+ console.log(`[HotReload] API reloaded: ${upsertPath}`, "green");
4211
+ }
4212
+ };
4213
+ const processPendingSyncChanges = async ({ regenerateTypeMap = false } = {}) => {
4214
+ const deletePaths = [...pendingSyncDeletes];
4215
+ const upsertPaths = [...pendingSyncUpserts];
4216
+ pendingSyncDeletes.clear();
4217
+ pendingSyncUpserts.clear();
4218
+ if (regenerateTypeMap) {
4219
+ console.log(`[HotReload] Sync routes changed (add/delete), scheduling type map regeneration`, "blue");
4220
+ requestTypeMapRegeneration();
4221
+ }
4222
+ for (const deletePath of deletePaths) {
4223
+ removeSyncFromFile(deletePath);
4224
+ console.log(`[HotReload] Sync removed: ${deletePath}`, "yellow");
4225
+ }
4226
+ for (const upsertPath of upsertPaths) {
4227
+ await upsertSyncFromFile(upsertPath);
4228
+ console.log(`[HotReload] Sync reloaded: ${upsertPath}`, "green");
4229
+ }
4230
+ };
4231
+ const handleChange = async (path13) => {
4232
+ const normalizedPath = normalizeFsPath(path13);
4233
+ invalidateGraphForFile(normalizedPath);
4234
+ const routeValidationMessage = getRouteFilenameValidationMessage(normalizedPath);
4235
+ if (routeValidationMessage) {
4236
+ if (shouldInjectTemplate(path13)) {
4237
+ const injected = await injectTemplate(path13);
4238
+ if (injected) {
4239
+ return;
4240
+ }
4241
+ }
4242
+ console.log(`[HotReload] ${routeValidationMessage}`, "yellow");
4243
+ return;
4244
+ }
4245
+ if (shouldInjectTemplate(path13)) {
4246
+ const injected = await injectTemplate(path13);
4247
+ if (injected) {
4248
+ return;
4249
+ }
4250
+ }
4251
+ if (isGeneratedPath(normalizedPath)) {
4252
+ return;
4253
+ }
4254
+ if (isLocaleFile(normalizedPath)) {
4255
+ scheduleReload("locales", async () => {
4256
+ await getLocaleReloader()?.();
4257
+ });
4258
+ return;
4259
+ }
4260
+ if (isRouteDependencyFile(normalizedPath)) {
4261
+ scheduleReload("typemap", () => {
4262
+ console.log(`[HotReload] Route dependency changed, scheduling type map regeneration`, "blue");
4263
+ requestTypeMapRegeneration();
4264
+ });
4265
+ enqueueAffectedRoutesFromDependency(normalizedPath);
4266
+ return;
4267
+ }
4268
+ if (isTypeMapRelevantFile(normalizedPath)) {
4269
+ scheduleReload("typemap", () => {
4270
+ console.log(`[HotReload] Route dependency changed, scheduling type map regeneration`, "blue");
4271
+ requestTypeMapRegeneration();
4272
+ });
4273
+ }
4274
+ if (normalizedPath.includes(apiMarkerNoLead)) {
4275
+ scheduleReload("typemap", () => {
4276
+ console.log(`[HotReload] API changed, scheduling type map regeneration`, "blue");
4277
+ requestTypeMapRegeneration();
4278
+ });
4279
+ pendingApiDeletes.delete(normalizedPath);
4280
+ pendingApiUpserts.add(normalizedPath);
4281
+ scheduleReload("api", async () => {
4282
+ await processPendingApiChanges();
4283
+ });
4284
+ } else if (normalizedPath.includes(syncMarkerNoLead)) {
4285
+ scheduleReload("typemap", () => {
4286
+ console.log(`[HotReload] Sync changed, scheduling type map regeneration`, "blue");
4287
+ requestTypeMapRegeneration();
4288
+ });
4289
+ pendingSyncDeletes.delete(normalizedPath);
4290
+ pendingSyncUpserts.add(normalizedPath);
4291
+ scheduleReload("sync", async () => {
4292
+ await processPendingSyncChanges();
4293
+ });
4294
+ }
4295
+ };
4296
+ const handleFunctionChange = (changedPath) => {
4297
+ const normalizedPath = normalizeFsPath(changedPath);
4298
+ invalidateGraphForFile(normalizedPath);
4299
+ scheduleReload("functions", async () => {
4300
+ requestTypeMapRegeneration();
4301
+ await initializeFunctions();
4302
+ });
4303
+ if (isSharedDependencyFile(normalizedPath)) {
4304
+ enqueueAffectedRoutesFromDependency(normalizedPath);
4305
+ }
4306
+ };
4307
+ const handleDelete = async (path13) => {
4308
+ const normalizedPath = normalizeFsPath(path13);
4309
+ invalidateGraphForFile(normalizedPath);
4310
+ if (isGeneratedPath(normalizedPath)) {
4311
+ return;
4312
+ }
4313
+ if (isLocaleFile(normalizedPath)) {
4314
+ scheduleReload("locales", async () => {
4315
+ await getLocaleReloader()?.();
4316
+ });
4317
+ return;
4318
+ }
4319
+ if (isRouteDependencyFile(normalizedPath)) {
4320
+ scheduleReload("typemap", () => {
4321
+ console.log(`[HotReload] Route dependency deleted, scheduling type map regeneration`, "blue");
4322
+ requestTypeMapRegeneration();
4323
+ });
4324
+ enqueueAffectedRoutesFromDependency(normalizedPath);
4325
+ return;
4326
+ }
4327
+ if (isTypeMapRelevantFile(normalizedPath)) {
4328
+ scheduleReload("typemap", () => {
4329
+ console.log(`[HotReload] Route dependency deleted, scheduling type map regeneration`, "blue");
4330
+ requestTypeMapRegeneration();
4331
+ });
4332
+ }
4333
+ if (normalizedPath.includes(apiMarkerNoLead)) {
4334
+ scheduleReload("typemap", () => {
4335
+ console.log(`[HotReload] API deleted, scheduling type map regeneration`, "blue");
4336
+ requestTypeMapRegeneration();
4337
+ });
4338
+ pendingApiUpserts.delete(normalizedPath);
4339
+ pendingApiDeletes.add(normalizedPath);
4340
+ scheduleReload("api", async () => {
4341
+ await processPendingApiChanges();
4342
+ });
4343
+ } else if (normalizedPath.includes(syncMarkerNoLead)) {
4344
+ scheduleReload("typemap", () => {
4345
+ console.log(`[HotReload] Sync deleted, scheduling type map regeneration`, "blue");
4346
+ requestTypeMapRegeneration();
4347
+ });
4348
+ pendingSyncUpserts.delete(normalizedPath);
4349
+ pendingSyncDeletes.add(normalizedPath);
4350
+ scheduleReload("sync", async () => {
4351
+ if (isSyncServerFile(normalizedPath)) {
4352
+ const clientPath = getPairedSyncFile(normalizedPath);
4353
+ if (clientPath && fs9.existsSync(clientPath)) {
4354
+ const pagePath = extractSyncPagePath2(normalizedPath);
4355
+ const syncName = extractSyncName2(normalizedPath);
4356
+ const clientInputTypes = extractClientInputFromGeneratedTypes(pagePath, syncName);
4357
+ if (clientInputTypes) {
4358
+ await updateClientFileForDeletedServer(clientPath, clientInputTypes);
4359
+ } else {
4360
+ await updateClientFileForDeletedServer(clientPath, "{\n // Types were in _server.ts - please add them here\n }");
4361
+ }
4362
+ }
4363
+ }
4364
+ await processPendingSyncChanges();
4365
+ });
4366
+ }
4367
+ };
4368
+ const devConfig = getProjectConfig().dev;
4369
+ const pathsConfig = getProjectConfig().paths;
4370
+ watch(pathsConfig.srcDir, {
4371
+ ignoreInitial: true,
4372
+ awaitWriteFinish: {
4373
+ stabilityThreshold: devConfig.watcherStabilityThresholdMs,
4374
+ pollInterval: devConfig.watcherPollIntervalMs
4375
+ }
4376
+ }).on("add", handleAdd).on("change", handleChange).on("unlink", handleDelete);
4377
+ const serverFunctionDirsToWatch = pathsConfig.serverFunctionDirs && pathsConfig.serverFunctionDirs.length > 0 ? pathsConfig.serverFunctionDirs : [pathsConfig.serverFunctionsDir];
4378
+ for (const dir of serverFunctionDirsToWatch) {
4379
+ watch(dir, { ignoreInitial: true }).on("add", handleFunctionChange).on("change", handleFunctionChange).on("unlink", handleFunctionChange);
4380
+ }
4381
+ watch(pathsConfig.sharedDir, { ignoreInitial: true }).on("add", handleFunctionChange).on("change", handleFunctionChange).on("unlink", handleFunctionChange);
4382
+ setImmediate(() => {
4383
+ void (async () => {
4384
+ const [err] = await tryCatch2(() => {
4385
+ generateTypeMapFile({ quiet: true });
4386
+ });
4387
+ if (err) {
4388
+ console.log(`[HotReload] initial type map generation failed: ${String(err)}`, "red");
4389
+ } else {
4390
+ console.log(`[HotReload] type map ready in background`, "green");
4391
+ }
4392
+ })();
4393
+ });
4394
+ };
4395
+
4396
+ // src/validateDeploy.ts
4397
+ var validateDeploy = ({
4398
+ services,
4399
+ deploy,
4400
+ env = process.env
4401
+ }) => {
4402
+ const findings = [];
4403
+ const serviceNames = new Set(Object.keys(services.services));
4404
+ const servicesInPresets = /* @__PURE__ */ new Map();
4405
+ for (const [presetKey, preset] of Object.entries(services.presets)) {
4406
+ for (const service of preset.services) {
4407
+ const existing = servicesInPresets.get(service) ?? [];
4408
+ existing.push(presetKey);
4409
+ servicesInPresets.set(service, existing);
4410
+ }
4411
+ }
4412
+ for (const service of serviceNames) {
4413
+ const owners = servicesInPresets.get(service) ?? [];
4414
+ if (owners.length === 0) {
4415
+ findings.push({
4416
+ severity: "error",
4417
+ code: "service-unassigned",
4418
+ message: `Service "${service}" is declared in services.services but not assigned to any preset. Every service must belong to exactly one preset.`,
4419
+ location: `services.config.ts > services.${service}`
4420
+ });
4421
+ } else if (owners.length > 1) {
4422
+ findings.push({
4423
+ severity: "error",
4424
+ code: "service-in-multiple-presets",
4425
+ message: `Service "${service}" is in multiple presets: ${owners.join(", ")}. A service may belong to exactly one preset.`,
4426
+ location: `services.config.ts > services.${service}`
4427
+ });
4428
+ }
4429
+ }
4430
+ for (const [presetKey, preset] of Object.entries(services.presets)) {
4431
+ for (const service of preset.services) {
4432
+ if (!serviceNames.has(service)) {
4433
+ findings.push({
4434
+ severity: "error",
4435
+ code: "preset-references-unknown-service",
4436
+ message: `Preset "${presetKey}" references unknown service "${service}". Add it to services.services or remove the reference.`,
4437
+ location: `services.config.ts > presets.${presetKey}.services`
4438
+ });
4439
+ }
4440
+ }
4441
+ }
4442
+ const environments = deploy.environments ?? {};
4443
+ const resourceNames = new Set(Object.keys(deploy.resources));
4444
+ for (const [envKey, envDef] of Object.entries(environments)) {
4445
+ for (const [bindingService, bindingUrl] of Object.entries(envDef.bindings)) {
4446
+ if (!serviceNames.has(bindingService)) {
4447
+ findings.push({
4448
+ severity: "error",
4449
+ code: "binding-references-unknown-service",
4450
+ message: `Environment "${envKey}" binds unknown service "${bindingService}". Either add it to services.services or remove the binding.`,
4451
+ location: `deploy.config.ts > environments.${envKey}.bindings`
4452
+ });
4453
+ }
4454
+ let parsed = null;
4455
+ try {
4456
+ parsed = new URL(bindingUrl);
4457
+ } catch {
4458
+ findings.push({
4459
+ severity: "error",
4460
+ code: "binding-invalid-url",
4461
+ message: `Environment "${envKey}" service "${bindingService}" binding is not a valid URL: "${bindingUrl}".`,
4462
+ location: `deploy.config.ts > environments.${envKey}.bindings.${bindingService}`
4463
+ });
4464
+ }
4465
+ if (parsed && !parsed.port) {
4466
+ findings.push({
4467
+ severity: "error",
4468
+ code: "binding-missing-port",
4469
+ message: `Environment "${envKey}" service "${bindingService}" binding "${bindingUrl}" is missing an explicit port. Set one to avoid the URL-spec default (80/443) silently winning.`,
4470
+ location: `deploy.config.ts > environments.${envKey}.bindings.${bindingService}`
4471
+ });
4472
+ }
4473
+ }
4474
+ if (!resourceNames.has(envDef.redis)) {
4475
+ findings.push({
4476
+ severity: "error",
4477
+ code: "unknown-redis-resource",
4478
+ message: `Environment "${envKey}" references unknown redis resource "${envDef.redis}". Add it to deploy.resources.`,
4479
+ location: `deploy.config.ts > environments.${envKey}.redis`
4480
+ });
4481
+ }
4482
+ if (!resourceNames.has(envDef.mongo)) {
4483
+ findings.push({
4484
+ severity: "error",
4485
+ code: "unknown-mongo-resource",
4486
+ message: `Environment "${envKey}" references unknown mongo resource "${envDef.mongo}". Add it to deploy.resources.`,
4487
+ location: `deploy.config.ts > environments.${envKey}.mongo`
4488
+ });
4489
+ }
4490
+ if (envDef.fallback) {
4491
+ const fallbackEnv = environments[envDef.fallback];
4492
+ if (fallbackEnv) {
4493
+ if (fallbackEnv.redis !== envDef.redis) {
4494
+ findings.push({
4495
+ severity: "error",
4496
+ code: "fallback-redis-mismatch",
4497
+ message: `Environment "${envKey}" and its fallback "${envDef.fallback}" reference different redis resources ("${envDef.redis}" vs "${fallbackEnv.redis}"). Fallback envs must share the same redis resource key to keep the shared-Redis invariant.`,
4498
+ location: `deploy.config.ts > environments.${envKey}.fallback`
4499
+ });
4500
+ }
4501
+ if (fallbackEnv.mongo !== envDef.mongo) {
4502
+ findings.push({
4503
+ severity: "error",
4504
+ code: "fallback-mongo-mismatch",
4505
+ message: `Environment "${envKey}" and its fallback "${envDef.fallback}" reference different mongo resources ("${envDef.mongo}" vs "${fallbackEnv.mongo}"). Fallback envs must share the same mongo resource key.`,
4506
+ location: `deploy.config.ts > environments.${envKey}.fallback`
4507
+ });
4508
+ }
4509
+ } else {
4510
+ findings.push({
4511
+ severity: "error",
4512
+ code: "unknown-fallback-env",
4513
+ message: `Environment "${envKey}" has fallback "${envDef.fallback}" which does not exist in deploy.environments.`,
4514
+ location: `deploy.config.ts > environments.${envKey}.fallback`
4515
+ });
4516
+ }
4517
+ }
4518
+ }
4519
+ for (const [resourceKey, resource] of Object.entries(deploy.resources)) {
4520
+ const urlValue = env[resource.urlEnvKey];
4521
+ if (urlValue === void 0 || urlValue === "") {
4522
+ findings.push({
4523
+ severity: "warning",
4524
+ code: "missing-resource-env-var",
4525
+ message: `Resource "${resourceKey}" expects env var "${resource.urlEnvKey}" but it is unset. The framework will still boot if the env is provided at runtime, but config-time tooling cannot verify the value.`,
4526
+ location: `deploy.config.ts > resources.${resourceKey}.urlEnvKey`
4527
+ });
4528
+ }
4529
+ for (const synchronizedKey of resource.synchronizedEnvKeys ?? []) {
4530
+ const value = env[synchronizedKey];
4531
+ if (value === void 0 || value === "") {
4532
+ findings.push({
4533
+ severity: "warning",
4534
+ code: "missing-synchronized-env-var",
4535
+ message: `Resource "${resourceKey}" requires synchronized env var "${synchronizedKey}" but it is unset. Boot will compute an empty hash and fall back to warning-only.`,
4536
+ location: `deploy.config.ts > resources.${resourceKey}.synchronizedEnvKeys`
4537
+ });
4538
+ }
4539
+ }
4540
+ }
4541
+ for (const service of serviceNames) {
4542
+ const boundIn = Object.entries(environments).filter(([, env2]) => service in env2.bindings);
4543
+ if (boundIn.length === 0) {
4544
+ findings.push({
4545
+ severity: "warning",
4546
+ code: "service-bound-in-no-environment",
4547
+ message: `Service "${service}" is in services.services and assigned to a preset but never bound in any environment. Requests for this service will fall through to the missing-service handler.`,
4548
+ location: `services.config.ts > services.${service}`
4549
+ });
4550
+ }
4551
+ }
4552
+ const errorCount = findings.filter((f) => f.severity === "error").length;
4553
+ const warningCount = findings.filter((f) => f.severity === "warning").length;
4554
+ return {
4555
+ ok: errorCount === 0,
4556
+ findings,
4557
+ errorCount,
4558
+ warningCount
4559
+ };
4560
+ };
4561
+ export {
4562
+ API_VERSION_TOKEN_REGEX,
4563
+ BUILT_IN_TEMPLATE_FILENAMES,
4564
+ BUILT_IN_TEMPLATE_KINDS,
4565
+ DEFAULT_DASHBOARD_PATH_PATTERN,
4566
+ SYNC_VERSION_TOKEN_REGEX,
4567
+ apiMarkerSegment,
4568
+ assertNoDuplicateNormalizedRouteKeys,
4569
+ assertValidRouteNaming,
4570
+ clearRuntimeTypeResolverCache,
4571
+ clearTemplateOverrides,
4572
+ clearTemplateRules,
4573
+ devApis,
4574
+ devFunctions,
4575
+ devSyncs,
4576
+ generateTypeMapFile,
4577
+ getInputTypeFromFile,
4578
+ getRegisteredTemplate,
4579
+ getRoutingRules,
4580
+ getSyncClientDataType,
4581
+ getTemplateRules,
4582
+ initializeAll,
4583
+ initializeApis,
4584
+ initializeFunctions,
4585
+ initializeSyncs,
4586
+ isApiFileName,
4587
+ isSyncClientFileName,
4588
+ isSyncFileName,
4589
+ isSyncServerFileName,
4590
+ listRegisteredTemplateKinds,
4591
+ registerDefaultTemplateRules,
4592
+ registerRoutingRules,
4593
+ registerTemplate,
4594
+ registerTemplateKind,
4595
+ registerTemplateRule,
4596
+ removeApiFromFile,
4597
+ removeSyncFromFile,
4598
+ resolveRuntimeTypeText,
4599
+ resolveTemplateKind,
4600
+ setupWatchers,
4601
+ syncMarkerSegment,
4602
+ upsertApiFromFile,
4603
+ upsertSyncFromFile,
4604
+ validateDeploy
4605
+ };
4606
+ //# sourceMappingURL=index.js.map