@luckystack/devkit 0.8.4 → 0.8.5

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 CHANGED
@@ -1943,8 +1943,8 @@ var getSyncClientOutputTypeDetailsFromFile = (filePath) => {
1943
1943
 
1944
1944
  // src/typeMap/functionsMeta.ts
1945
1945
  import * as ts7 from "typescript";
1946
- import fs4 from "fs";
1947
- import path7 from "path";
1946
+ import fs5 from "fs";
1947
+ import path8 from "path";
1948
1948
 
1949
1949
  // src/typeMap/typeContext.ts
1950
1950
  import * as ts6 from "typescript";
@@ -2038,7 +2038,143 @@ var sanitizeTypeAndCollectImports = ({
2038
2038
  };
2039
2039
 
2040
2040
  // src/typeMap/functionsMeta.ts
2041
- import { getGeneratedSocketTypesPath as getGeneratedSocketTypesPath5, getServerFunctionDirs } from "@luckystack/core";
2041
+ import { getGeneratedSocketTypesPath as getGeneratedSocketTypesPath5, ROOT_DIR as ROOT_DIR4 } from "@luckystack/core";
2042
+
2043
+ // src/functionRegistry.ts
2044
+ import fs4 from "fs";
2045
+ import path7 from "path";
2046
+ import { ROOT_DIR as ROOT_DIR3, getServerFunctionDirs, isTestFile, isTestDirectory } from "@luckystack/core";
2047
+ var normalizePath = (value) => value.replaceAll("\\", "/");
2048
+ var toWorkspaceRelative = (absolutePath) => normalizePath(path7.relative(ROOT_DIR3, absolutePath));
2049
+ var formatFunctionKeyPath = (keyPath) => keyPath.join(".");
2050
+ var functionModuleFileName = (keyPath) => {
2051
+ const fileName = keyPath.at(-1);
2052
+ if (fileName === void 0) {
2053
+ throw new Error("[function-injection] Internal error: encountered an empty function key path.");
2054
+ }
2055
+ return fileName;
2056
+ };
2057
+ var formatFunctionKeyConflict = (keyPath, firstSourcePath, secondSourcePath) => `[function-injection] Conflict at \`functions.${formatFunctionKeyPath(keyPath)}\`: defined in both \`${firstSourcePath}\` and \`${secondSourcePath}\`. Delete one \u2014 \`shared/\` is the canonical location for framework re-exports.`;
2058
+ var formatFunctionNamespaceConflict = (moduleKeyPath, moduleSourcePath, nestedKeyPath, nestedSourcePath) => `[function-injection] Conflict at \`functions.${formatFunctionKeyPath(moduleKeyPath)}\`: \`${moduleSourcePath}\` defines it as a module, but \`${nestedSourcePath}\` nests \`functions.${formatFunctionKeyPath(nestedKeyPath)}\` underneath it. Rename one \u2014 a key cannot be both a module and a namespace.`;
2059
+ var walkFunctionRoot = (dir, rootDir, basePath, collected) => {
2060
+ const entries = fs4.readdirSync(dir).toSorted();
2061
+ for (const entry of entries) {
2062
+ if (entry === "node_modules") continue;
2063
+ const fullPath = path7.join(dir, entry);
2064
+ if (getRoutingRules().ignore(toWorkspaceRelative(fullPath))) continue;
2065
+ if (fs4.statSync(fullPath).isDirectory()) {
2066
+ if (isTestDirectory(entry)) continue;
2067
+ walkFunctionRoot(fullPath, rootDir, [...basePath, entry], collected);
2068
+ continue;
2069
+ }
2070
+ if (!entry.endsWith(".ts")) continue;
2071
+ if (isTestFile(entry)) continue;
2072
+ collected.push({
2073
+ keyPath: [...basePath, entry.slice(0, -".ts".length)],
2074
+ sourcePath: toWorkspaceRelative(fullPath),
2075
+ rootPath: toWorkspaceRelative(rootDir)
2076
+ });
2077
+ }
2078
+ };
2079
+ var resolveNamespaceConflicts = (modules, reportConflict) => {
2080
+ const byDottedKey = /* @__PURE__ */ new Map();
2081
+ for (const module of modules) {
2082
+ byDottedKey.set(formatFunctionKeyPath(module.keyPath), module);
2083
+ }
2084
+ return modules.filter((module) => {
2085
+ for (let depth = 1; depth < module.keyPath.length; depth++) {
2086
+ const ancestor = byDottedKey.get(formatFunctionKeyPath(module.keyPath.slice(0, depth)));
2087
+ if (!ancestor) continue;
2088
+ reportConflict(formatFunctionNamespaceConflict(
2089
+ ancestor.keyPath,
2090
+ ancestor.sourcePath,
2091
+ module.keyPath,
2092
+ module.sourcePath
2093
+ ));
2094
+ return false;
2095
+ }
2096
+ return true;
2097
+ });
2098
+ };
2099
+ var collectFunctionModules = (options = {}) => {
2100
+ const { roots, onConflict } = options;
2101
+ const functionRoots = roots ?? getServerFunctionDirs();
2102
+ const claimedByDottedKey = /* @__PURE__ */ new Map();
2103
+ const reportConflict = (message) => {
2104
+ if (!onConflict) throw new Error(message);
2105
+ onConflict(message);
2106
+ };
2107
+ for (const root of functionRoots) {
2108
+ if (!fs4.existsSync(root)) continue;
2109
+ const fromThisRoot = [];
2110
+ walkFunctionRoot(root, root, [], fromThisRoot);
2111
+ for (const module of fromThisRoot) {
2112
+ const dottedKey = formatFunctionKeyPath(module.keyPath);
2113
+ const claimed = claimedByDottedKey.get(dottedKey);
2114
+ if (claimed) {
2115
+ reportConflict(formatFunctionKeyConflict(
2116
+ module.keyPath,
2117
+ claimed.sourcePath,
2118
+ module.sourcePath
2119
+ ));
2120
+ continue;
2121
+ }
2122
+ claimedByDottedKey.set(dottedKey, module);
2123
+ }
2124
+ }
2125
+ const modules = [...claimedByDottedKey.values()].toSorted((left, right) => formatFunctionKeyPath(left.keyPath).localeCompare(formatFunctionKeyPath(right.keyPath)));
2126
+ return resolveNamespaceConflicts(modules, reportConflict);
2127
+ };
2128
+ var createRenderNode = () => ({ children: /* @__PURE__ */ new Map(), module: null });
2129
+ var renderModuleEntry = (varName, fileName, indent) => `(() => {
2130
+ ${indent} const { default: _default, ...named } = ${varName} as Record<string, unknown>;
2131
+ ${indent} const cleaned = Object.fromEntries(Object.entries(named).filter(([key]) => key !== '__esModule'));
2132
+ ${indent} if (Object.keys(cleaned).length > 0) return cleaned;
2133
+ ${indent} return _default !== undefined ? { ${JSON.stringify(fileName)}: _default } : {};
2134
+ ${indent}})()`;
2135
+ var renderNode = (node, indent) => {
2136
+ let output = "";
2137
+ for (const [name, child] of node.children) {
2138
+ if (child.module) {
2139
+ output += `${indent}${JSON.stringify(name)}: ${renderModuleEntry(child.module.varName, child.module.fileName, indent)},
2140
+ `;
2141
+ continue;
2142
+ }
2143
+ output += `${indent}${JSON.stringify(name)}: {
2144
+ ${renderNode(child, `${indent} `)}${indent}},
2145
+ `;
2146
+ }
2147
+ return output;
2148
+ };
2149
+ var renderFunctionsMap = ({
2150
+ modules,
2151
+ importPrefix,
2152
+ varPrefix = "fn"
2153
+ }) => {
2154
+ const imports = [];
2155
+ const root = createRenderNode();
2156
+ for (const [index, module] of modules.entries()) {
2157
+ const varName = `${varPrefix}${index}`;
2158
+ imports.push(`import * as ${varName} from '${importPrefix}${module.sourcePath.replace(/\.ts$/, "")}';`);
2159
+ let target = root;
2160
+ for (const segment of module.keyPath.slice(0, -1)) {
2161
+ let child = target.children.get(segment);
2162
+ if (!child) {
2163
+ child = createRenderNode();
2164
+ target.children.set(segment, child);
2165
+ }
2166
+ target = child;
2167
+ }
2168
+ const fileName = functionModuleFileName(module.keyPath);
2169
+ const leaf = createRenderNode();
2170
+ leaf.module = { varName, fileName };
2171
+ target.children.set(fileName, leaf);
2172
+ }
2173
+ const source = "export const functions: Record<string, Record<string, unknown>> = {\n" + renderNode(root, " ") + "};";
2174
+ return { imports, source };
2175
+ };
2176
+
2177
+ // src/typeMap/functionsMeta.ts
2042
2178
  var stripDefaultValues = (params) => {
2043
2179
  return params.replaceAll(/\s*=(?!>)[^,)]+/g, "");
2044
2180
  };
@@ -2066,9 +2202,9 @@ var simplifyInferredType = (value) => {
2066
2202
  if (/\bRedis\b/.test(value)) return "Redis";
2067
2203
  return value;
2068
2204
  };
2069
- var getGeneratedFileDir = () => path7.dirname(getGeneratedSocketTypesPath5());
2205
+ var getGeneratedFileDir = () => path8.dirname(getGeneratedSocketTypesPath5());
2070
2206
  var sourceModuleSpecifier = (sourceFilePath) => {
2071
- const relative = path7.relative(getGeneratedFileDir(), sourceFilePath).replaceAll("\\", "/").replace(/\.tsx?$/i, "");
2207
+ const relative = path8.relative(getGeneratedFileDir(), sourceFilePath).replaceAll("\\", "/").replace(/\.tsx?$/i, "");
2072
2208
  return relative.startsWith(".") ? relative : `./${relative}`;
2073
2209
  };
2074
2210
  var portableInferredValueType = (inferred, sourceFilePath, exportName) => {
@@ -2079,9 +2215,9 @@ var relativizeModuleSpecifier = (specifier, sourceFilePath) => {
2079
2215
  if (!specifier.startsWith("./") && !specifier.startsWith("../")) {
2080
2216
  return specifier;
2081
2217
  }
2082
- const absolute = path7.resolve(path7.dirname(sourceFilePath), specifier);
2083
- const rel = path7.relative(getGeneratedFileDir(), absolute);
2084
- const normalized = rel.split(path7.sep).join("/");
2218
+ const absolute = path8.resolve(path8.dirname(sourceFilePath), specifier);
2219
+ const rel = path8.relative(getGeneratedFileDir(), absolute);
2220
+ const normalized = rel.split(path8.sep).join("/");
2085
2221
  return normalized.startsWith(".") ? normalized : `./${normalized}`;
2086
2222
  };
2087
2223
  var findProgramVariableDeclaration = (sourceFile, exportName) => {
@@ -2255,7 +2391,7 @@ var findSignatureForExport = (name, sourceFile, rawContent, filePath, availableE
2255
2391
  };
2256
2392
  var parseFunctionFile = (fullPath, collectors) => {
2257
2393
  try {
2258
- const rawContent = fs4.readFileSync(fullPath, "utf8");
2394
+ const rawContent = fs5.readFileSync(fullPath, "utf8");
2259
2395
  const sourceFile = ts7.createSourceFile(fullPath, rawContent, ts7.ScriptTarget.Latest, true);
2260
2396
  const { availableExports, fileImports } = parseFileTypeContext(rawContent);
2261
2397
  const program = getServerProgram();
@@ -2337,48 +2473,19 @@ var parseFunctionFile = (fullPath, collectors) => {
2337
2473
  return null;
2338
2474
  }
2339
2475
  };
2340
- var walkDirToIR = (dir, collectors) => {
2341
- if (!fs4.existsSync(dir)) return null;
2342
- const entries = fs4.readdirSync(dir, { withFileTypes: true });
2343
- const children = /* @__PURE__ */ new Map();
2344
- for (const entry of entries) {
2345
- const fullPath = path7.join(dir, entry.name);
2346
- if (entry.isDirectory()) {
2347
- const subDir = walkDirToIR(fullPath, collectors);
2348
- if (subDir && subDir.children.size > 0) {
2349
- children.set(entry.name, subDir);
2350
- }
2476
+ var insertFileNode = (root, keyPath, file) => {
2477
+ let target = root;
2478
+ for (const segment of keyPath.slice(0, -1)) {
2479
+ const existing = target.children.get(segment);
2480
+ if (existing?.kind === "dir") {
2481
+ target = existing;
2351
2482
  continue;
2352
2483
  }
2353
- if (!entry.isFile() || !entry.name.endsWith(".ts")) continue;
2354
- const fileName = entry.name.replace(".ts", "");
2355
- const parsed = parseFunctionFile(fullPath, collectors);
2356
- if (parsed && (parsed.exports.size > 0 || parsed.defaultExportName !== null || parsed.wildcardReExport !== null)) {
2357
- children.set(fileName, parsed);
2358
- }
2359
- }
2360
- return { kind: "dir", children, sourcePath: dir };
2361
- };
2362
- var formatConflict = (keyPath, a, b) => {
2363
- const dottedKey = keyPath.join(".");
2364
- 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.`;
2365
- };
2366
- var mergeIR = (target, source, prefix = []) => {
2367
- for (const [name, sourceChild] of source.children) {
2368
- const targetChild = target.children.get(name);
2369
- if (!targetChild) {
2370
- target.children.set(name, sourceChild);
2371
- continue;
2372
- }
2373
- const keyPath = [...prefix, name];
2374
- if (targetChild.kind !== sourceChild.kind) {
2375
- throw new Error(formatConflict(keyPath, targetChild, sourceChild));
2376
- }
2377
- if (targetChild.kind === "file" || sourceChild.kind === "file") {
2378
- throw new Error(formatConflict(keyPath, targetChild, sourceChild));
2379
- }
2380
- mergeIR(targetChild, sourceChild, keyPath);
2484
+ const created = { kind: "dir", children: /* @__PURE__ */ new Map(), sourcePath: segment };
2485
+ target.children.set(segment, created);
2486
+ target = created;
2381
2487
  }
2488
+ target.children.set(functionModuleFileName(keyPath), file);
2382
2489
  };
2383
2490
  var serializeIRDir = (dir, indent) => {
2384
2491
  let output = "";
@@ -2426,13 +2533,17 @@ ${fileOutput}${indent}};
2426
2533
  return output;
2427
2534
  };
2428
2535
  var generateServerFunctions = (collectors) => {
2429
- const dirs = getServerFunctionDirs();
2430
- if (dirs.length === 0) return "";
2536
+ const modules = collectFunctionModules();
2537
+ if (modules.length === 0) return "";
2431
2538
  const merged = { kind: "dir", children: /* @__PURE__ */ new Map(), sourcePath: "<merged>" };
2432
- for (const dir of dirs) {
2433
- const ir = walkDirToIR(dir, collectors);
2434
- if (!ir) continue;
2435
- mergeIR(merged, ir);
2539
+ for (const module of modules) {
2540
+ const absolutePath = path8.resolve(ROOT_DIR4, module.sourcePath);
2541
+ const parsed = parseFunctionFile(absolutePath, collectors);
2542
+ if (!parsed) continue;
2543
+ if (parsed.exports.size === 0 && parsed.defaultExportName === null && parsed.wildcardReExport === null) {
2544
+ continue;
2545
+ }
2546
+ insertFileNode(merged, module.keyPath, parsed);
2436
2547
  }
2437
2548
  return serializeIRDir(merged, " ");
2438
2549
  };
@@ -2441,28 +2552,28 @@ var generateServerFunctions = (collectors) => {
2441
2552
  import { getSrcDir as getSrcDir2 } from "@luckystack/core";
2442
2553
 
2443
2554
  // src/routeNamingValidation.ts
2444
- import fs5 from "fs";
2445
- import path8 from "path";
2446
- import { ROOT_DIR as ROOT_DIR3 } from "@luckystack/core";
2447
- var normalizePath = (value) => {
2555
+ import fs6 from "fs";
2556
+ import path9 from "path";
2557
+ import { ROOT_DIR as ROOT_DIR5 } from "@luckystack/core";
2558
+ var normalizePath2 = (value) => {
2448
2559
  return value.replaceAll("\\", "/");
2449
2560
  };
2450
- var toRel = (absolute) => path8.relative(ROOT_DIR3, absolute).replaceAll("\\", "/");
2561
+ var toRel = (absolute) => path9.relative(ROOT_DIR5, absolute).replaceAll("\\", "/");
2451
2562
  var isInsidePrivateRouteSubfolder = (fullPath) => {
2452
2563
  const { privateFolderPrefix, apiMarker, syncMarker } = getRoutingRules();
2453
- const segments = normalizePath(fullPath).split("/");
2564
+ const segments = normalizePath2(fullPath).split("/");
2454
2565
  const markerIndex = segments.findIndex((segment) => segment === apiMarker || segment === syncMarker);
2455
2566
  if (markerIndex === -1) return false;
2456
2567
  return segments.slice(markerIndex + 1).some((segment) => segment.startsWith(privateFolderPrefix));
2457
2568
  };
2458
2569
  var walkRouteFiles = (dir, results = []) => {
2459
- const entries = fs5.readdirSync(dir, { withFileTypes: true });
2570
+ const entries = fs6.readdirSync(dir, { withFileTypes: true });
2460
2571
  const apiSeg = apiMarkerSegment();
2461
2572
  const syncSeg = syncMarkerSegment();
2462
2573
  const { ignore } = getRoutingRules();
2463
2574
  for (const entry of entries) {
2464
- const fullPath = path8.join(dir, entry.name);
2465
- const normalizedFullPath = normalizePath(fullPath);
2575
+ const fullPath = path9.join(dir, entry.name);
2576
+ const normalizedFullPath = normalizePath2(fullPath);
2466
2577
  if (ignore(toRel(fullPath))) continue;
2467
2578
  if (isInsidePrivateRouteSubfolder(fullPath)) continue;
2468
2579
  if (entry.isDirectory()) {
@@ -2497,8 +2608,8 @@ var getFileRouteToken = ({
2497
2608
  };
2498
2609
  var validateRouteFilePath = (filePath) => {
2499
2610
  const issues = [];
2500
- const normalizedFilePath = normalizePath(path8.resolve(filePath));
2501
- const fileName = path8.basename(filePath);
2611
+ const normalizedFilePath = normalizePath2(path9.resolve(filePath));
2612
+ const fileName = path9.basename(filePath);
2502
2613
  const apiSeg = apiMarkerSegment();
2503
2614
  const syncSeg = syncMarkerSegment();
2504
2615
  if (normalizedFilePath.includes(apiSeg)) {
@@ -2546,7 +2657,7 @@ var resolveApiRouteKey = ({
2546
2657
  filePath
2547
2658
  }) => {
2548
2659
  const rules2 = getRoutingRules();
2549
- const relativePath = normalizePath(path8.relative(srcDir, filePath));
2660
+ const relativePath = normalizePath2(path9.relative(srcDir, filePath));
2550
2661
  const segments = relativePath.split("/");
2551
2662
  const apiIndex = segments.indexOf(rules2.apiMarker);
2552
2663
  if (apiIndex === -1 || apiIndex === segments.length - 1) {
@@ -2569,7 +2680,7 @@ var resolveSyncRouteKey = ({
2569
2680
  filePath
2570
2681
  }) => {
2571
2682
  const rules2 = getRoutingRules();
2572
- const relativePath = normalizePath(path8.relative(srcDir, filePath));
2683
+ const relativePath = normalizePath2(path9.relative(srcDir, filePath));
2573
2684
  const segments = relativePath.split("/");
2574
2685
  const syncIndex = segments.indexOf(rules2.syncMarker);
2575
2686
  if (syncIndex === -1 || syncIndex === segments.length - 1) {
@@ -2603,7 +2714,7 @@ var collectDuplicateNormalizedRouteKeyIssues = (srcDir) => {
2603
2714
  const apiSeg = apiMarkerSegment();
2604
2715
  const syncSeg = syncMarkerSegment();
2605
2716
  for (const filePath of allRouteFiles) {
2606
- const normalizedFilePath = normalizePath(path8.resolve(filePath));
2717
+ const normalizedFilePath = normalizePath2(path9.resolve(filePath));
2607
2718
  if (normalizedFilePath.includes(apiSeg)) {
2608
2719
  const routeKey = resolveApiRouteKey({ srcDir, filePath });
2609
2720
  if (!routeKey) {
@@ -2687,12 +2798,12 @@ var assertNoDuplicateNormalizedRouteKeys = ({
2687
2798
  var walkPageFiles = (dir, results = []) => {
2688
2799
  let entries;
2689
2800
  try {
2690
- entries = fs5.readdirSync(dir, { withFileTypes: true });
2801
+ entries = fs6.readdirSync(dir, { withFileTypes: true });
2691
2802
  } catch {
2692
2803
  return results;
2693
2804
  }
2694
2805
  for (const entry of entries) {
2695
- const fullPath = path8.join(dir, entry.name);
2806
+ const fullPath = path9.join(dir, entry.name);
2696
2807
  if (entry.isDirectory()) {
2697
2808
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
2698
2809
  walkPageFiles(fullPath, results);
@@ -2709,11 +2820,11 @@ var collectDuplicatePageRoutes = (srcDir) => {
2709
2820
  const pageFiles = walkPageFiles(srcDir);
2710
2821
  const routeToFiles = /* @__PURE__ */ new Map();
2711
2822
  for (const absoluteFilePath of pageFiles) {
2712
- const relative = normalizePath(path8.relative(srcDir, absoluteFilePath));
2823
+ const relative = normalizePath2(path9.relative(srcDir, absoluteFilePath));
2713
2824
  const result = validatePagePath(relative);
2714
2825
  if (!result.valid || !result.route) continue;
2715
2826
  const list = routeToFiles.get(result.route) ?? [];
2716
- list.push(normalizePath(path8.relative(ROOT_DIR3, absoluteFilePath)));
2827
+ list.push(normalizePath2(path9.relative(ROOT_DIR5, absoluteFilePath)));
2717
2828
  routeToFiles.set(result.route, list);
2718
2829
  }
2719
2830
  const issues = [];
@@ -2971,10 +3082,10 @@ var registerDefaultTemplateRules = () => {
2971
3082
  registerDefaultTemplateRules();
2972
3083
 
2973
3084
  // src/loader.ts
2974
- import fs6 from "fs";
2975
- import path9 from "path";
3085
+ import fs7 from "fs";
3086
+ import path10 from "path";
2976
3087
  import { pathToFileURL } from "url";
2977
- import { tryCatch, getServerFunctionDirs as getServerFunctionDirs2, getSrcDir as getSrcDir3, ROOT_DIR as ROOT_DIR4 } from "@luckystack/core";
3088
+ import { tryCatch, getSrcDir as getSrcDir3, ROOT_DIR as ROOT_DIR6 } from "@luckystack/core";
2978
3089
 
2979
3090
  // src/runtimeTypeResolver.ts
2980
3091
  import * as ts8 from "typescript";
@@ -3352,9 +3463,9 @@ var resolveRuntimeTypeText = ({
3352
3463
  var devApis = {};
3353
3464
  var devSyncs = {};
3354
3465
  var devFunctions = {};
3355
- var normalizePath2 = (value) => value.replaceAll("\\", "/");
3466
+ var normalizePath3 = (value) => value.replaceAll("\\", "/");
3356
3467
  var isIgnoredPath = (absolutePath) => {
3357
- const relativeFromRoot = normalizePath2(path9.relative(ROOT_DIR4, absolutePath));
3468
+ const relativeFromRoot = normalizePath3(path10.relative(ROOT_DIR6, absolutePath));
3358
3469
  return getRoutingRules().ignore(relativeFromRoot);
3359
3470
  };
3360
3471
  var mapApiPageLocation = (pageLocation) => {
@@ -3364,14 +3475,14 @@ var mapSyncPageLocation = (pageLocation) => {
3364
3475
  return pageLocation || "system";
3365
3476
  };
3366
3477
  var resolveApiRouteMetaFromPath = (filePath) => {
3367
- const absolutePath = path9.resolve(filePath);
3368
- const normalizedAbsolutePath = normalizePath2(absolutePath);
3369
- const normalizedSrcDir = normalizePath2(getSrcDir3());
3478
+ const absolutePath = path10.resolve(filePath);
3479
+ const normalizedAbsolutePath = normalizePath3(absolutePath);
3480
+ const normalizedSrcDir = normalizePath3(getSrcDir3());
3370
3481
  if (!normalizedAbsolutePath.startsWith(normalizedSrcDir) || !normalizedAbsolutePath.endsWith(".ts")) {
3371
3482
  return null;
3372
3483
  }
3373
3484
  const rules2 = getRoutingRules();
3374
- const relativePath = normalizePath2(path9.relative(getSrcDir3(), absolutePath));
3485
+ const relativePath = normalizePath3(path10.relative(getSrcDir3(), absolutePath));
3375
3486
  const segments = relativePath.split("/");
3376
3487
  const apiIndex = segments.indexOf(rules2.apiMarker);
3377
3488
  if (apiIndex === -1 || apiIndex === segments.length - 1) {
@@ -3391,14 +3502,14 @@ var resolveApiRouteMetaFromPath = (filePath) => {
3391
3502
  return { routeKey, absolutePath };
3392
3503
  };
3393
3504
  var resolveSyncRouteMetaFromPath = (filePath) => {
3394
- const absolutePath = path9.resolve(filePath);
3395
- const normalizedAbsolutePath = normalizePath2(absolutePath);
3396
- const normalizedSrcDir = normalizePath2(getSrcDir3());
3505
+ const absolutePath = path10.resolve(filePath);
3506
+ const normalizedAbsolutePath = normalizePath3(absolutePath);
3507
+ const normalizedSrcDir = normalizePath3(getSrcDir3());
3397
3508
  if (!normalizedAbsolutePath.startsWith(normalizedSrcDir) || !normalizedAbsolutePath.endsWith(".ts")) {
3398
3509
  return null;
3399
3510
  }
3400
3511
  const rules2 = getRoutingRules();
3401
- const relativePath = normalizePath2(path9.relative(getSrcDir3(), absolutePath));
3512
+ const relativePath = normalizePath3(path10.relative(getSrcDir3(), absolutePath));
3402
3513
  const segments = relativePath.split("/");
3403
3514
  const syncIndex = segments.indexOf(rules2.syncMarker);
3404
3515
  if (syncIndex === -1 || syncIndex === segments.length - 1) {
@@ -3442,13 +3553,13 @@ var importFile = async (absolutePath) => {
3442
3553
  };
3443
3554
  var collectTsFiles = (dir, relativeTo = "") => {
3444
3555
  const results = [];
3445
- const entries = fs6.readdirSync(dir);
3556
+ const entries = fs7.readdirSync(dir);
3446
3557
  for (const entry of entries) {
3447
3558
  if (entry === "node_modules") continue;
3448
- const entryPath = path9.join(dir, entry);
3559
+ const entryPath = path10.join(dir, entry);
3449
3560
  if (isIgnoredPath(entryPath)) continue;
3450
3561
  const relPath = relativeTo ? `${relativeTo}/${entry}` : entry;
3451
- if (fs6.statSync(entryPath).isDirectory()) {
3562
+ if (fs7.statSync(entryPath).isDirectory()) {
3452
3563
  results.push(...collectTsFiles(entryPath, relPath));
3453
3564
  } else if (entry.endsWith(".ts") && !isRouteTestFile(entry)) {
3454
3565
  results.push(relPath);
@@ -3479,16 +3590,16 @@ var resolveFunctionModule = (loadedModule, fileName) => {
3479
3590
  var initializeApis = async () => {
3480
3591
  for (const key of Object.keys(devApis)) delete devApis[key];
3481
3592
  clearRuntimeTypeResolverCache();
3482
- const srcFolder = fs6.readdirSync(getSrcDir3());
3593
+ const srcFolder = fs7.readdirSync(getSrcDir3());
3483
3594
  for (const file of srcFolder) {
3484
3595
  await scanApiFolder(file);
3485
3596
  }
3486
3597
  };
3487
3598
  var upsertApiFromFile = async (filePath) => {
3488
- if (isIgnoredPath(path9.resolve(filePath))) return;
3599
+ if (isIgnoredPath(path10.resolve(filePath))) return;
3489
3600
  const routeMeta = resolveApiRouteMetaFromPath(filePath);
3490
3601
  if (!routeMeta) {
3491
- const normalized = normalizePath2(path9.resolve(filePath));
3602
+ const normalized = normalizePath3(path10.resolve(filePath));
3492
3603
  if (normalized.includes("/_api/") && normalized.endsWith(".ts") && !isRouteTestFile(normalized)) {
3493
3604
  console.log(
3494
3605
  `[loader][api] invalid filename: ${normalized}. Expected <name>_v<number>.ts. File will not be loaded.`,
@@ -3536,13 +3647,13 @@ var removeApiFromFile = (filePath) => {
3536
3647
  delete devApis[routeMeta.routeKey];
3537
3648
  };
3538
3649
  var scanApiFolder = async (file, basePath = "") => {
3539
- const fullPath = path9.join(getSrcDir3(), basePath, file);
3650
+ const fullPath = path10.join(getSrcDir3(), basePath, file);
3540
3651
  if (isIgnoredPath(fullPath)) return;
3541
- if (!fs6.statSync(fullPath).isDirectory()) return;
3652
+ if (!fs7.statSync(fullPath).isDirectory()) return;
3542
3653
  if (!file.toLowerCase().endsWith("api")) {
3543
- const subFolders = fs6.readdirSync(fullPath);
3654
+ const subFolders = fs7.readdirSync(fullPath);
3544
3655
  for (const sub of subFolders) {
3545
- await scanApiFolder(sub, path9.join(basePath, file));
3656
+ await scanApiFolder(sub, path10.join(basePath, file));
3546
3657
  }
3547
3658
  return;
3548
3659
  }
@@ -3555,7 +3666,7 @@ var scanApiFolder = async (file, basePath = "") => {
3555
3666
  const versionMatch = rawApiName.match(apiRules.apiVersionRegex);
3556
3667
  if (!versionMatch) {
3557
3668
  console.log(
3558
- `[loader][api] invalid filename: ${path9.join(fullPath, relFile)}. Expected <name>_v<number>.ts. File will not be loaded.`,
3669
+ `[loader][api] invalid filename: ${path10.join(fullPath, relFile)}. Expected <name>_v<number>.ts. File will not be loaded.`,
3559
3670
  "red"
3560
3671
  );
3561
3672
  continue;
@@ -3563,7 +3674,7 @@ var scanApiFolder = async (file, basePath = "") => {
3563
3674
  const version = `v${versionMatch[1]}`;
3564
3675
  const apiName = rawApiName.replace(apiRules.apiVersionRegex, "");
3565
3676
  const routeKey = `api/${mappedPageLocation}/${apiName}/${version}`;
3566
- const modulePath = path9.resolve(path9.join(fullPath, relFile));
3677
+ const modulePath = path10.resolve(path10.join(fullPath, relFile));
3567
3678
  const [err, module] = await tryCatch(async () => importFile(modulePath));
3568
3679
  if (err) {
3569
3680
  console.log(`[loader][api] failed to import ${routeKey} from ${modulePath}:`, err, "red");
@@ -3592,16 +3703,16 @@ var scanApiFolder = async (file, basePath = "") => {
3592
3703
  var initializeSyncs = async () => {
3593
3704
  for (const key of Object.keys(devSyncs)) delete devSyncs[key];
3594
3705
  clearRuntimeTypeResolverCache();
3595
- const srcFolder = fs6.readdirSync(getSrcDir3());
3706
+ const srcFolder = fs7.readdirSync(getSrcDir3());
3596
3707
  for (const file of srcFolder) {
3597
3708
  await scanSyncFolder(file);
3598
3709
  }
3599
3710
  };
3600
3711
  var upsertSyncFromFile = async (filePath) => {
3601
- if (isIgnoredPath(path9.resolve(filePath))) return;
3712
+ if (isIgnoredPath(path10.resolve(filePath))) return;
3602
3713
  const routeMeta = resolveSyncRouteMetaFromPath(filePath);
3603
3714
  if (!routeMeta) {
3604
- const normalized = normalizePath2(path9.resolve(filePath));
3715
+ const normalized = normalizePath3(path10.resolve(filePath));
3605
3716
  if (normalized.includes(`/${getRoutingRules().syncMarker}/`) && normalized.endsWith(".ts") && !isRouteTestFile(normalized)) {
3606
3717
  console.log(
3607
3718
  `[loader][sync] invalid filename: ${normalized}. Expected <name>_server_v<number>.ts or <name>_client_v<number>.ts. File will not be loaded.`,
@@ -3654,13 +3765,13 @@ var removeSyncFromFile = (filePath) => {
3654
3765
  delete devSyncs[routeMeta.routeKey];
3655
3766
  };
3656
3767
  var scanSyncFolder = async (file, basePath = "") => {
3657
- const fullPath = path9.join(getSrcDir3(), basePath, file);
3768
+ const fullPath = path10.join(getSrcDir3(), basePath, file);
3658
3769
  if (isIgnoredPath(fullPath)) return;
3659
- if (!fs6.statSync(fullPath).isDirectory()) return;
3770
+ if (!fs7.statSync(fullPath).isDirectory()) return;
3660
3771
  if (!file.toLowerCase().endsWith("sync")) {
3661
- const subFolders = fs6.readdirSync(fullPath);
3772
+ const subFolders = fs7.readdirSync(fullPath);
3662
3773
  for (const sub of subFolders) {
3663
- await scanSyncFolder(sub, path9.join(basePath, file));
3774
+ await scanSyncFolder(sub, path10.join(basePath, file));
3664
3775
  }
3665
3776
  return;
3666
3777
  }
@@ -3672,7 +3783,7 @@ var scanSyncFolder = async (file, basePath = "") => {
3672
3783
  const syncMatch = rawSyncFileName.match(syncRules.syncVersionRegex);
3673
3784
  if (!syncMatch) {
3674
3785
  console.log(
3675
- `[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.`,
3786
+ `[loader][sync] invalid filename: ${path10.join(fullPath, relFile)}. Expected <name>_server_v<number>.ts or <name>_client_v<number>.ts. File will not be loaded.`,
3676
3787
  "red"
3677
3788
  );
3678
3789
  continue;
@@ -3682,7 +3793,7 @@ var scanSyncFolder = async (file, basePath = "") => {
3682
3793
  const syncName = rawSyncFileName.replace(syncRules.syncVersionRegex, "");
3683
3794
  const mappedPageLocation = mapSyncPageLocation(pageLocation);
3684
3795
  const routeBaseKey = `sync/${mappedPageLocation}/${syncName}/${version}`;
3685
- const filePath = path9.resolve(path9.join(fullPath, relFile));
3796
+ const filePath = path10.resolve(path10.join(fullPath, relFile));
3686
3797
  const [fileError, fileResult] = await tryCatch(async () => importFile(filePath));
3687
3798
  if (fileError) {
3688
3799
  console.log(`[loader][sync] failed to import ${filePath}:`, fileError, "red");
@@ -3706,79 +3817,50 @@ var scanSyncFolder = async (file, basePath = "") => {
3706
3817
  }
3707
3818
  }
3708
3819
  };
3709
- var functionClaimMap = /* @__PURE__ */ new Map();
3710
3820
  var initializeFunctions = async () => {
3711
3821
  for (const key of Object.keys(devFunctions)) delete devFunctions[key];
3712
- functionClaimMap.clear();
3713
- const dirs = getServerFunctionDirs2();
3714
- for (const dir of dirs) {
3715
- if (fs6.existsSync(dir)) {
3716
- await scanFunctionsFolder(dir, dir);
3717
- }
3718
- }
3719
- };
3720
- var scanFunctionsFolder = async (dir, rootDir, basePath = []) => {
3721
- const entries = fs6.readdirSync(dir);
3722
- for (const entry of entries) {
3723
- const fullPath = path9.join(dir, entry);
3724
- if (isIgnoredPath(fullPath)) continue;
3725
- const stat = fs6.statSync(fullPath);
3726
- if (stat.isDirectory()) {
3727
- await scanFunctionsFolder(fullPath, rootDir, [...basePath, entry]);
3728
- continue;
3729
- }
3730
- if (!entry.endsWith(".ts")) {
3731
- continue;
3822
+ const modules = collectFunctionModules({
3823
+ onConflict: (message) => {
3824
+ console.log(`[loader][function] ${message} Skipping the second copy.`, "red");
3732
3825
  }
3733
- const [err, module] = await tryCatch(async () => importFile(fullPath));
3826
+ });
3827
+ for (const functionModule of modules) {
3828
+ const absolutePath = path10.resolve(ROOT_DIR6, functionModule.sourcePath);
3829
+ const [err, module] = await tryCatch(async () => importFile(absolutePath));
3734
3830
  if (err) {
3735
- console.log(`[loader][function] failed to import ${fullPath}:`, err, "red");
3831
+ console.log(`[loader][function] failed to import ${absolutePath}:`, err, "red");
3736
3832
  continue;
3737
3833
  }
3738
- const fileName = entry.replace(".ts", "");
3834
+ const fileName = functionModuleFileName(functionModule.keyPath);
3739
3835
  const resolvedFunctionModule = resolveFunctionModule(module, fileName);
3740
3836
  if (!isMergeable(resolvedFunctionModule)) continue;
3741
- const keyPath = [...basePath, fileName].join(".");
3742
- const previousRoot = functionClaimMap.get(keyPath);
3743
- if (previousRoot !== void 0 && previousRoot !== rootDir) {
3744
- console.log(
3745
- `[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).`,
3746
- "red"
3747
- );
3748
- continue;
3749
- }
3750
- functionClaimMap.set(keyPath, rootDir);
3751
3837
  let target = devFunctions;
3752
- for (const part of basePath) {
3838
+ for (const part of functionModule.keyPath.slice(0, -1)) {
3753
3839
  const existing = target[part];
3754
3840
  if (!existing || typeof existing !== "object") {
3755
3841
  target[part] = {};
3756
3842
  }
3757
3843
  target = target[part];
3758
3844
  }
3759
- const existingAtFileName = target[fileName];
3760
- if (existingAtFileName !== void 0 && isMergeable(resolvedFunctionModule) && isMergeable(existingAtFileName)) {
3761
- Object.assign(resolvedFunctionModule, existingAtFileName);
3762
- }
3763
3845
  target[fileName] = resolvedFunctionModule;
3764
3846
  }
3765
3847
  };
3766
3848
 
3767
3849
  // src/hotReload.ts
3768
3850
  import { watch } from "chokidar";
3769
- import fs10 from "fs";
3770
- import path13 from "path";
3851
+ import fs11 from "fs";
3852
+ import path14 from "path";
3771
3853
 
3772
3854
  // src/templateInjector.ts
3773
- import fs7 from "fs";
3774
- import path10 from "path";
3855
+ import fs8 from "fs";
3856
+ import path11 from "path";
3775
3857
  import { fileURLToPath, pathToFileURL as pathToFileURL2 } from "url";
3776
3858
  import * as ts9 from "typescript";
3777
- import { ROOT_DIR as ROOT_DIR5, getGeneratedSocketTypesPath as getGeneratedSocketTypesPath6, getSrcDir as getSrcDir4 } from "@luckystack/core";
3859
+ import { ROOT_DIR as ROOT_DIR7, getGeneratedSocketTypesPath as getGeneratedSocketTypesPath6, getSrcDir as getSrcDir4 } from "@luckystack/core";
3778
3860
  var __filename = fileURLToPath(import.meta.url);
3779
- var __dirname = path10.dirname(__filename);
3780
- var templatesDir = path10.join(__dirname, "templates");
3781
- var getConsumerTemplatesDir = () => path10.join(ROOT_DIR5, ".luckystack", "templates");
3861
+ var __dirname = path11.dirname(__filename);
3862
+ var templatesDir = path11.join(__dirname, "templates");
3863
+ var getConsumerTemplatesDir = () => path11.join(ROOT_DIR7, ".luckystack", "templates");
3782
3864
  var templateContentFilenames = (kind) => {
3783
3865
  const builtIn = BUILT_IN_TEMPLATE_FILENAMES[kind];
3784
3866
  if (builtIn) return [builtIn];
@@ -3787,10 +3869,10 @@ var templateContentFilenames = (kind) => {
3787
3869
  var resolveTemplateContent = (kind) => {
3788
3870
  const consumerDir = getConsumerTemplatesDir();
3789
3871
  for (const fileName of templateContentFilenames(kind)) {
3790
- const consumerFile = path10.join(consumerDir, fileName);
3791
- if (fs7.existsSync(consumerFile)) {
3872
+ const consumerFile = path11.join(consumerDir, fileName);
3873
+ if (fs8.existsSync(consumerFile)) {
3792
3874
  try {
3793
- return fs7.readFileSync(consumerFile, "utf8");
3875
+ return fs8.readFileSync(consumerFile, "utf8");
3794
3876
  } catch (error) {
3795
3877
  console.error(`[TemplateInjector] Could not read consumer template: ${consumerFile}`, error);
3796
3878
  }
@@ -3800,9 +3882,9 @@ var resolveTemplateContent = (kind) => {
3800
3882
  if (override !== null) return override;
3801
3883
  const builtInName = BUILT_IN_TEMPLATE_FILENAMES[kind];
3802
3884
  if (builtInName) {
3803
- const bundled = path10.join(templatesDir, builtInName);
3885
+ const bundled = path11.join(templatesDir, builtInName);
3804
3886
  try {
3805
- return fs7.readFileSync(bundled, "utf8");
3887
+ return fs8.readFileSync(bundled, "utf8");
3806
3888
  } catch (error) {
3807
3889
  console.error(`[TemplateInjector] Could not read bundled template: ${bundled}`, error);
3808
3890
  return null;
@@ -3816,11 +3898,11 @@ var ensureConsumerTemplateConfigLoaded = () => {
3816
3898
  consumerTemplateConfig ??= (async () => {
3817
3899
  const dir = getConsumerTemplatesDir();
3818
3900
  for (const fileName of ["templateRules.ts", "templateRules.mjs", "templateRules.js"]) {
3819
- const rulesFile = path10.join(dir, fileName);
3820
- if (!fs7.existsSync(rulesFile)) continue;
3821
- const resolvedRulesFile = path10.resolve(rulesFile);
3822
- const resolvedRoot = path10.resolve(ROOT_DIR5);
3823
- if (!resolvedRulesFile.startsWith(resolvedRoot + path10.sep) && resolvedRulesFile !== resolvedRoot) {
3901
+ const rulesFile = path11.join(dir, fileName);
3902
+ if (!fs8.existsSync(rulesFile)) continue;
3903
+ const resolvedRulesFile = path11.resolve(rulesFile);
3904
+ const resolvedRoot = path11.resolve(ROOT_DIR7);
3905
+ if (!resolvedRulesFile.startsWith(resolvedRoot + path11.sep) && resolvedRulesFile !== resolvedRoot) {
3824
3906
  console.warn(
3825
3907
  `[TemplateInjector] Template rules file resolves outside project root \u2014 skipping: ${rulesFile}`
3826
3908
  );
@@ -3838,7 +3920,7 @@ var ensureConsumerTemplateConfigLoaded = () => {
3838
3920
  };
3839
3921
  var isEmptyFile = (filePath) => {
3840
3922
  try {
3841
- const content = fs7.readFileSync(filePath, "utf8");
3923
+ const content = fs8.readFileSync(filePath, "utf8");
3842
3924
  return content.trim().length === 0;
3843
3925
  } catch {
3844
3926
  return false;
@@ -3846,7 +3928,7 @@ var isEmptyFile = (filePath) => {
3846
3928
  };
3847
3929
  var isCommentOnlyFile = (filePath) => {
3848
3930
  try {
3849
- const content = fs7.readFileSync(filePath, "utf8");
3931
+ const content = fs8.readFileSync(filePath, "utf8");
3850
3932
  const withoutBlockComments = content.replaceAll(/\/\*[\s\S]*?\*\//g, "");
3851
3933
  const withoutLineComments = withoutBlockComments.replaceAll(/(^|\s)\/\/.*$/gm, "$1");
3852
3934
  return withoutLineComments.trim().length === 0;
@@ -3880,7 +3962,7 @@ var isVersionedSyncFile = (filePath) => {
3880
3962
  return isVersionedSyncFileName(filePath);
3881
3963
  };
3882
3964
  var getFileName = (filePath) => {
3883
- return path10.basename(filePath.replaceAll("\\", "/"));
3965
+ return path11.basename(filePath.replaceAll("\\", "/"));
3884
3966
  };
3885
3967
  var stripTsExtension = (fileName) => {
3886
3968
  return fileName.replace(/\.ts$/, "");
@@ -3956,7 +4038,7 @@ var getInvalidVersionMessage = (filePath) => {
3956
4038
  var computeSrcRelativePath = (filePath) => {
3957
4039
  try {
3958
4040
  const srcDir = getSrcDir4();
3959
- const absolute = path10.resolve(filePath);
4041
+ const absolute = path11.resolve(filePath);
3960
4042
  const normalizedSrc = srcDir.replaceAll("\\", "/");
3961
4043
  const normalizedAbs = absolute.replaceAll("\\", "/");
3962
4044
  if (!normalizedAbs.startsWith(`${normalizedSrc}/`)) return null;
@@ -4004,7 +4086,7 @@ var getPairedSyncFile = (filePath) => {
4004
4086
  var hasPairedFile = (filePath) => {
4005
4087
  const pairedPath = getPairedSyncFile(filePath);
4006
4088
  if (!pairedPath) return false;
4007
- return fs7.existsSync(pairedPath);
4089
+ return fs8.existsSync(pairedPath);
4008
4090
  };
4009
4091
  var extractSyncPagePath2 = (filePath) => {
4010
4092
  const normalized = filePath.replaceAll("\\", "/");
@@ -4017,14 +4099,14 @@ var extractSyncName2 = (filePath) => {
4017
4099
  const normalized = filePath.replaceAll("\\", "/");
4018
4100
  const match = /_sync\/(.+)\.ts$/.exec(normalized);
4019
4101
  if (!match) {
4020
- const basename = path10.basename(filePath, ".ts");
4102
+ const basename = path11.basename(filePath, ".ts");
4021
4103
  return basename.replace(/_server_v\d+$/, "").replace(/_client_v\d+$/, "");
4022
4104
  }
4023
4105
  return (match[1] ?? "").replace(/_server_v\d+$/, "").replace(/_client_v\d+$/, "");
4024
4106
  };
4025
4107
  var extractClientInputFromFile = (filePath) => {
4026
4108
  try {
4027
- const content = fs7.readFileSync(filePath, "utf8");
4109
+ const content = fs8.readFileSync(filePath, "utf8");
4028
4110
  const syncParamsMatch = /interface\s+SyncParams\s*\{/.exec(content);
4029
4111
  if (!syncParamsMatch) return null;
4030
4112
  const searchRegion = content.slice(syncParamsMatch.index + syncParamsMatch[0].length);
@@ -4055,7 +4137,7 @@ var extractClientInputFromFile = (filePath) => {
4055
4137
  var extractClientInputFromGeneratedTypes = (pagePath, syncName) => {
4056
4138
  try {
4057
4139
  const generatedTypesPath = getGeneratedSocketTypesPath6();
4058
- const content = fs7.readFileSync(generatedTypesPath, "utf8");
4140
+ const content = fs8.readFileSync(generatedTypesPath, "utf8");
4059
4141
  const escapeRegex = (value) => value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
4060
4142
  const escapedPagePath = escapeRegex(pagePath);
4061
4143
  const escapedSyncName = escapeRegex(syncName);
@@ -4101,7 +4183,7 @@ var calculateRelativePath = (filePath) => {
4101
4183
  const srcRelative = computeSrcRelativePath(filePath);
4102
4184
  if (srcRelative !== null) {
4103
4185
  try {
4104
- const srcDepthFromRoot = path10.relative(ROOT_DIR5, getSrcDir4()).replaceAll("\\", "/").split("/").filter((segment) => segment.length > 0).length;
4186
+ const srcDepthFromRoot = path11.relative(ROOT_DIR7, getSrcDir4()).replaceAll("\\", "/").split("/").filter((segment) => segment.length > 0).length;
4105
4187
  const dirSegmentsAfterSrc = Math.max(
4106
4188
  0,
4107
4189
  srcRelative.split("/").filter((segment) => segment.length > 0).length - 1
@@ -4180,7 +4262,7 @@ var getTemplate = (filePath) => {
4180
4262
  var injectTemplate = async (filePath) => {
4181
4263
  await ensureConsumerTemplateConfigLoaded();
4182
4264
  if (getRouteFilenameValidationMessage(filePath)) {
4183
- fs7.writeFileSync(filePath, getInvalidVersionMessage(filePath), "utf8");
4265
+ fs8.writeFileSync(filePath, getInvalidVersionMessage(filePath), "utf8");
4184
4266
  console.log(`[TemplateInjector] Invalid route filename, injected guidance: ${filePath}`);
4185
4267
  return true;
4186
4268
  }
@@ -4189,7 +4271,7 @@ var injectTemplate = async (filePath) => {
4189
4271
  return false;
4190
4272
  }
4191
4273
  try {
4192
- fs7.writeFileSync(filePath, template, "utf8");
4274
+ fs8.writeFileSync(filePath, template, "utf8");
4193
4275
  console.log(`[TemplateInjector] Injected template into: ${filePath}`);
4194
4276
  return true;
4195
4277
  } catch (error) {
@@ -4317,10 +4399,10 @@ var updateClientFileForPairedServer = async (clientFilePath) => {
4317
4399
  try {
4318
4400
  const pagePath = extractSyncPagePath2(clientFilePath);
4319
4401
  const syncName = extractSyncName2(clientFilePath);
4320
- let content = fs7.readFileSync(clientFilePath, "utf8");
4402
+ let content = fs8.readFileSync(clientFilePath, "utf8");
4321
4403
  content = ensureImportedTypes(content, clientFilePath);
4322
4404
  content = insertTypeAliases(content, pagePath, syncName);
4323
- const astResult = rewriteClientInputTypeAst(content, path10.basename(clientFilePath));
4405
+ const astResult = rewriteClientInputTypeAst(content, path11.basename(clientFilePath));
4324
4406
  if (astResult === null) {
4325
4407
  console.log(
4326
4408
  `[TemplateInjector] AST clientInput rewrite unavailable for ${clientFilePath} \u2014 using regex fallback`,
@@ -4332,7 +4414,7 @@ var updateClientFileForPairedServer = async (clientFilePath) => {
4332
4414
  }
4333
4415
  content = addServerOutputToParams(content);
4334
4416
  content = addServerOutputToDestructuring(content);
4335
- fs7.writeFileSync(clientFilePath, content, "utf8");
4417
+ fs8.writeFileSync(clientFilePath, content, "utf8");
4336
4418
  console.log(`[TemplateInjector] Updated client file to use paired types (preserved code): ${clientFilePath}`);
4337
4419
  return true;
4338
4420
  } catch (error) {
@@ -4373,7 +4455,7 @@ var splitAtMainBody = (content) => {
4373
4455
  };
4374
4456
  var updateClientFileForDeletedServer = async (clientFilePath, clientInputTypes) => {
4375
4457
  try {
4376
- let content = fs7.readFileSync(clientFilePath, "utf8");
4458
+ let content = fs8.readFileSync(clientFilePath, "utf8");
4377
4459
  content = inlineClientInputType(content, clientInputTypes);
4378
4460
  const [header, body] = splitAtMainBody(content);
4379
4461
  const cleanedHeader = removeServerOutputFromDestructuring(removeServerOutputFromParams(header));
@@ -4381,7 +4463,7 @@ var updateClientFileForDeletedServer = async (clientFilePath, clientInputTypes)
4381
4463
  content = removeGeneratedTypeImports(content);
4382
4464
  content = removeTypeAliases(content);
4383
4465
  content = content.replaceAll(/\n{3,}/g, "\n\n");
4384
- fs7.writeFileSync(clientFilePath, content, "utf8");
4466
+ fs8.writeFileSync(clientFilePath, content, "utf8");
4385
4467
  console.log(`[TemplateInjector] Updated client file for deleted server (preserved code): ${clientFilePath}`);
4386
4468
  return true;
4387
4469
  } catch (error) {
@@ -4407,7 +4489,7 @@ var injectServerTemplateWithClientInput = async (serverFilePath, clientInputType
4407
4489
  /clientInput:\s*\{[^}]*\}/s,
4408
4490
  (_match) => `clientInput: ${clientInputTypes}`
4409
4491
  );
4410
- fs7.writeFileSync(serverFilePath, content, "utf8");
4492
+ fs8.writeFileSync(serverFilePath, content, "utf8");
4411
4493
  console.log(`[TemplateInjector] Injected server template with clientInput: ${serverFilePath}`);
4412
4494
  return true;
4413
4495
  } catch (error) {
@@ -4417,24 +4499,24 @@ var injectServerTemplateWithClientInput = async (serverFilePath, clientInputType
4417
4499
  };
4418
4500
 
4419
4501
  // src/importDependencyGraph.ts
4420
- import fs8 from "fs";
4421
- import path11 from "path";
4422
- import { ROOT_DIR as ROOT_DIR6, getServerFunctionDirs as getServerFunctionDirs3, getSharedDir, getSrcDir as getSrcDir5 } from "@luckystack/core";
4502
+ import fs9 from "fs";
4503
+ import path12 from "path";
4504
+ import { ROOT_DIR as ROOT_DIR8, getServerFunctionDirs as getServerFunctionDirs2, getSharedDir, getSrcDir as getSrcDir5 } from "@luckystack/core";
4423
4505
  var SUPPORTED_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"];
4424
- var normalizePath3 = (value) => path11.resolve(value).replaceAll("\\", "/");
4506
+ var normalizePath4 = (value) => path12.resolve(value).replaceAll("\\", "/");
4425
4507
  var isSupportedScriptFile = (value) => {
4426
4508
  return SUPPORTED_EXTENSIONS.some((extension) => value.endsWith(extension));
4427
4509
  };
4428
4510
  var existsFile = (value) => {
4429
4511
  try {
4430
- return fs8.existsSync(value) && fs8.statSync(value).isFile();
4512
+ return fs9.existsSync(value) && fs9.statSync(value).isFile();
4431
4513
  } catch {
4432
4514
  return false;
4433
4515
  }
4434
4516
  };
4435
4517
  var existsDirectory = (value) => {
4436
4518
  try {
4437
- return fs8.existsSync(value) && fs8.statSync(value).isDirectory();
4519
+ return fs9.existsSync(value) && fs9.statSync(value).isDirectory();
4438
4520
  } catch {
4439
4521
  return false;
4440
4522
  }
@@ -4445,23 +4527,23 @@ var collectScriptFiles = (dir, output, visited = /* @__PURE__ */ new Set()) => {
4445
4527
  }
4446
4528
  let realDir;
4447
4529
  try {
4448
- realDir = fs8.realpathSync(dir);
4530
+ realDir = fs9.realpathSync(dir);
4449
4531
  } catch {
4450
4532
  return;
4451
4533
  }
4452
4534
  if (visited.has(realDir)) return;
4453
4535
  visited.add(realDir);
4454
- const entries = fs8.readdirSync(dir, { withFileTypes: true });
4536
+ const entries = fs9.readdirSync(dir, { withFileTypes: true });
4455
4537
  for (const entry of entries) {
4456
4538
  if (entry.name === "node_modules") continue;
4457
- const fullPath = path11.join(dir, entry.name);
4539
+ const fullPath = path12.join(dir, entry.name);
4458
4540
  if (entry.isDirectory() || entry.isSymbolicLink()) {
4459
4541
  if (existsDirectory(fullPath)) {
4460
4542
  collectScriptFiles(fullPath, output, visited);
4461
4543
  }
4462
4544
  continue;
4463
4545
  }
4464
- const normalizedPath = normalizePath3(fullPath);
4546
+ const normalizedPath = normalizePath4(fullPath);
4465
4547
  if (isSupportedScriptFile(normalizedPath)) {
4466
4548
  output.add(normalizedPath);
4467
4549
  }
@@ -4473,7 +4555,7 @@ var SCOPED_FILES_TTL_MS = 1e3;
4473
4555
  var extractImportSpecifiers = (filePath) => {
4474
4556
  let mtimeMs = 0;
4475
4557
  try {
4476
- mtimeMs = fs8.statSync(filePath).mtimeMs;
4558
+ mtimeMs = fs9.statSync(filePath).mtimeMs;
4477
4559
  } catch {
4478
4560
  specifiersCache.delete(filePath);
4479
4561
  return [];
@@ -4483,7 +4565,7 @@ var extractImportSpecifiers = (filePath) => {
4483
4565
  return cached.specifiers;
4484
4566
  }
4485
4567
  try {
4486
- const source = fs8.readFileSync(filePath, "utf8");
4568
+ const source = fs9.readFileSync(filePath, "utf8");
4487
4569
  const specifiers = /* @__PURE__ */ new Set();
4488
4570
  const importExportRegex = /(?:import|export)\s+(?:[\s\S]*?\s+from\s+)?['"]([^'"]+)['"]/g;
4489
4571
  const dynamicImportRegex = /import\(\s*['"]([^'"]+)['"]\s*\)/g;
@@ -4503,24 +4585,24 @@ var extractImportSpecifiers = (filePath) => {
4503
4585
  }
4504
4586
  };
4505
4587
  var invalidateGraphForFile = (absolutePath) => {
4506
- specifiersCache.delete(normalizePath3(absolutePath));
4588
+ specifiersCache.delete(normalizePath4(absolutePath));
4507
4589
  scopedFilesCache = null;
4508
4590
  };
4509
4591
  var tryResolveWithExtensions = (basePath) => {
4510
4592
  if (existsFile(basePath)) {
4511
- return normalizePath3(basePath);
4593
+ return normalizePath4(basePath);
4512
4594
  }
4513
4595
  for (const extension of SUPPORTED_EXTENSIONS) {
4514
4596
  const withExtension = `${basePath}${extension}`;
4515
4597
  if (existsFile(withExtension)) {
4516
- return normalizePath3(withExtension);
4598
+ return normalizePath4(withExtension);
4517
4599
  }
4518
4600
  }
4519
4601
  if (existsDirectory(basePath)) {
4520
4602
  for (const extension of SUPPORTED_EXTENSIONS) {
4521
- const indexFile = path11.join(basePath, `index${extension}`);
4603
+ const indexFile = path12.join(basePath, `index${extension}`);
4522
4604
  if (existsFile(indexFile)) {
4523
- return normalizePath3(indexFile);
4605
+ return normalizePath4(indexFile);
4524
4606
  }
4525
4607
  }
4526
4608
  }
@@ -4531,18 +4613,18 @@ var resolveImportToFile = (importerPath, specifier) => {
4531
4613
  return null;
4532
4614
  }
4533
4615
  if (specifier === "config") {
4534
- return tryResolveWithExtensions(path11.join(ROOT_DIR6, "config"));
4616
+ return tryResolveWithExtensions(path12.join(ROOT_DIR8, "config"));
4535
4617
  }
4536
4618
  if (specifier.startsWith("./") || specifier.startsWith("../")) {
4537
- const relativeBase = path11.resolve(path11.dirname(importerPath), specifier);
4619
+ const relativeBase = path12.resolve(path12.dirname(importerPath), specifier);
4538
4620
  return tryResolveWithExtensions(relativeBase);
4539
4621
  }
4540
4622
  if (specifier.startsWith("src/") || specifier.startsWith("@/")) {
4541
4623
  const sourcePath = specifier.startsWith("@/") ? specifier.slice(2) : specifier.slice(4);
4542
- return tryResolveWithExtensions(path11.join(getSrcDir5(), sourcePath));
4624
+ return tryResolveWithExtensions(path12.join(getSrcDir5(), sourcePath));
4543
4625
  }
4544
4626
  if (specifier.startsWith("shared/")) {
4545
- return tryResolveWithExtensions(path11.join(getSharedDir(), specifier.slice(7)));
4627
+ return tryResolveWithExtensions(path12.join(getSharedDir(), specifier.slice(7)));
4546
4628
  }
4547
4629
  return null;
4548
4630
  };
@@ -4557,10 +4639,10 @@ var collectScopedFiles = () => {
4557
4639
  const files = /* @__PURE__ */ new Set();
4558
4640
  collectScriptFiles(getSrcDir5(), files);
4559
4641
  collectScriptFiles(getSharedDir(), files);
4560
- for (const dir of getServerFunctionDirs3()) {
4642
+ for (const dir of getServerFunctionDirs2()) {
4561
4643
  collectScriptFiles(dir, files);
4562
4644
  }
4563
- const configFile = tryResolveWithExtensions(path11.join(ROOT_DIR6, "config"));
4645
+ const configFile = tryResolveWithExtensions(path12.join(ROOT_DIR8, "config"));
4564
4646
  if (configFile) {
4565
4647
  files.add(configFile);
4566
4648
  }
@@ -4569,7 +4651,7 @@ var collectScopedFiles = () => {
4569
4651
  };
4570
4652
  var findDependentRouteFiles = (changedFilePath) => {
4571
4653
  const scopedFiles = collectScopedFiles();
4572
- const changedAbsolutePath = normalizePath3(changedFilePath);
4654
+ const changedAbsolutePath = normalizePath4(changedFilePath);
4573
4655
  const reverseDependencies = /* @__PURE__ */ new Map();
4574
4656
  for (const filePath of scopedFiles) {
4575
4657
  const specifiers = extractImportSpecifiers(filePath);
@@ -4608,27 +4690,27 @@ var findDependentRouteFiles = (changedFilePath) => {
4608
4690
  };
4609
4691
 
4610
4692
  // src/prismaClientCheck.ts
4611
- import fs9 from "fs";
4612
- import path12 from "path";
4693
+ import fs10 from "fs";
4694
+ import path13 from "path";
4613
4695
  import { spawn } from "child_process";
4614
- import { ROOT_DIR as ROOT_DIR7, tryCatch as tryCatch2 } from "@luckystack/core";
4615
- var GENERATED_CLIENT_DIR = path12.join(ROOT_DIR7, "node_modules", ".prisma", "client");
4616
- var GENERATED_CLIENT_MARKER = path12.join(GENERATED_CLIENT_DIR, "schema.prisma");
4617
- var DEFAULT_SCHEMA_PATH = path12.join(ROOT_DIR7, "prisma", "schema.prisma");
4618
- var isPrismaClientMissing = () => fs9.existsSync(DEFAULT_SCHEMA_PATH) && !fs9.existsSync(GENERATED_CLIENT_MARKER);
4696
+ import { ROOT_DIR as ROOT_DIR9, tryCatch as tryCatch2 } from "@luckystack/core";
4697
+ var GENERATED_CLIENT_DIR = path13.join(ROOT_DIR9, "node_modules", ".prisma", "client");
4698
+ var GENERATED_CLIENT_MARKER = path13.join(GENERATED_CLIENT_DIR, "schema.prisma");
4699
+ var DEFAULT_SCHEMA_PATH = path13.join(ROOT_DIR9, "prisma", "schema.prisma");
4700
+ var isPrismaClientMissing = () => fs10.existsSync(DEFAULT_SCHEMA_PATH) && !fs10.existsSync(GENERATED_CLIENT_MARKER);
4619
4701
  var runPrismaGenerate = async () => tryCatch2(
4620
4702
  () => new Promise((resolve, reject) => {
4621
4703
  const localBinName = process.platform === "win32" ? "prisma.cmd" : "prisma";
4622
- const localBin = path12.join(ROOT_DIR7, "node_modules", ".bin", localBinName);
4623
- const useLocal = fs9.existsSync(localBin);
4704
+ const localBin = path13.join(ROOT_DIR9, "node_modules", ".bin", localBinName);
4705
+ const useLocal = fs10.existsSync(localBin);
4624
4706
  const [cmd, args] = useLocal ? [localBin, ["generate"]] : [process.platform === "win32" ? "npx.cmd" : "npx", ["prisma", "generate"]];
4625
4707
  const isWinShim = process.platform === "win32" && /\.(cmd|bat)$/i.test(cmd);
4626
4708
  const child = isWinShim ? spawn(
4627
4709
  process.env.ComSpec ?? "cmd.exe",
4628
4710
  ["/d", "/s", "/c", `""${cmd}" ${args.join(" ")}"`],
4629
- { cwd: ROOT_DIR7, stdio: "inherit", windowsVerbatimArguments: true }
4711
+ { cwd: ROOT_DIR9, stdio: "inherit", windowsVerbatimArguments: true }
4630
4712
  ) : spawn(cmd, args, {
4631
- cwd: ROOT_DIR7,
4713
+ cwd: ROOT_DIR9,
4632
4714
  stdio: "inherit",
4633
4715
  shell: false
4634
4716
  });
@@ -4641,7 +4723,7 @@ var runPrismaGenerate = async () => tryCatch2(
4641
4723
 
4642
4724
  // src/hotReload.ts
4643
4725
  import { tryCatch as tryCatch3, getProjectConfig, getLocaleReloader, isProductionRuntime } from "@luckystack/core";
4644
- var normalizeFsPath = (value) => path13.resolve(value).replaceAll("\\", "/");
4726
+ var normalizeFsPath = (value) => path14.resolve(value).replaceAll("\\", "/");
4645
4727
  var isGeneratedPath = (normalizedPath) => {
4646
4728
  return normalizedPath.includes("apiTypes.generated.ts") || normalizedPath.includes("apiDocs.generated.json");
4647
4729
  };
@@ -4849,7 +4931,7 @@ var setupWatchers = () => {
4849
4931
  if (shouldInjectTemplate(filePath, { isNewFile: true })) {
4850
4932
  if (isSyncServerFile(normalizedPath)) {
4851
4933
  const clientPath = getPairedSyncFile(normalizedPath);
4852
- if (clientPath && fs10.existsSync(clientPath) && !isEmptyFile(clientPath)) {
4934
+ if (clientPath && fs11.existsSync(clientPath) && !isEmptyFile(clientPath)) {
4853
4935
  const clientInputTypes = extractClientInputFromFile(clientPath);
4854
4936
  if (clientInputTypes) {
4855
4937
  await injectServerTemplateWithClientInput(filePath, clientInputTypes);
@@ -5012,7 +5094,7 @@ var setupWatchers = () => {
5012
5094
  scheduleReload("sync", async () => {
5013
5095
  if (isSyncServerFile(normalizedPath)) {
5014
5096
  const clientPath = getPairedSyncFile(normalizedPath);
5015
- if (clientPath && fs10.existsSync(clientPath)) {
5097
+ if (clientPath && fs11.existsSync(clientPath)) {
5016
5098
  const pagePath = extractSyncPagePath2(normalizedPath);
5017
5099
  const syncName = extractSyncName2(normalizedPath);
5018
5100
  const clientInputTypes = extractClientInputFromGeneratedTypes(pagePath, syncName);
@@ -5075,9 +5157,14 @@ export {
5075
5157
  clearRuntimeTypeResolverCache,
5076
5158
  clearTemplateOverrides,
5077
5159
  clearTemplateRules,
5160
+ collectFunctionModules,
5078
5161
  devApis,
5079
5162
  devFunctions,
5080
5163
  devSyncs,
5164
+ formatFunctionKeyConflict,
5165
+ formatFunctionKeyPath,
5166
+ formatFunctionNamespaceConflict,
5167
+ functionModuleFileName,
5081
5168
  generateTypeMapFile,
5082
5169
  getInputTypeFromFile,
5083
5170
  getRegisteredTemplate,
@@ -5100,6 +5187,7 @@ export {
5100
5187
  registerTemplateRule,
5101
5188
  removeApiFromFile,
5102
5189
  removeSyncFromFile,
5190
+ renderFunctionsMap,
5103
5191
  resolveRuntimeTypeText,
5104
5192
  resolveTemplateKind,
5105
5193
  setupWatchers,