zodvex 0.7.1 → 0.7.2-beta.1

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.
Files changed (48) hide show
  1. package/dist/cli/index.js +186 -24
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/codegen/index.js +137 -14
  4. package/dist/codegen/index.js.map +1 -1
  5. package/dist/core/index.js +56 -10
  6. package/dist/core/index.js.map +1 -1
  7. package/dist/index.js +56 -10
  8. package/dist/index.js.map +1 -1
  9. package/dist/internal/db.d.ts +14 -11
  10. package/dist/internal/db.d.ts.map +1 -1
  11. package/dist/internal/meta.d.ts +11 -0
  12. package/dist/internal/meta.d.ts.map +1 -1
  13. package/dist/internal/model.d.ts +21 -0
  14. package/dist/internal/model.d.ts.map +1 -1
  15. package/dist/internal/zx.d.ts +14 -0
  16. package/dist/internal/zx.d.ts.map +1 -1
  17. package/dist/labs/index.js +29 -4
  18. package/dist/labs/index.js.map +1 -1
  19. package/dist/legacy/index.js +13 -2
  20. package/dist/legacy/index.js.map +1 -1
  21. package/dist/mini/index.js +58 -12
  22. package/dist/mini/index.js.map +1 -1
  23. package/dist/mini/server/index.js +37 -27
  24. package/dist/mini/server/index.js.map +1 -1
  25. package/dist/public/cli/commands.d.ts +12 -0
  26. package/dist/public/cli/commands.d.ts.map +1 -1
  27. package/dist/public/codegen/discover.d.ts +8 -0
  28. package/dist/public/codegen/discover.d.ts.map +1 -1
  29. package/dist/public/codegen/generate.d.ts.map +1 -1
  30. package/dist/public/mini/model.d.ts +6 -0
  31. package/dist/public/mini/model.d.ts.map +1 -1
  32. package/dist/public/mini/zx.d.ts +2 -0
  33. package/dist/public/mini/zx.d.ts.map +1 -1
  34. package/dist/public/model.d.ts +6 -0
  35. package/dist/public/model.d.ts.map +1 -1
  36. package/dist/server/index.js +37 -27
  37. package/dist/server/index.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/internal/db.ts +33 -52
  40. package/src/internal/meta.ts +26 -0
  41. package/src/internal/model.ts +76 -9
  42. package/src/internal/zx.ts +18 -2
  43. package/src/public/cli/commands.ts +35 -6
  44. package/src/public/codegen/discover.ts +9 -1
  45. package/src/public/codegen/generate.ts +203 -15
  46. package/src/public/mini/model.ts +6 -0
  47. package/src/public/mini/zx.ts +3 -2
  48. package/src/public/model.ts +6 -0
@@ -20,6 +20,20 @@ function readMeta(target) {
20
20
  }
21
21
  return target[META_KEY];
22
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
+ }
23
37
  var metadata = /* @__PURE__ */ new WeakMap();
24
38
  var registryHelpers = {
25
39
  getMetadata: (type) => metadata.get(type),
@@ -60,8 +74,10 @@ function id(tableName) {
60
74
  branded._tableName = tableName;
61
75
  return branded;
62
76
  }
63
- function codec(wire, runtime, transforms) {
64
- 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;
65
81
  }
66
82
  function sharedWeakMap(name) {
67
83
  const key = /* @__PURE__ */ Symbol.for(`zodvex.zx.cache.${name}`);
@@ -567,7 +583,7 @@ async function discoverModules(convexDir) {
567
583
  "crons.ts",
568
584
  "crons.js"
569
585
  ]
570
- });
586
+ }).sort();
571
587
  try {
572
588
  for (const file of files) {
573
589
  const absPath = path.resolve(convexDir, file);
@@ -728,7 +744,35 @@ var HEADER = `// AUTO-GENERATED by zodvex \u2014 do not edit
728
744
  `;
729
745
  function fingerprintCodec(schema) {
730
746
  if (!(schema instanceof $ZodCodec)) return "";
731
- return `${zodToSource(schema._zod.def.in)}|${zodToSource(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}`;
751
+ }
752
+ function fingerprintLeaf(schema) {
753
+ return `${zodToSource(schema)}#${fingerprintChecks(schema)}`;
754
+ }
755
+ function fingerprintChecks(schema) {
756
+ const checks = schema?._zod?.def?.checks;
757
+ if (!Array.isArray(checks) || checks.length === 0) return "";
758
+ const parts = [];
759
+ for (const check of checks) {
760
+ const def = check?._zod?.def;
761
+ if (!def) continue;
762
+ const checkType = def.check ?? def.type ?? "check";
763
+ const data = {};
764
+ for (const [k, v7] of Object.entries(def)) {
765
+ if (k === "check" || k === "type" || k === "error" || k === "message") continue;
766
+ const t = typeof v7;
767
+ if (t === "string" || t === "number" || t === "boolean" || v7 === null) {
768
+ data[k] = v7;
769
+ } else if (v7 instanceof RegExp) {
770
+ data[k] = v7.source;
771
+ }
772
+ }
773
+ parts.push(`${checkType}(${JSON.stringify(data)})`);
774
+ }
775
+ return parts.sort().join("&");
732
776
  }
733
777
  function generateSchemaFile(models) {
734
778
  const exports$1 = models.map((m) => {
@@ -815,6 +859,30 @@ function deriveCodecVarName(modelExportName, accessPath) {
815
859
  return `_${prefix}${fieldPart[0].toUpperCase() + fieldPart.slice(1)}`;
816
860
  }
817
861
  function generateApiFile(functions, models, codecs, modelCodecs, functionCodecs, options) {
862
+ const sortedModels = [...models].sort(
863
+ (a, b) => `${a.sourceFile}|${a.exportName}`.localeCompare(`${b.sourceFile}|${b.exportName}`)
864
+ );
865
+ const sortedFunctions = [...functions].sort(
866
+ (a, b) => a.functionPath.localeCompare(b.functionPath)
867
+ );
868
+ const sortedCodecs = codecs ? [...codecs].sort(
869
+ (a, b) => `${a.sourceFile}|${a.exportName}`.localeCompare(`${b.sourceFile}|${b.exportName}`)
870
+ ) : void 0;
871
+ const sortedModelCodecs = modelCodecs ? [...modelCodecs].sort(
872
+ (a, b) => `${a.modelSourceFile}|${a.modelExportName}|${a.schemaKey}|${a.accessPath}`.localeCompare(
873
+ `${b.modelSourceFile}|${b.modelExportName}|${b.schemaKey}|${b.accessPath}`
874
+ )
875
+ ) : void 0;
876
+ const sortedFunctionCodecs = functionCodecs ? [...functionCodecs].sort(
877
+ (a, b) => `${a.functionSourceFile}|${a.functionExportName}|${a.schemaSource}|${a.accessPath}`.localeCompare(
878
+ `${b.functionSourceFile}|${b.functionExportName}|${b.schemaSource}|${b.accessPath}`
879
+ )
880
+ ) : void 0;
881
+ models = sortedModels;
882
+ functions = sortedFunctions;
883
+ codecs = sortedCodecs;
884
+ modelCodecs = sortedModelCodecs;
885
+ functionCodecs = sortedFunctionCodecs;
818
886
  const identityMap = /* @__PURE__ */ new Map();
819
887
  const neededModelImports = /* @__PURE__ */ new Set();
820
888
  for (const model of models) {
@@ -898,21 +966,74 @@ function generateApiFile(functions, models, codecs, modelCodecs, functionCodecs,
898
966
  }
899
967
  if (functionCodecs) {
900
968
  const fingerprintMap = /* @__PURE__ */ new Map();
969
+ const brandMap = /* @__PURE__ */ new Map();
970
+ const codecSchemaToSourceFile = /* @__PURE__ */ new Map();
971
+ for (const c of codecs ?? []) {
972
+ codecSchemaToSourceFile.set(c.schema, c.sourceFile);
973
+ }
974
+ for (const mc of modelCodecs ?? []) {
975
+ codecSchemaToSourceFile.set(mc.codec, mc.modelSourceFile);
976
+ }
901
977
  for (const [codecSchema, ref] of codecMap) {
978
+ const sourceFile = codecSchemaToSourceFile.get(codecSchema);
979
+ const brand = readCodecBrand(codecSchema);
980
+ const candidate = { ref, sourceFile, brand };
902
981
  const fp = fingerprintCodec(codecSchema);
903
- if (fp) fingerprintMap.set(fp, ref);
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]);
991
+ }
904
992
  }
993
+ const undiscoverable = [];
905
994
  for (const fc of functionCodecs) {
906
995
  if (codecMap.has(fc.codec)) continue;
907
- const fp = fingerprintCodec(fc.codec);
908
- const matchingRef = fp ? fingerprintMap.get(fp) : void 0;
909
- if (matchingRef) {
910
- codecMap.set(fc.codec, matchingRef);
911
- } else {
912
- console.warn(
913
- `[zodvex] Warning: Codec in ${fc.functionExportName}() (${fc.accessPath}) has no matching model or exported codec. Export it standalone for full client-side codec support.`
996
+ const brand = readCodecBrand(fc.codec);
997
+ let candidates = brand ? brandMap.get(brand) : void 0;
998
+ if (!candidates || candidates.length === 0) {
999
+ const fp = fingerprintCodec(fc.codec);
1000
+ candidates = (fp ? fingerprintMap.get(fp) : void 0)?.filter(
1001
+ (c) => c.brand === void 0 || c.brand === brand
914
1002
  );
915
1003
  }
1004
+ if (!candidates || candidates.length === 0) {
1005
+ undiscoverable.push({ fn: fc.functionExportName, path: fc.accessPath });
1006
+ continue;
1007
+ }
1008
+ let chosen;
1009
+ if (candidates.length === 1) {
1010
+ chosen = candidates[0].ref;
1011
+ } else {
1012
+ const sameFile = candidates.filter((c) => c.sourceFile === fc.functionSourceFile);
1013
+ if (sameFile.length === 1) {
1014
+ chosen = sameFile[0].ref;
1015
+ } else {
1016
+ const sorted = [...candidates].sort(
1017
+ (a, b) => `${a.sourceFile ?? ""}|${a.ref.exportName}`.localeCompare(
1018
+ `${b.sourceFile ?? ""}|${b.ref.exportName}`
1019
+ )
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
+ }
1027
+ }
1028
+ }
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
+ );
916
1037
  }
917
1038
  }
918
1039
  const zodToSourceCtx = {
@@ -971,15 +1092,17 @@ function generateApiFile(functions, models, codecs, modelCodecs, functionCodecs,
971
1092
  if (coreImports.length > 0) {
972
1093
  imports.push(`import { ${coreImports.join(", ")} } from '${zodvexImport}'`);
973
1094
  }
974
- for (const exportName of neededModelImports) {
1095
+ for (const exportName of [...neededModelImports].sort()) {
975
1096
  const model = models.find((m) => m.exportName === exportName);
976
1097
  if (model) {
977
1098
  const importPath = `../${model.sourceFile.replace(/\.ts$/, ".js")}`;
978
1099
  imports.push(`import { ${exportName} } from '${importPath}'`);
979
1100
  }
980
1101
  }
981
- for (const [importPath, exportNames] of zodToSourceCtx.neededCodecImports) {
1102
+ const sortedCodecImportPaths = [...zodToSourceCtx.neededCodecImports.keys()].sort();
1103
+ for (const importPath of sortedCodecImportPaths) {
982
1104
  if (importPath === MODEL_CODEC_SENTINEL) continue;
1105
+ const exportNames = zodToSourceCtx.neededCodecImports.get(importPath) ?? /* @__PURE__ */ new Set();
983
1106
  const names = Array.from(exportNames).sort().join(", ");
984
1107
  imports.push(`import { ${names} } from '${importPath}'`);
985
1108
  }