zodvex 0.7.2-beta.0 → 0.7.2

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,5 +1,4 @@
1
1
  import path from 'path';
2
- import { pathToFileURL } from 'url';
3
2
  import { globSync } from 'tinyglobby';
4
3
  import { z } from 'zod';
5
4
  import { $ZodCodec, $ZodNumber, $ZodCustom, $ZodOptional, $ZodNullable, $ZodString, $ZodBoolean, $ZodNull, $ZodUndefined, $ZodAny, $ZodObject, $ZodArray, $ZodEnum, $ZodLiteral, $ZodUnion, $ZodTuple, $ZodRecord, $ZodType, clone, $ZodDiscriminatedUnion } from 'zod/v4/core';
@@ -21,6 +20,20 @@ function readMeta(target) {
21
20
  }
22
21
  return target[META_KEY];
23
22
  }
23
+ var CODEC_BRAND_KEY = "__zodvexCodecBrand";
24
+ function attachCodecBrand(target, brand) {
25
+ Object.defineProperty(target, CODEC_BRAND_KEY, {
26
+ value: brand,
27
+ enumerable: false,
28
+ writable: false,
29
+ configurable: false
30
+ });
31
+ }
32
+ function readCodecBrand(target) {
33
+ if (target == null || typeof target !== "object") return void 0;
34
+ const value = target[CODEC_BRAND_KEY];
35
+ return typeof value === "string" ? value : void 0;
36
+ }
24
37
  var metadata = /* @__PURE__ */ new WeakMap();
25
38
  var registryHelpers = {
26
39
  getMetadata: (type) => metadata.get(type),
@@ -61,8 +74,10 @@ function id(tableName) {
61
74
  branded._tableName = tableName;
62
75
  return branded;
63
76
  }
64
- function codec(wire, runtime, transforms) {
65
- return zodvexCodec(wire, runtime, transforms);
77
+ function codec(wire, runtime, transforms, opts) {
78
+ const built = zodvexCodec(wire, runtime, transforms);
79
+ if (opts?.brand) attachCodecBrand(built, opts.brand);
80
+ return built;
66
81
  }
67
82
  function sharedWeakMap(name) {
68
83
  const key = /* @__PURE__ */ Symbol.for(`zodvex.zx.cache.${name}`);
@@ -408,7 +423,6 @@ function readFnReturns(fn) {
408
423
  }
409
424
 
410
425
  // src/public/codegen/discover.ts
411
- var discoveryRunCounter = 0;
412
426
  function walkSchemaRecursive(schema, accessPath, visited, seenCodecs, results) {
413
427
  if (visited.has(schema)) return;
414
428
  visited.add(schema);
@@ -546,7 +560,7 @@ function walkFunctionCodecs(functions) {
546
560
  }
547
561
  return found;
548
562
  }
549
- async function discoverModules(convexDir, options) {
563
+ async function discoverModules(convexDir) {
550
564
  const models = [];
551
565
  const functions = [];
552
566
  const codecs = [];
@@ -570,18 +584,12 @@ async function discoverModules(convexDir, options) {
570
584
  "crons.js"
571
585
  ]
572
586
  }).sort();
573
- let cacheKey = null;
574
- if (options?.freshImports) {
575
- discoveryRunCounter += 1;
576
- cacheKey = `${process.pid}-${discoveryRunCounter}`;
577
- }
578
587
  try {
579
588
  for (const file of files) {
580
589
  const absPath = path.resolve(convexDir, file);
581
- const importUrl = cacheKey ? `${pathToFileURL(absPath).href}?t=${cacheKey}` : absPath;
582
590
  let moduleExports;
583
591
  try {
584
- moduleExports = await import(importUrl);
592
+ moduleExports = await import(absPath);
585
593
  } catch (err) {
586
594
  console.warn(`[zodvex] Warning: Failed to import ${file}:`, err.message);
587
595
  continue;
@@ -736,7 +744,10 @@ var HEADER = `// AUTO-GENERATED by zodvex \u2014 do not edit
736
744
  `;
737
745
  function fingerprintCodec(schema) {
738
746
  if (!(schema instanceof $ZodCodec)) return "";
739
- return `${fingerprintLeaf(schema._zod.def.in)}|${fingerprintLeaf(schema._zod.def.out)}`;
747
+ const def = schema._zod.def;
748
+ const transform = typeof def.transform === "function" ? def.transform.toString() : "";
749
+ const reverse = typeof def.reverseTransform === "function" ? def.reverseTransform.toString() : "";
750
+ return `${fingerprintLeaf(def.in)}|${fingerprintLeaf(def.out)}|${transform}|${reverse}`;
740
751
  }
741
752
  function fingerprintLeaf(schema) {
742
753
  return `${zodToSource(schema)}#${fingerprintChecks(schema)}`;
@@ -955,6 +966,7 @@ function generateApiFile(functions, models, codecs, modelCodecs, functionCodecs,
955
966
  }
956
967
  if (functionCodecs) {
957
968
  const fingerprintMap = /* @__PURE__ */ new Map();
969
+ const brandMap = /* @__PURE__ */ new Map();
958
970
  const codecSchemaToSourceFile = /* @__PURE__ */ new Map();
959
971
  for (const c of codecs ?? []) {
960
972
  codecSchemaToSourceFile.set(c.schema, c.sourceFile);
@@ -963,24 +975,34 @@ function generateApiFile(functions, models, codecs, modelCodecs, functionCodecs,
963
975
  codecSchemaToSourceFile.set(mc.codec, mc.modelSourceFile);
964
976
  }
965
977
  for (const [codecSchema, ref] of codecMap) {
966
- const fp = fingerprintCodec(codecSchema);
967
- if (!fp) continue;
968
978
  const sourceFile = codecSchemaToSourceFile.get(codecSchema);
969
- const existing = fingerprintMap.get(fp);
970
- if (existing) {
971
- existing.push({ ref, sourceFile });
972
- } else {
973
- fingerprintMap.set(fp, [{ ref, sourceFile }]);
979
+ const brand = readCodecBrand(codecSchema);
980
+ const candidate = { ref, sourceFile, brand };
981
+ const fp = fingerprintCodec(codecSchema);
982
+ if (fp) {
983
+ const existing = fingerprintMap.get(fp);
984
+ if (existing) existing.push(candidate);
985
+ else fingerprintMap.set(fp, [candidate]);
986
+ }
987
+ if (brand) {
988
+ const existing = brandMap.get(brand);
989
+ if (existing) existing.push(candidate);
990
+ else brandMap.set(brand, [candidate]);
974
991
  }
975
992
  }
993
+ const undiscoverable = [];
976
994
  for (const fc of functionCodecs) {
977
995
  if (codecMap.has(fc.codec)) continue;
978
- const fp = fingerprintCodec(fc.codec);
979
- const candidates = fp ? fingerprintMap.get(fp) : void 0;
996
+ const brand = readCodecBrand(fc.codec);
997
+ let candidates = brand ? brandMap.get(brand) : void 0;
980
998
  if (!candidates || candidates.length === 0) {
981
- console.warn(
982
- `[zodvex] Warning: Codec in ${fc.functionExportName}() (${fc.accessPath}) has no matching model or exported codec. Export it standalone for full client-side codec support.`
999
+ const fp = fingerprintCodec(fc.codec);
1000
+ candidates = (fp ? fingerprintMap.get(fp) : void 0)?.filter(
1001
+ (c) => c.brand === void 0 || c.brand === brand
983
1002
  );
1003
+ }
1004
+ if (!candidates || candidates.length === 0) {
1005
+ undiscoverable.push({ fn: fc.functionExportName, path: fc.accessPath });
984
1006
  continue;
985
1007
  }
986
1008
  let chosen;
@@ -991,14 +1013,27 @@ function generateApiFile(functions, models, codecs, modelCodecs, functionCodecs,
991
1013
  if (sameFile.length === 1) {
992
1014
  chosen = sameFile[0].ref;
993
1015
  } else {
994
- console.warn(
995
- `[zodvex] Warning: Codec in ${fc.functionExportName}() (${fc.accessPath}) matches ${candidates.length} candidates with the same fingerprint (${candidates.map((c) => c.ref.exportName).join(", ")}). Cannot pick a canonical reference \u2014 emitting inline. Export the codec standalone to disambiguate.`
1016
+ const sorted = [...candidates].sort(
1017
+ (a, b) => `${a.sourceFile ?? ""}|${a.ref.exportName}`.localeCompare(
1018
+ `${b.sourceFile ?? ""}|${b.ref.exportName}`
1019
+ )
996
1020
  );
1021
+ chosen = sorted[0].ref;
1022
+ if (!brand) {
1023
+ console.warn(
1024
+ `[zodvex] Note: Codec in ${fc.functionExportName}() (${fc.accessPath}) matches ${candidates.length} fingerprint-equivalent codecs (${candidates.map((c) => c.ref.exportName).join(", ")}). Referencing '${chosen.exportName}'. Export the codec standalone or give it a brand if you want the reference to be explicit.`
1025
+ );
1026
+ }
997
1027
  }
998
1028
  }
999
- if (chosen) {
1000
- codecMap.set(fc.codec, chosen);
1001
- }
1029
+ codecMap.set(fc.codec, chosen);
1030
+ }
1031
+ if (undiscoverable.length > 0) {
1032
+ const list = undiscoverable.map((u) => ` - ${u.fn}() at ${u.path}`).join("\n");
1033
+ throw new Error(
1034
+ `[zodvex] ${undiscoverable.length} codec(s) in function args/returns have no importable reference (not exported standalone, not embedded in a model). The generated client cannot encode or decode them, which silently breaks the codec boundary. Export each codec standalone (or add it to a model) so codegen can import it:
1035
+ ${list}`
1036
+ );
1002
1037
  }
1003
1038
  }
1004
1039
  const zodToSourceCtx = {