@luckystack/devkit 0.6.6 → 0.7.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/validateDeploy.ts"],"sourcesContent":["#!/usr/bin/env node\r\n//? `luckystack-validate-deploy` CLI. Loads the consumer's compiled\r\n//? services.config.js and deploy.config.js as side-effects (they call\r\n//? `registerServicesConfig` / `registerDeployConfig` on import), then runs\r\n//? `validateDeploy()` and prints findings.\r\n//?\r\n//? Designed to be a pre-deploy gate: exit 1 on any error finding, 0\r\n//? otherwise. Warnings print to stderr but never fail the run.\r\n\r\nimport { pathToFileURL } from 'node:url';\r\nimport path from 'node:path';\r\nimport {\r\n getDeployConfig,\r\n getServicesConfig,\r\n isDeployConfigRegistered,\r\n isServicesConfigRegistered,\r\n} from '@luckystack/core';\r\n\r\nimport { validateDeploy, type ValidationFinding } from '../validateDeploy';\r\n\r\ninterface CliArgs {\r\n deploy: string | null;\r\n services: string | null;\r\n failOnWarning: boolean;\r\n}\r\n\r\nconst parseArgs = (argv: readonly string[]): CliArgs => {\r\n const args: CliArgs = {\r\n deploy: null,\r\n services: null,\r\n failOnWarning: false,\r\n };\r\n\r\n for (let i = 0; i < argv.length; i++) {\r\n const flag = argv[i];\r\n const next: string | undefined = i + 1 < argv.length ? argv[i + 1] : undefined;\r\n switch (flag) {\r\n case '--deploy':\r\n case '-d': {\r\n args.deploy = next ?? null;\r\n i++;\r\n break;\r\n }\r\n case '--services':\r\n case '-s': {\r\n args.services = next ?? null;\r\n i++;\r\n break;\r\n }\r\n case '--strict': {\r\n args.failOnWarning = true;\r\n break;\r\n }\r\n case '--help':\r\n case '-h': {\r\n printHelp();\r\n process.exit(0);\r\n break;\r\n }\r\n default: {\r\n break;\r\n }\r\n }\r\n }\r\n\r\n return args;\r\n};\r\n\r\nconst printHelp = (): void => {\r\n process.stdout.write(`\r\nluckystack-validate-deploy — pre-deploy validator for services + deploy configs\r\n\r\nUSAGE\r\n luckystack-validate-deploy --deploy <file> --services <file> [options]\r\n\r\nREQUIRED\r\n --deploy, -d <file> Path to compiled deploy.config.js (registers DeployConfig).\r\n --services, -s <file> Path to compiled services.config.js (registers ServicesConfig).\r\n\r\nOPTIONS\r\n --strict Exit 1 on warnings as well as errors.\r\n --help, -h Show this help.\r\n\r\nWHAT IT CHECKS\r\n - Every service is assigned to exactly one preset.\r\n - Every preset references services that exist.\r\n - Every environment binding's service matches an actual service.\r\n - Every environment's redis/mongo resource keys exist.\r\n - Fallback envs must share the same redis/mongo resource keys.\r\n - synchronizedEnvKeys and urlEnvKey values resolve at config time (warning only).\r\n - Services bound in no environment (warning only — valid mid-rollout).\r\n\r\nEXAMPLES\r\n luckystack-validate-deploy --deploy dist/deploy.config.js --services dist/services.config.js\r\n npx tsx packages/devkit/dist/cli/validateDeploy.js --deploy ./deploy.config.ts --services ./services.config.ts\r\n`);\r\n};\r\n\r\nconst ALLOWED_CONFIG_EXTENSIONS = new Set(['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts']);\r\n\r\nconst importConfig = async (file: string, label: string): Promise<void> => {\r\n const abs = path.isAbsolute(file) ? file : path.join(process.cwd(), file);\r\n\r\n //? Guard: resolved path must stay inside cwd (or the resolved project root)\r\n //? to prevent `--deploy ../../../../etc/shadow` style path injection.\r\n const root = path.resolve(process.cwd());\r\n const resolved = path.resolve(abs);\r\n if (!resolved.startsWith(root + path.sep) && resolved !== root) {\r\n throw new Error(\r\n `[luckystack-validate-deploy] ${label} path \"${file}\" resolves outside the project root — aborting`,\r\n );\r\n }\r\n\r\n //? Guard: only load known JS/TS module extensions.\r\n const ext = path.extname(resolved).toLowerCase();\r\n if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) {\r\n throw new Error(\r\n `[luckystack-validate-deploy] ${label} path \"${file}\" has disallowed extension \"${ext}\" — expected one of ${[...ALLOWED_CONFIG_EXTENSIONS].join(', ')}`,\r\n );\r\n }\r\n\r\n try {\r\n await import(pathToFileURL(resolved).href);\r\n } catch (error) {\r\n const message = error instanceof Error ? error.message : String(error);\r\n throw new Error(`[luckystack-validate-deploy] failed to import ${label} at ${abs}: ${message}`);\r\n }\r\n};\r\n\r\nconst COLORS = process.stdout.isTTY\r\n ? {\r\n reset: '\u001b[0m',\r\n bold: '\u001b[1m',\r\n dim: '\u001b[2m',\r\n red: '\u001b[31m',\r\n green: '\u001b[32m',\r\n yellow: '\u001b[33m',\r\n cyan: '\u001b[36m',\r\n }\r\n : { reset: '', bold: '', dim: '', red: '', green: '', yellow: '', cyan: '' };\r\n\r\nconst printFinding = (finding: ValidationFinding): void => {\r\n const tag = finding.severity === 'error'\r\n ? `${COLORS.red}${COLORS.bold}ERROR${COLORS.reset}`\r\n : `${COLORS.yellow}${COLORS.bold}WARN ${COLORS.reset}`;\r\n const code = `${COLORS.dim}[${finding.code}]${COLORS.reset}`;\r\n const location = finding.location ? `\\n ${COLORS.dim}at ${finding.location}${COLORS.reset}` : '';\r\n const sink = finding.severity === 'error' ? process.stderr : process.stdout;\r\n sink.write(`${tag} ${code} ${finding.message}${location}\\n`);\r\n};\r\n\r\nconst main = async (): Promise<void> => {\r\n const args = parseArgs(process.argv.slice(2));\r\n\r\n if (!args.deploy || !args.services) {\r\n process.stderr.write('[luckystack-validate-deploy] --deploy and --services are required. Run with --help for usage.\\n');\r\n process.exit(2);\r\n }\r\n\r\n await importConfig(args.deploy, 'deploy config');\r\n await importConfig(args.services, 'services config');\r\n\r\n if (!isDeployConfigRegistered()) {\r\n process.stderr.write('[luckystack-validate-deploy] deploy config file did not call registerDeployConfig — nothing to validate.\\n');\r\n process.exit(2);\r\n }\r\n if (!isServicesConfigRegistered()) {\r\n process.stderr.write('[luckystack-validate-deploy] services config file did not call registerServicesConfig — nothing to validate.\\n');\r\n process.exit(2);\r\n }\r\n\r\n const result = validateDeploy({\r\n services: getServicesConfig(),\r\n deploy: getDeployConfig(),\r\n });\r\n\r\n for (const finding of result.findings) {\r\n printFinding(finding);\r\n }\r\n\r\n const summary = result.ok\r\n ? `${COLORS.green}${COLORS.bold}OK${COLORS.reset}`\r\n : `${COLORS.red}${COLORS.bold}FAILED${COLORS.reset}`;\r\n process.stdout.write(\r\n `\\n${summary} — ${String(result.errorCount)} error(s), ${String(result.warningCount)} warning(s)\\n`,\r\n );\r\n\r\n if (result.errorCount > 0) {\r\n process.exit(1);\r\n }\r\n if (args.failOnWarning && result.warningCount > 0) {\r\n process.exit(1);\r\n }\r\n process.exit(0);\r\n};\r\n\r\nmain().catch((error: unknown) => {\r\n const message = error instanceof Error ? error.stack ?? error.message : String(error);\r\n process.stderr.write(`[luckystack-validate-deploy] fatal: ${message}\\n`);\r\n process.exit(1);\r\n});\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/cli/validateDeploy.ts"],"sourcesContent":["#!/usr/bin/env node\n//? `luckystack-validate-deploy` CLI. Loads the consumer's compiled\n//? services.config.js and deploy.config.js as side-effects (they call\n//? `registerServicesConfig` / `registerDeployConfig` on import), then runs\n//? `validateDeploy()` and prints findings.\n//?\n//? Designed to be a pre-deploy gate: exit 1 on any error finding, 0\n//? otherwise. Warnings print to stderr but never fail the run.\n\nimport { pathToFileURL } from 'node:url';\nimport path from 'node:path';\nimport {\n getDeployConfig,\n getServicesConfig,\n isDeployConfigRegistered,\n isServicesConfigRegistered,\n} from '@luckystack/core';\n\nimport { validateDeploy, type ValidationFinding } from '../validateDeploy';\n\ninterface CliArgs {\n deploy: string | null;\n services: string | null;\n failOnWarning: boolean;\n}\n\nconst parseArgs = (argv: readonly string[]): CliArgs => {\n const args: CliArgs = {\n deploy: null,\n services: null,\n failOnWarning: false,\n };\n\n for (let i = 0; i < argv.length; i++) {\n const flag = argv[i];\n const next: string | undefined = i + 1 < argv.length ? argv[i + 1] : undefined;\n switch (flag) {\n case '--deploy':\n case '-d': {\n args.deploy = next ?? null;\n i++;\n break;\n }\n case '--services':\n case '-s': {\n args.services = next ?? null;\n i++;\n break;\n }\n case '--strict': {\n args.failOnWarning = true;\n break;\n }\n case '--help':\n case '-h': {\n printHelp();\n process.exit(0);\n break;\n }\n default: {\n break;\n }\n }\n }\n\n return args;\n};\n\nconst printHelp = (): void => {\n process.stdout.write(`\nluckystack-validate-deploy — pre-deploy validator for services + deploy configs\n\nUSAGE\n luckystack-validate-deploy --deploy <file> --services <file> [options]\n\nREQUIRED\n --deploy, -d <file> Path to compiled deploy.config.js (registers DeployConfig).\n --services, -s <file> Path to compiled services.config.js (registers ServicesConfig).\n\nOPTIONS\n --strict Exit 1 on warnings as well as errors.\n --help, -h Show this help.\n\nWHAT IT CHECKS\n - Every service is assigned to exactly one preset.\n - Every preset references services that exist.\n - Every environment binding's service matches an actual service.\n - Every environment's redis/mongo resource keys exist.\n - Fallback envs must share the same redis/mongo resource keys.\n - synchronizedEnvKeys and urlEnvKey values resolve at config time (warning only).\n - Services bound in no environment (warning only — valid mid-rollout).\n\nEXAMPLES\n luckystack-validate-deploy --deploy dist/deploy.config.js --services dist/services.config.js\n npx tsx packages/devkit/dist/cli/validateDeploy.js --deploy ./deploy.config.ts --services ./services.config.ts\n`);\n};\n\nconst ALLOWED_CONFIG_EXTENSIONS = new Set(['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts']);\n\nconst importConfig = async (file: string, label: string): Promise<void> => {\n const abs = path.isAbsolute(file) ? file : path.join(process.cwd(), file);\n\n //? Guard: resolved path must stay inside cwd (or the resolved project root)\n //? to prevent `--deploy ../../../../etc/shadow` style path injection.\n const root = path.resolve(process.cwd());\n const resolved = path.resolve(abs);\n if (!resolved.startsWith(root + path.sep) && resolved !== root) {\n throw new Error(\n `[luckystack-validate-deploy] ${label} path \"${file}\" resolves outside the project root — aborting`,\n );\n }\n\n //? Guard: only load known JS/TS module extensions.\n const ext = path.extname(resolved).toLowerCase();\n if (!ALLOWED_CONFIG_EXTENSIONS.has(ext)) {\n throw new Error(\n `[luckystack-validate-deploy] ${label} path \"${file}\" has disallowed extension \"${ext}\" — expected one of ${[...ALLOWED_CONFIG_EXTENSIONS].join(', ')}`,\n );\n }\n\n try {\n await import(pathToFileURL(resolved).href);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(`[luckystack-validate-deploy] failed to import ${label} at ${abs}: ${message}`);\n }\n};\n\nconst COLORS = process.stdout.isTTY\n ? {\n reset: '\u001b[0m',\n bold: '\u001b[1m',\n dim: '\u001b[2m',\n red: '\u001b[31m',\n green: '\u001b[32m',\n yellow: '\u001b[33m',\n cyan: '\u001b[36m',\n }\n : { reset: '', bold: '', dim: '', red: '', green: '', yellow: '', cyan: '' };\n\nconst printFinding = (finding: ValidationFinding): void => {\n const tag = finding.severity === 'error'\n ? `${COLORS.red}${COLORS.bold}ERROR${COLORS.reset}`\n : `${COLORS.yellow}${COLORS.bold}WARN ${COLORS.reset}`;\n const code = `${COLORS.dim}[${finding.code}]${COLORS.reset}`;\n const location = finding.location ? `\\n ${COLORS.dim}at ${finding.location}${COLORS.reset}` : '';\n const sink = finding.severity === 'error' ? process.stderr : process.stdout;\n sink.write(`${tag} ${code} ${finding.message}${location}\\n`);\n};\n\nconst main = async (): Promise<void> => {\n const args = parseArgs(process.argv.slice(2));\n\n if (!args.deploy || !args.services) {\n process.stderr.write('[luckystack-validate-deploy] --deploy and --services are required. Run with --help for usage.\\n');\n process.exit(2);\n }\n\n await importConfig(args.deploy, 'deploy config');\n await importConfig(args.services, 'services config');\n\n if (!isDeployConfigRegistered()) {\n process.stderr.write('[luckystack-validate-deploy] deploy config file did not call registerDeployConfig — nothing to validate.\\n');\n process.exit(2);\n }\n if (!isServicesConfigRegistered()) {\n process.stderr.write('[luckystack-validate-deploy] services config file did not call registerServicesConfig — nothing to validate.\\n');\n process.exit(2);\n }\n\n const result = validateDeploy({\n services: getServicesConfig(),\n deploy: getDeployConfig(),\n });\n\n for (const finding of result.findings) {\n printFinding(finding);\n }\n\n const summary = result.ok\n ? `${COLORS.green}${COLORS.bold}OK${COLORS.reset}`\n : `${COLORS.red}${COLORS.bold}FAILED${COLORS.reset}`;\n process.stdout.write(\n `\\n${summary} — ${String(result.errorCount)} error(s), ${String(result.warningCount)} warning(s)\\n`,\n );\n\n if (result.errorCount > 0) {\n process.exit(1);\n }\n if (args.failOnWarning && result.warningCount > 0) {\n process.exit(1);\n }\n process.exit(0);\n};\n\nmain().catch((error: unknown) => {\n const message = error instanceof Error ? error.stack ?? error.message : String(error);\n process.stderr.write(`[luckystack-validate-deploy] fatal: ${message}\\n`);\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  validateDeploy
3
- } from "./chunk-EU2RFSLO.js";
3
+ } from "./chunk-BZTCJ7JY.js";
4
4
 
5
5
  // src/typeMap/discovery.ts
6
6
  import fs from "fs";
@@ -468,7 +468,7 @@ var convertTypeNode = (node) => {
468
468
  return args.length === 1 && arg0 ? `z.array(${convertTypeNode(arg0)})` : anyFallback("Array without element type");
469
469
  }
470
470
  case "Date": {
471
- return "z.date()";
471
+ return "z.iso.datetime()";
472
472
  }
473
473
  default: {
474
474
  return anyFallback(`unresolved TypeReference '${name}'`);
@@ -519,6 +519,41 @@ var typeTextToZodSource = (typeText) => {
519
519
  return convertTypeNode(statement.type);
520
520
  };
521
521
 
522
+ // src/typeMap/extractionDiagnostics.ts
523
+ import { tryCatchSync as tryCatchSync2 } from "@luckystack/core";
524
+ var failures = /* @__PURE__ */ new Map();
525
+ var keyOf = (filePath, kind, field) => `${filePath}|${kind}|${field}`;
526
+ var messageOf = (error) => {
527
+ if (error instanceof Error) return error.message;
528
+ if (typeof error === "string") return error;
529
+ const [, encoded] = tryCatchSync2(() => JSON.stringify(error));
530
+ return encoded ?? Object.prototype.toString.call(error);
531
+ };
532
+ var recordExtractionOutcome = ({
533
+ filePath,
534
+ kind,
535
+ field,
536
+ error
537
+ }) => {
538
+ const key = keyOf(filePath, kind, field);
539
+ if (error === null) {
540
+ failures.delete(key);
541
+ return;
542
+ }
543
+ failures.set(key, { filePath, kind, field, message: messageOf(error) });
544
+ };
545
+ var routeKeyOf = ({ filePath, kind }) => {
546
+ const [, route] = tryCatchSync2(() => kind === "api" ? `${extractPagePath(filePath)}/${extractApiName(filePath)}@${extractApiVersion(filePath)}` : `${extractSyncPagePath(filePath)}/${extractSyncName(filePath)}@${extractSyncVersion(filePath)}`);
547
+ return route;
548
+ };
549
+ var findExtractionFailure = (route, kind, field) => {
550
+ for (const failure of failures.values()) {
551
+ if (failure.kind !== kind || failure.field !== field) continue;
552
+ if (routeKeyOf(failure) === route) return failure.message;
553
+ }
554
+ return void 0;
555
+ };
556
+
522
557
  // src/typeMap/emitterArtifacts.ts
523
558
  var buildImportStatements = ({
524
559
  namedImports,
@@ -683,12 +718,17 @@ var validateGeneratedTypeIdentifiers = (content) => {
683
718
  };
684
719
  var collectFallbacks = (typesByPage, syncTypesByPage) => {
685
720
  const entries = [];
686
- const flagField = (route, kind, field, value) => {
721
+ const flagField = (route, kind, field, value, checkZod = false) => {
722
+ const extractionError = findExtractionFailure(route, kind, field);
723
+ if (extractionError !== void 0) {
724
+ entries.push({ route, kind, field, fallback: value, reason: "extraction-error", detail: extractionError });
725
+ return;
726
+ }
687
727
  if (value === "{ }" || value === "{ status: string }") {
688
728
  entries.push({ route, kind, field, fallback: value, reason: "default-fallback" });
689
729
  return;
690
730
  }
691
- const zodSrc = typeTextToZodSource(value);
731
+ const zodSrc = checkZod ? typeTextToZodSource(value) : null;
692
732
  if (zodSrc?.includes("z.any()")) {
693
733
  entries.push({ route, kind, field, fallback: value.slice(0, 80), reason: "zod-any-fallback" });
694
734
  }
@@ -697,8 +737,9 @@ var collectFallbacks = (typesByPage, syncTypesByPage) => {
697
737
  for (const [apiKey, entry] of apis) {
698
738
  const { name, version } = splitVersionedKey(apiKey);
699
739
  const route = `${pagePath}/${name}@${version}`;
700
- flagField(route, "api", "input", entry.input);
740
+ flagField(route, "api", "input", entry.input, true);
701
741
  flagField(route, "api", "output", entry.output);
742
+ flagField(route, "api", "stream", entry.stream);
702
743
  }
703
744
  }
704
745
  for (const [pagePath, syncs] of syncTypesByPage) {
@@ -708,6 +749,8 @@ var collectFallbacks = (typesByPage, syncTypesByPage) => {
708
749
  flagField(route, "sync", "clientInput", entry.clientInput);
709
750
  flagField(route, "sync", "serverOutput", entry.serverOutput);
710
751
  flagField(route, "sync", "clientOutput", entry.clientOutput);
752
+ flagField(route, "sync", "serverStream", entry.serverStream);
753
+ flagField(route, "sync", "clientStream", entry.clientStream);
711
754
  }
712
755
  }
713
756
  return entries;
@@ -1146,7 +1189,7 @@ var getServerProgram = () => {
1146
1189
  var invalidateProgramCache = () => {
1147
1190
  cachedProgram = null;
1148
1191
  };
1149
- var DEPTH_LIMIT = 12;
1192
+ var DEPTH_LIMIT = 14;
1150
1193
  var JSON_TYPE_NAMES = /* @__PURE__ */ new Set([
1151
1194
  "Json",
1152
1195
  "JsonValue",
@@ -1169,12 +1212,37 @@ var SKIP_EXPANSION = /* @__PURE__ */ new Set([
1169
1212
  "ArrayBuffer",
1170
1213
  "ReadonlyArray"
1171
1214
  ]);
1215
+ var TRANSPORT_DEPENDENT_BINARY_TYPES = /* @__PURE__ */ new Set([
1216
+ "Buffer",
1217
+ "ArrayBuffer",
1218
+ "SharedArrayBuffer",
1219
+ "DataView",
1220
+ "Blob",
1221
+ "File",
1222
+ "Int8Array",
1223
+ "Uint8Array",
1224
+ "Uint8ClampedArray",
1225
+ "Int16Array",
1226
+ "Uint16Array",
1227
+ "Int32Array",
1228
+ "Uint32Array",
1229
+ "Float32Array",
1230
+ "Float64Array",
1231
+ "BigInt64Array",
1232
+ "BigUint64Array"
1233
+ ]);
1234
+ var UnsupportedWireTypeError = class extends Error {
1235
+ constructor(typeName) {
1236
+ super(`[TypeMapGenerator] ${typeName} has transport-dependent or non-JSON output semantics. Return an explicit JSON DTO/base64 string, or use a transport-specific custom route.`);
1237
+ this.name = "UnsupportedWireTypeError";
1238
+ }
1239
+ };
1172
1240
  var isJsonLikeType = (type, checker) => {
1173
1241
  const symbolName = type.symbol?.name ?? "";
1174
1242
  const aliasName = type.aliasSymbol?.name ?? "";
1175
1243
  if (JSON_TYPE_NAMES.has(symbolName) || JSON_TYPE_NAMES.has(aliasName)) return true;
1176
- const rendered = checker.typeToString(type);
1177
- return /(\bPrisma\.)?(Input)?Json(Value|Object|Array)\b/.test(rendered);
1244
+ const rendered = checker.typeToString(type).trim();
1245
+ return /^(?:Prisma\.)?(?:Input)?Json(?:Value|Object|Array)$/.test(rendered);
1178
1246
  };
1179
1247
  var getLiteralTypeFromExpression = (expression, checker, depth) => {
1180
1248
  if (ts4.isParenthesizedExpression(expression)) {
@@ -1258,8 +1326,64 @@ var collectTypeSymbolFallback = (type) => {
1258
1326
  importPath: normalizeImportPath(sourceFile)
1259
1327
  }];
1260
1328
  };
1261
- var expandTypeDetailed = (type, checker, depth = 0, state) => {
1262
- const expandState = state ?? { stackTypeIds: /* @__PURE__ */ new Set() };
1329
+ var WIRE_PROJECTED_TYPES = /* @__PURE__ */ new Map([["Date", "string"]]);
1330
+ var resolveToJsonReturnType = (type, checker) => {
1331
+ const toJson = type.getProperty("toJSON");
1332
+ if (!toJson) return null;
1333
+ const declaration = toJson.valueDeclaration ?? toJson.declarations?.[0];
1334
+ if (!declaration) return null;
1335
+ const signature = checker.getTypeOfSymbolAtLocation(toJson, declaration).getCallSignatures()[0];
1336
+ return signature ? signature.getReturnType() : null;
1337
+ };
1338
+ var isFunctionValueType = (type) => type.getCallSignatures().length > 0;
1339
+ var typeNameOf = (type, checker) => {
1340
+ const objectType = type;
1341
+ if (type.flags & ts4.TypeFlags.Object && objectType.objectFlags & ts4.ObjectFlags.Reference) {
1342
+ const targetName = objectType.target.getSymbol()?.name;
1343
+ if (targetName) return targetName;
1344
+ }
1345
+ return type.getSymbol()?.name ?? checker.typeToString(type);
1346
+ };
1347
+ var assertSupportedWireType = (type, checker) => {
1348
+ const typeName = typeNameOf(type, checker);
1349
+ if (TRANSPORT_DEPENDENT_BINARY_TYPES.has(typeName)) {
1350
+ throw new UnsupportedWireTypeError(typeName);
1351
+ }
1352
+ if ((type.flags & (ts4.TypeFlags.BigInt | ts4.TypeFlags.BigIntLiteral)) !== 0) {
1353
+ throw new UnsupportedWireTypeError("bigint");
1354
+ }
1355
+ };
1356
+ var isJsonOmittedValueType = (type) => (type.flags & (ts4.TypeFlags.Undefined | ts4.TypeFlags.Void | ts4.TypeFlags.ESSymbol | ts4.TypeFlags.UniqueESSymbol)) !== 0 || isFunctionValueType(type);
1357
+ var resolveWireConstituents = (type, checker) => {
1358
+ const queue = type.isUnion() ? [...type.types] : [type];
1359
+ const serializable = [];
1360
+ let omitted = false;
1361
+ let changed = false;
1362
+ for (const candidate of queue) {
1363
+ assertSupportedWireType(candidate, checker);
1364
+ if (isJsonOmittedValueType(candidate)) {
1365
+ omitted = true;
1366
+ changed = true;
1367
+ continue;
1368
+ }
1369
+ const jsonType = resolveToJsonReturnType(candidate, checker);
1370
+ const effective = jsonType && jsonType !== candidate ? jsonType : candidate;
1371
+ if (effective !== candidate) changed = true;
1372
+ const effectiveTypes = effective.isUnion() ? effective.types : [effective];
1373
+ for (const effectiveType of effectiveTypes) {
1374
+ assertSupportedWireType(effectiveType, checker);
1375
+ if (isJsonOmittedValueType(effectiveType)) {
1376
+ omitted = true;
1377
+ changed = true;
1378
+ } else {
1379
+ serializable.push(effectiveType);
1380
+ }
1381
+ }
1382
+ }
1383
+ return changed ? { serializable, omitted } : { serializable: [type], omitted: false };
1384
+ };
1385
+ var expandTypeDetailed = (type, checker, depth = 0, state, options) => {
1386
+ const expandState = state ?? { stackTypeIds: /* @__PURE__ */ new Set(), wireProjection: options?.wireProjection ?? false };
1263
1387
  const typeId = type.id;
1264
1388
  if (typeId !== void 0) {
1265
1389
  if (expandState.stackTypeIds.has(typeId)) {
@@ -1271,6 +1395,12 @@ var expandTypeDetailed = (type, checker, depth = 0, state) => {
1271
1395
  expandState.stackTypeIds.add(typeId);
1272
1396
  }
1273
1397
  try {
1398
+ if (expandState.wireProjection) {
1399
+ assertSupportedWireType(type, checker);
1400
+ if (isJsonOmittedValueType(type)) {
1401
+ throw new UnsupportedWireTypeError(checker.typeToString(type));
1402
+ }
1403
+ }
1274
1404
  if (depth > DEPTH_LIMIT) {
1275
1405
  return {
1276
1406
  text: checker.typeToString(type),
@@ -1290,7 +1420,8 @@ var expandTypeDetailed = (type, checker, depth = 0, state) => {
1290
1420
  unresolvedSymbols = mergeUnresolvedSymbols(unresolvedSymbols, expanded.unresolvedSymbols);
1291
1421
  return expanded.text;
1292
1422
  });
1293
- return { text: expandedTypes.join(" | "), unresolvedSymbols };
1423
+ const uniqueTypes = [...new Set(expandedTypes)];
1424
+ return { text: uniqueTypes.join(" | "), unresolvedSymbols };
1294
1425
  }
1295
1426
  if (type.isIntersection()) {
1296
1427
  let unresolvedSymbols = [];
@@ -1303,16 +1434,29 @@ var expandTypeDetailed = (type, checker, depth = 0, state) => {
1303
1434
  }
1304
1435
  if (type.flags & ts4.TypeFlags.Object) {
1305
1436
  const objectType = type;
1306
- if (objectType.objectFlags & ts4.ObjectFlags.Tuple) {
1437
+ const tupleTarget = objectType.objectFlags & ts4.ObjectFlags.Reference ? objectType.target : objectType;
1438
+ if (tupleTarget.objectFlags & ts4.ObjectFlags.Tuple) {
1307
1439
  const typeArgs = checker.getTypeArguments(objectType);
1308
1440
  let unresolvedSymbols = [];
1309
1441
  const tupleTypes = typeArgs.map((innerType) => {
1310
- const expanded = expandTypeDetailed(innerType, checker, depth + 1, expandState);
1311
- unresolvedSymbols = mergeUnresolvedSymbols(unresolvedSymbols, expanded.unresolvedSymbols);
1312
- return expanded.text;
1442
+ const wire = expandState.wireProjection ? resolveWireConstituents(innerType, checker) : { serializable: [innerType], omitted: false };
1443
+ const texts = [];
1444
+ for (const serializableType of wire.serializable) {
1445
+ const expanded = expandTypeDetailed(serializableType, checker, depth + 1, expandState);
1446
+ unresolvedSymbols = mergeUnresolvedSymbols(unresolvedSymbols, expanded.unresolvedSymbols);
1447
+ texts.push(expanded.text);
1448
+ }
1449
+ if (wire.omitted) texts.push("null");
1450
+ return [...new Set(texts)].join(" | ");
1313
1451
  });
1314
1452
  return { text: `[${tupleTypes.join(", ")}]`, unresolvedSymbols };
1315
1453
  }
1454
+ if (expandState.wireProjection) {
1455
+ const jsonType = resolveToJsonReturnType(type, checker);
1456
+ if (jsonType && jsonType !== type) {
1457
+ return expandTypeDetailed(jsonType, checker, depth + 1, expandState);
1458
+ }
1459
+ }
1316
1460
  if (objectType.objectFlags & ts4.ObjectFlags.Reference) {
1317
1461
  const refType = objectType;
1318
1462
  const targetName = refType.target.symbol?.name ?? "";
@@ -1320,21 +1464,29 @@ var expandTypeDetailed = (type, checker, depth = 0, state) => {
1320
1464
  const typeArgs = checker.getTypeArguments(refType);
1321
1465
  const firstArg = typeArgs[0];
1322
1466
  if (firstArg) {
1323
- const expanded = expandTypeDetailed(firstArg, checker, depth + 1, expandState);
1324
- const elementType = /\s[|&]\s/.test(expanded.text) ? `(${expanded.text})` : expanded.text;
1325
- return {
1326
- text: `${elementType}[]`,
1327
- unresolvedSymbols: expanded.unresolvedSymbols
1328
- };
1467
+ const wire = expandState.wireProjection ? resolveWireConstituents(firstArg, checker) : { serializable: [firstArg], omitted: false };
1468
+ let unresolvedSymbols = [];
1469
+ const texts = [];
1470
+ for (const serializableType of wire.serializable) {
1471
+ const expanded = expandTypeDetailed(serializableType, checker, depth + 1, expandState);
1472
+ unresolvedSymbols = mergeUnresolvedSymbols(unresolvedSymbols, expanded.unresolvedSymbols);
1473
+ texts.push(expanded.text);
1474
+ }
1475
+ if (wire.omitted) texts.push("null");
1476
+ const expandedText = [...new Set(texts)].join(" | ");
1477
+ const elementType = /\s[|&]\s/.test(expandedText) ? `(${expandedText})` : expandedText;
1478
+ return { text: `${elementType}[]`, unresolvedSymbols };
1329
1479
  }
1330
1480
  }
1331
1481
  if (SKIP_EXPANSION.has(targetName)) {
1332
- return { text: checker.typeToString(type), unresolvedSymbols: [] };
1482
+ const projected = expandState.wireProjection ? WIRE_PROJECTED_TYPES.get(targetName) : void 0;
1483
+ return { text: projected ?? checker.typeToString(type), unresolvedSymbols: [] };
1333
1484
  }
1334
1485
  }
1335
- const symbolName = type.symbol.name || (type.aliasSymbol?.name ?? "");
1486
+ const symbolName = type.symbol?.name || (type.aliasSymbol?.name ?? "");
1336
1487
  if (SKIP_EXPANSION.has(symbolName)) {
1337
- return { text: checker.typeToString(type), unresolvedSymbols: [] };
1488
+ const projected = expandState.wireProjection ? WIRE_PROJECTED_TYPES.get(symbolName) : void 0;
1489
+ return { text: projected ?? checker.typeToString(type), unresolvedSymbols: [] };
1338
1490
  }
1339
1491
  const props = checker.getPropertiesOfType(type);
1340
1492
  const indexInfos = checker.getIndexInfosOfType(type);
@@ -1344,22 +1496,34 @@ var expandTypeDetailed = (type, checker, depth = 0, state) => {
1344
1496
  for (const prop of props) {
1345
1497
  if (prop.getName().startsWith("__@")) continue;
1346
1498
  const propType = checker.getTypeOfSymbol(prop);
1499
+ const wire = expandState.wireProjection ? resolveWireConstituents(propType, checker) : { serializable: [propType], omitted: false };
1500
+ if (wire.serializable.length === 0) continue;
1347
1501
  const literalType = getLiteralTypeFromPropertySymbol(prop, checker, depth + 1);
1348
- const isOptional = (prop.flags & ts4.SymbolFlags.Optional) !== 0;
1349
- if (literalType) {
1502
+ const isOptional = (prop.flags & ts4.SymbolFlags.Optional) !== 0 || wire.omitted;
1503
+ if (literalType && !wire.omitted) {
1350
1504
  fields.push(`${prop.name}${isOptional ? "?" : ""}: ${literalType}`);
1351
1505
  continue;
1352
1506
  }
1353
- const expandedProp = expandTypeDetailed(propType, checker, depth + 1, expandState);
1354
- unresolvedSymbols = mergeUnresolvedSymbols(unresolvedSymbols, expandedProp.unresolvedSymbols);
1355
- fields.push(`${prop.name}${isOptional ? "?" : ""}: ${expandedProp.text}`);
1507
+ const texts = [];
1508
+ for (const serializableType of wire.serializable) {
1509
+ const expandedProp = expandTypeDetailed(serializableType, checker, depth + 1, expandState);
1510
+ unresolvedSymbols = mergeUnresolvedSymbols(unresolvedSymbols, expandedProp.unresolvedSymbols);
1511
+ texts.push(expandedProp.text);
1512
+ }
1513
+ fields.push(`${prop.name}${isOptional ? "?" : ""}: ${[...new Set(texts)].join(" | ")}`);
1356
1514
  }
1357
1515
  for (const indexInfo of indexInfos) {
1358
1516
  const keyType = expandTypeDetailed(indexInfo.keyType, checker, depth + 1, expandState);
1359
- const valueType = expandTypeDetailed(indexInfo.type, checker, depth + 1, expandState);
1517
+ const wire = expandState.wireProjection ? resolveWireConstituents(indexInfo.type, checker) : { serializable: [indexInfo.type], omitted: false };
1518
+ if (wire.serializable.length === 0) continue;
1519
+ const valueTexts = [];
1520
+ for (const serializableType of wire.serializable) {
1521
+ const valueType = expandTypeDetailed(serializableType, checker, depth + 1, expandState);
1522
+ unresolvedSymbols = mergeUnresolvedSymbols(unresolvedSymbols, valueType.unresolvedSymbols);
1523
+ valueTexts.push(valueType.text);
1524
+ }
1360
1525
  unresolvedSymbols = mergeUnresolvedSymbols(unresolvedSymbols, keyType.unresolvedSymbols);
1361
- unresolvedSymbols = mergeUnresolvedSymbols(unresolvedSymbols, valueType.unresolvedSymbols);
1362
- fields.push(`[key: ${keyType.text}]: ${valueType.text}`);
1526
+ fields.push(`[key: ${keyType.text}]: ${[...new Set(valueTexts)].join(" | ")}`);
1363
1527
  }
1364
1528
  const indent = " ".repeat(depth + 1);
1365
1529
  const outerIndent = " ".repeat(depth);
@@ -1504,7 +1668,7 @@ var collectReturnObjectTypeDetails = (funcNode, checker) => {
1504
1668
  const visit = (node) => {
1505
1669
  if (ts5.isReturnStatement(node) && node.expression && ts5.isObjectLiteralExpression(node.expression)) {
1506
1670
  const type = checker.getTypeAtLocation(node.expression);
1507
- const expanded = expandTypeDetailed(type, checker);
1671
+ const expanded = expandTypeDetailed(type, checker, 0, void 0, { wireProjection: true });
1508
1672
  types.push(expanded.text);
1509
1673
  unresolvedSymbols = mergeUnresolvedSymbols2(unresolvedSymbols, expanded.unresolvedSymbols);
1510
1674
  unresolvedSymbols = mergeUnresolvedSymbols2(
@@ -1531,7 +1695,7 @@ var collectStreamCallPayloadTypeDetails = (funcNode, checker) => {
1531
1695
  if (payloadArg) {
1532
1696
  const argType = checker.getTypeAtLocation(payloadArg);
1533
1697
  const nonNullableArgType = checker.getNonNullableType(argType);
1534
- const expanded = expandTypeDetailed(nonNullableArgType, checker);
1698
+ const expanded = expandTypeDetailed(nonNullableArgType, checker, 0, void 0, { wireProjection: true });
1535
1699
  if (expanded.text.trim().length > 0) {
1536
1700
  types.push(expanded.text);
1537
1701
  }
@@ -1553,11 +1717,34 @@ var unionTypes = (types) => {
1553
1717
  const unique = [...new Set(types)];
1554
1718
  return unique.length > 0 ? unique.join(" | ") : "";
1555
1719
  };
1720
+ var UnsupportedTransportInputError = class extends Error {
1721
+ constructor(filePath) {
1722
+ super(`[TypeMapGenerator] ${filePath} declares Date in a transport input. JSON delivers an ISO string; declare string and validate/convert it explicitly.`);
1723
+ this.name = "UnsupportedTransportInputError";
1724
+ }
1725
+ };
1726
+ var assertTransportInputIsWireSafe = (typeText, filePath) => {
1727
+ const sourceFile = ts5.createSourceFile(
1728
+ "__luckystack_input_check.ts",
1729
+ `type __LuckyStackInput = ${typeText};`,
1730
+ ts5.ScriptTarget.Latest,
1731
+ true,
1732
+ ts5.ScriptKind.TS
1733
+ );
1734
+ const containsDate = (node) => {
1735
+ if (ts5.isTypeReferenceNode(node) && ts5.isIdentifier(node.typeName) && node.typeName.text === "Date") {
1736
+ return true;
1737
+ }
1738
+ return node.getChildren(sourceFile).some((child) => containsDate(child));
1739
+ };
1740
+ if (containsDate(sourceFile)) throw new UnsupportedTransportInputError(filePath);
1741
+ };
1556
1742
  var getInputTypeFromFile = (filePath) => {
1557
1743
  return getInputTypeDetailsFromFile(filePath).text;
1558
1744
  };
1559
1745
  var getInputTypeDetailsFromFile = (filePath) => {
1560
1746
  const DEFAULT = "{ }";
1747
+ recordExtractionOutcome({ filePath, kind: "api", field: "input", error: null });
1561
1748
  try {
1562
1749
  const program = getServerProgram();
1563
1750
  const sourceFile = program.getSourceFile(filePath);
@@ -1571,9 +1758,12 @@ var getInputTypeDetailsFromFile = (filePath) => {
1571
1758
  const dataType = getInterfacePropertyType(iface, "data", checker);
1572
1759
  if (!dataType) return { text: DEFAULT, unresolvedSymbols: [] };
1573
1760
  const expanded = expandTypeDetailed(dataType, checker);
1761
+ assertTransportInputIsWireSafe(expanded.text, filePath);
1574
1762
  return { text: expanded.text || DEFAULT, unresolvedSymbols: expanded.unresolvedSymbols };
1575
1763
  } catch (error) {
1764
+ if (error instanceof UnsupportedTransportInputError) throw error;
1576
1765
  console.error(`[TypeMapGenerator] Error extracting input type from ${filePath}:`, error);
1766
+ recordExtractionOutcome({ filePath, kind: "api", field: "input", error });
1577
1767
  return { text: DEFAULT, unresolvedSymbols: [] };
1578
1768
  }
1579
1769
  };
@@ -1594,7 +1784,7 @@ var extractDeclaredStreamTypeFromApiParams = (sourceFile, checker) => {
1594
1784
  const typeArg = member.type.typeArguments?.[0];
1595
1785
  if (typeArg) {
1596
1786
  const argType = checker.getNonNullableType(checker.getTypeFromTypeNode(typeArg));
1597
- const expanded = expandTypeDetailed(argType, checker);
1787
+ const expanded = expandTypeDetailed(argType, checker, 0, void 0, { wireProjection: true });
1598
1788
  if (expanded.text.trim().length > 0) {
1599
1789
  result = { text: expanded.text, unresolvedSymbols: expanded.unresolvedSymbols };
1600
1790
  return;
@@ -1610,6 +1800,7 @@ var extractDeclaredStreamTypeFromApiParams = (sourceFile, checker) => {
1610
1800
  };
1611
1801
  var getApiStreamPayloadTypeDetailsFromFile = (filePath) => {
1612
1802
  const DEFAULT = "never";
1803
+ recordExtractionOutcome({ filePath, kind: "api", field: "stream", error: null });
1613
1804
  try {
1614
1805
  const program = getServerProgram();
1615
1806
  const sourceFile = program.getSourceFile(filePath);
@@ -1623,12 +1814,15 @@ var getApiStreamPayloadTypeDetailsFromFile = (filePath) => {
1623
1814
  if (declared) return declared;
1624
1815
  return { text: DEFAULT, unresolvedSymbols: [] };
1625
1816
  } catch (error) {
1817
+ if (error instanceof UnsupportedWireTypeError) throw error;
1626
1818
  console.error(`[TypeMapGenerator] Error extracting API stream payload type from ${filePath}:`, error);
1819
+ recordExtractionOutcome({ filePath, kind: "api", field: "stream", error });
1627
1820
  return { text: DEFAULT, unresolvedSymbols: [] };
1628
1821
  }
1629
1822
  };
1630
1823
  var getOutputTypeDetailsFromFile = (filePath) => {
1631
1824
  const DEFAULT = "{ status: string }";
1825
+ recordExtractionOutcome({ filePath, kind: "api", field: "output", error: null });
1632
1826
  try {
1633
1827
  const program = getServerProgram();
1634
1828
  const sourceFile = program.getSourceFile(filePath);
@@ -1639,7 +1833,9 @@ var getOutputTypeDetailsFromFile = (filePath) => {
1639
1833
  const details = collectReturnObjectTypeDetails(mainFn, checker);
1640
1834
  return { text: details.text || DEFAULT, unresolvedSymbols: details.unresolvedSymbols };
1641
1835
  } catch (error) {
1836
+ if (error instanceof UnsupportedWireTypeError) throw error;
1642
1837
  console.error(`[TypeMapGenerator] Error extracting output type from ${filePath}:`, error);
1838
+ recordExtractionOutcome({ filePath, kind: "api", field: "output", error });
1643
1839
  return { text: DEFAULT, unresolvedSymbols: [] };
1644
1840
  }
1645
1841
  };
@@ -1648,6 +1844,7 @@ var getSyncClientDataType = (filePath) => {
1648
1844
  };
1649
1845
  var getSyncClientDataTypeDetailsFromFile = (filePath) => {
1650
1846
  const DEFAULT = "{ }";
1847
+ recordExtractionOutcome({ filePath, kind: "sync", field: "clientInput", error: null });
1651
1848
  try {
1652
1849
  const program = getServerProgram();
1653
1850
  const sourceFile = program.getSourceFile(filePath);
@@ -1658,14 +1855,18 @@ var getSyncClientDataTypeDetailsFromFile = (filePath) => {
1658
1855
  const dataType = getInterfacePropertyType(iface, "clientInput", checker) ?? getInterfacePropertyType(iface, "clientData", checker);
1659
1856
  if (!dataType) return { text: DEFAULT, unresolvedSymbols: [] };
1660
1857
  const expanded = expandTypeDetailed(dataType, checker);
1858
+ assertTransportInputIsWireSafe(expanded.text, filePath);
1661
1859
  return { text: expanded.text || DEFAULT, unresolvedSymbols: expanded.unresolvedSymbols };
1662
1860
  } catch (error) {
1861
+ if (error instanceof UnsupportedTransportInputError) throw error;
1663
1862
  console.error(`[TypeMapGenerator] Error extracting sync clientData type from ${filePath}:`, error);
1863
+ recordExtractionOutcome({ filePath, kind: "sync", field: "clientInput", error });
1664
1864
  return { text: DEFAULT, unresolvedSymbols: [] };
1665
1865
  }
1666
1866
  };
1667
1867
  var getSyncServerStreamPayloadTypeDetailsFromFile = (filePath) => {
1668
1868
  const DEFAULT = "never";
1869
+ recordExtractionOutcome({ filePath, kind: "sync", field: "serverStream", error: null });
1669
1870
  try {
1670
1871
  const program = getServerProgram();
1671
1872
  const sourceFile = program.getSourceFile(filePath);
@@ -1676,12 +1877,15 @@ var getSyncServerStreamPayloadTypeDetailsFromFile = (filePath) => {
1676
1877
  const details = collectStreamCallPayloadTypeDetails(mainFn, checker);
1677
1878
  return { text: details.text || DEFAULT, unresolvedSymbols: details.unresolvedSymbols };
1678
1879
  } catch (error) {
1880
+ if (error instanceof UnsupportedWireTypeError) throw error;
1679
1881
  console.error(`[TypeMapGenerator] Error extracting sync server stream payload type from ${filePath}:`, error);
1882
+ recordExtractionOutcome({ filePath, kind: "sync", field: "serverStream", error });
1680
1883
  return { text: DEFAULT, unresolvedSymbols: [] };
1681
1884
  }
1682
1885
  };
1683
1886
  var getSyncServerOutputTypeDetailsFromFile = (filePath) => {
1684
1887
  const DEFAULT = "{ status: string }";
1888
+ recordExtractionOutcome({ filePath, kind: "sync", field: "serverOutput", error: null });
1685
1889
  try {
1686
1890
  const program = getServerProgram();
1687
1891
  const sourceFile = program.getSourceFile(filePath);
@@ -1692,12 +1896,15 @@ var getSyncServerOutputTypeDetailsFromFile = (filePath) => {
1692
1896
  const details = collectReturnObjectTypeDetails(mainFn, checker);
1693
1897
  return { text: details.text || DEFAULT, unresolvedSymbols: details.unresolvedSymbols };
1694
1898
  } catch (error) {
1899
+ if (error instanceof UnsupportedWireTypeError) throw error;
1695
1900
  console.error(`[TypeMapGenerator] Error extracting sync serverOutput type from ${filePath}:`, error);
1901
+ recordExtractionOutcome({ filePath, kind: "sync", field: "serverOutput", error });
1696
1902
  return { text: DEFAULT, unresolvedSymbols: [] };
1697
1903
  }
1698
1904
  };
1699
1905
  var getSyncClientStreamPayloadTypeDetailsFromFile = (filePath) => {
1700
1906
  const DEFAULT = "never";
1907
+ recordExtractionOutcome({ filePath, kind: "sync", field: "clientStream", error: null });
1701
1908
  try {
1702
1909
  const program = getServerProgram();
1703
1910
  const sourceFile = program.getSourceFile(filePath);
@@ -1708,12 +1915,15 @@ var getSyncClientStreamPayloadTypeDetailsFromFile = (filePath) => {
1708
1915
  const details = collectStreamCallPayloadTypeDetails(mainFn, checker);
1709
1916
  return { text: details.text || DEFAULT, unresolvedSymbols: details.unresolvedSymbols };
1710
1917
  } catch (error) {
1918
+ if (error instanceof UnsupportedWireTypeError) throw error;
1711
1919
  console.error(`[TypeMapGenerator] Error extracting sync client stream payload type from ${filePath}:`, error);
1920
+ recordExtractionOutcome({ filePath, kind: "sync", field: "clientStream", error });
1712
1921
  return { text: DEFAULT, unresolvedSymbols: [] };
1713
1922
  }
1714
1923
  };
1715
1924
  var getSyncClientOutputTypeDetailsFromFile = (filePath) => {
1716
1925
  const DEFAULT = "{ }";
1926
+ recordExtractionOutcome({ filePath, kind: "sync", field: "clientOutput", error: null });
1717
1927
  try {
1718
1928
  const program = getServerProgram();
1719
1929
  const sourceFile = program.getSourceFile(filePath);
@@ -1724,7 +1934,9 @@ var getSyncClientOutputTypeDetailsFromFile = (filePath) => {
1724
1934
  const details = collectReturnObjectTypeDetails(mainFn, checker);
1725
1935
  return { text: details.text || DEFAULT, unresolvedSymbols: details.unresolvedSymbols };
1726
1936
  } catch (error) {
1937
+ if (error instanceof UnsupportedWireTypeError) throw error;
1727
1938
  console.error(`[TypeMapGenerator] Error extracting sync clientOutput type from ${filePath}:`, error);
1939
+ recordExtractionOutcome({ filePath, kind: "sync", field: "clientOutput", error });
1728
1940
  return { text: DEFAULT, unresolvedSymbols: [] };
1729
1941
  }
1730
1942
  };
@@ -1855,6 +2067,14 @@ var simplifyInferredType = (value) => {
1855
2067
  return value;
1856
2068
  };
1857
2069
  var getGeneratedFileDir = () => path7.dirname(getGeneratedSocketTypesPath5());
2070
+ var sourceModuleSpecifier = (sourceFilePath) => {
2071
+ const relative = path7.relative(getGeneratedFileDir(), sourceFilePath).replaceAll("\\", "/").replace(/\.tsx?$/i, "");
2072
+ return relative.startsWith(".") ? relative : `./${relative}`;
2073
+ };
2074
+ var portableInferredValueType = (inferred, sourceFilePath, exportName) => {
2075
+ if (!inferred.includes("typeof import(") && !inferred.includes("...")) return null;
2076
+ return `typeof import('${sourceModuleSpecifier(sourceFilePath)}')['${exportName}']`;
2077
+ };
1858
2078
  var relativizeModuleSpecifier = (specifier, sourceFilePath) => {
1859
2079
  if (!specifier.startsWith("./") && !specifier.startsWith("../")) {
1860
2080
  return specifier;
@@ -1949,6 +2169,8 @@ var inferValueTypeForExport = ({
1949
2169
  const programDeclaration = findProgramVariableDeclaration(resolvedSource, exportName);
1950
2170
  if (!programDeclaration) return "any";
1951
2171
  const inferred = resolvedChecker.typeToString(resolvedChecker.getTypeAtLocation(programDeclaration.name));
2172
+ const portable = portableInferredValueType(inferred, filePath, exportName);
2173
+ if (portable) return portable;
1952
2174
  const simplified = simplifyInferredType(normalizeInlineType(inferred));
1953
2175
  const localResolvedType = resolveLocalExportedTypes({
1954
2176
  type: simplified,