zodvex 0.7.1 → 0.7.2-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +130 -17
- package/dist/cli/index.js.map +1 -1
- package/dist/codegen/index.js +99 -11
- package/dist/codegen/index.js.map +1 -1
- package/dist/core/index.js +43 -8
- package/dist/core/index.js.map +1 -1
- package/dist/index.js +43 -8
- package/dist/index.js.map +1 -1
- package/dist/internal/db.d.ts +14 -11
- package/dist/internal/db.d.ts.map +1 -1
- package/dist/internal/model.d.ts +21 -0
- package/dist/internal/model.d.ts.map +1 -1
- package/dist/labs/index.js +29 -4
- package/dist/labs/index.js.map +1 -1
- package/dist/mini/index.js +43 -8
- package/dist/mini/index.js.map +1 -1
- package/dist/mini/server/index.js +24 -25
- package/dist/mini/server/index.js.map +1 -1
- package/dist/public/cli/commands.d.ts +1 -0
- package/dist/public/cli/commands.d.ts.map +1 -1
- package/dist/public/codegen/discover.d.ts +11 -1
- package/dist/public/codegen/discover.d.ts.map +1 -1
- package/dist/public/codegen/generate.d.ts.map +1 -1
- package/dist/public/mini/model.d.ts +6 -0
- package/dist/public/mini/model.d.ts.map +1 -1
- package/dist/public/model.d.ts +6 -0
- package/dist/public/model.d.ts.map +1 -1
- package/dist/server/index.js +24 -25
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
- package/src/internal/db.ts +33 -52
- package/src/internal/model.ts +76 -9
- package/src/public/cli/commands.ts +10 -3
- package/src/public/codegen/discover.ts +29 -3
- package/src/public/codegen/generate.ts +141 -10
- package/src/public/mini/model.ts +6 -0
- package/src/public/model.ts +6 -0
package/dist/cli/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import fs2, { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
|
3
3
|
import path3, { resolve, relative, join } from 'path';
|
|
4
4
|
import { Project, SyntaxKind } from 'ts-morph';
|
|
5
5
|
import { globSync } from 'tinyglobby';
|
|
6
|
+
import { pathToFileURL } from 'url';
|
|
6
7
|
import { z } from 'zod';
|
|
7
8
|
import { $ZodCodec, $ZodNumber, $ZodCustom, $ZodType, $ZodOptional, $ZodNullable, $ZodObject, $ZodUnion, $ZodArray, $ZodRecord, $ZodTuple, clone, $ZodString, $ZodBoolean, $ZodNull, $ZodUndefined, $ZodAny, $ZodEnum, $ZodLiteral, $ZodDiscriminatedUnion } from 'zod/v4/core';
|
|
8
9
|
import 'convex/values';
|
|
@@ -318,7 +319,7 @@ function getMethodName(call) {
|
|
|
318
319
|
if (expr.getKind() !== SyntaxKind.PropertyAccessExpression) return null;
|
|
319
320
|
return expr.getName();
|
|
320
321
|
}
|
|
321
|
-
function transformWrappers(file) {
|
|
322
|
+
function transformWrappers(file, typeChecker) {
|
|
322
323
|
let count = 0;
|
|
323
324
|
const calls = file.getDescendantsOfKind(SyntaxKind.CallExpression).reverse();
|
|
324
325
|
for (const call of calls) {
|
|
@@ -328,6 +329,18 @@ function transformWrappers(file) {
|
|
|
328
329
|
if (call.getArguments().length > 0) continue;
|
|
329
330
|
const obj = getCallObject(call);
|
|
330
331
|
if (!obj) continue;
|
|
332
|
+
if (isNamespaceCall(obj)) continue;
|
|
333
|
+
let isSchema;
|
|
334
|
+
if (typeChecker) {
|
|
335
|
+
const typeResult = isZodSchemaByType(call, typeChecker);
|
|
336
|
+
isSchema = typeResult === true || typeResult === null && isLikelySchemaExpr(obj);
|
|
337
|
+
} else {
|
|
338
|
+
isSchema = isLikelySchemaExpr(obj);
|
|
339
|
+
}
|
|
340
|
+
if (!isSchema && isSchemaVariable(call, obj.trim())) {
|
|
341
|
+
isSchema = true;
|
|
342
|
+
}
|
|
343
|
+
if (!isSchema) continue;
|
|
331
344
|
call.replaceWithText(`z.${method}(${obj})`);
|
|
332
345
|
count++;
|
|
333
346
|
}
|
|
@@ -337,8 +350,9 @@ function isNamespaceCall(obj) {
|
|
|
337
350
|
return NAMESPACE_IDENTIFIERS.has(obj.trim());
|
|
338
351
|
}
|
|
339
352
|
function isLikelySchemaExpr(obj) {
|
|
340
|
-
|
|
341
|
-
if (
|
|
353
|
+
const normalized = obj.trim().replace(/^\(+/, "").replace(/\s+as\s+[\w$<>,\s|&[\]]+\)*\s*$/, "").trim();
|
|
354
|
+
if (normalized.match(/^z\.\w+\(/)) return true;
|
|
355
|
+
if (normalized.match(/^zx\.\w+\(/)) return true;
|
|
342
356
|
return false;
|
|
343
357
|
}
|
|
344
358
|
function isSchemaVariable(call, varName) {
|
|
@@ -561,6 +575,17 @@ function findObjectOnlyMethods(file) {
|
|
|
561
575
|
}
|
|
562
576
|
return results;
|
|
563
577
|
}
|
|
578
|
+
function ensureZImport(file) {
|
|
579
|
+
const text = file.getFullText();
|
|
580
|
+
if (!/\bz\./.test(text)) return;
|
|
581
|
+
const hasZImport = file.getImportDeclarations().some((imp) => imp.getNamedImports().some((n) => n.getName() === "z"));
|
|
582
|
+
if (hasZImport) return;
|
|
583
|
+
if (/^\s*(?:const|let|var)\s+z\s*[=:]/m.test(text)) return;
|
|
584
|
+
file.addImportDeclaration({
|
|
585
|
+
moduleSpecifier: "zod/mini",
|
|
586
|
+
namedImports: [{ name: "z" }]
|
|
587
|
+
});
|
|
588
|
+
}
|
|
564
589
|
function transformImports(file) {
|
|
565
590
|
let count = 0;
|
|
566
591
|
const imports = file.getImportDeclarations();
|
|
@@ -708,7 +733,7 @@ function transformFile(file, typeChecker) {
|
|
|
708
733
|
let propertyAccessors = 0;
|
|
709
734
|
for (let i = 0; i < 10; i++) {
|
|
710
735
|
const cr = transformConstructorReplacements(file);
|
|
711
|
-
const w = transformWrappers(file);
|
|
736
|
+
const w = transformWrappers(file, typeChecker);
|
|
712
737
|
const c = transformChecks(file);
|
|
713
738
|
const m = transformMethods(file, typeChecker);
|
|
714
739
|
const pa = transformPropertyAccessors(file, typeChecker);
|
|
@@ -722,6 +747,7 @@ function transformFile(file, typeChecker) {
|
|
|
722
747
|
const classRefs = transformClassRefs(file);
|
|
723
748
|
const objectOnlyWarnings = findObjectOnlyMethods(file);
|
|
724
749
|
const propertyAccessWarnings = findInternalPropertyAccess(file, typeChecker);
|
|
750
|
+
ensureZImport(file);
|
|
725
751
|
const imports = 0;
|
|
726
752
|
return {
|
|
727
753
|
filePath,
|
|
@@ -1368,6 +1394,7 @@ function findCodec(schema) {
|
|
|
1368
1394
|
}
|
|
1369
1395
|
|
|
1370
1396
|
// src/public/codegen/discover.ts
|
|
1397
|
+
var discoveryRunCounter = 0;
|
|
1371
1398
|
function walkSchemaRecursive(schema, accessPath, visited, seenCodecs, results) {
|
|
1372
1399
|
if (visited.has(schema)) return;
|
|
1373
1400
|
visited.add(schema);
|
|
@@ -1505,7 +1532,7 @@ function walkFunctionCodecs(functions) {
|
|
|
1505
1532
|
}
|
|
1506
1533
|
return found;
|
|
1507
1534
|
}
|
|
1508
|
-
async function discoverModules(convexDir2) {
|
|
1535
|
+
async function discoverModules(convexDir2, options) {
|
|
1509
1536
|
const models = [];
|
|
1510
1537
|
const functions = [];
|
|
1511
1538
|
const codecs = [];
|
|
@@ -1528,13 +1555,19 @@ async function discoverModules(convexDir2) {
|
|
|
1528
1555
|
"crons.ts",
|
|
1529
1556
|
"crons.js"
|
|
1530
1557
|
]
|
|
1531
|
-
});
|
|
1558
|
+
}).sort();
|
|
1559
|
+
let cacheKey = null;
|
|
1560
|
+
if (options?.freshImports) {
|
|
1561
|
+
discoveryRunCounter += 1;
|
|
1562
|
+
cacheKey = `${process.pid}-${discoveryRunCounter}`;
|
|
1563
|
+
}
|
|
1532
1564
|
try {
|
|
1533
1565
|
for (const file of files) {
|
|
1534
1566
|
const absPath = path3.resolve(convexDir2, file);
|
|
1567
|
+
const importUrl = cacheKey ? `${pathToFileURL(absPath).href}?t=${cacheKey}` : absPath;
|
|
1535
1568
|
let moduleExports;
|
|
1536
1569
|
try {
|
|
1537
|
-
moduleExports = await import(
|
|
1570
|
+
moduleExports = await import(importUrl);
|
|
1538
1571
|
} catch (err) {
|
|
1539
1572
|
console.warn(`[zodvex] Warning: Failed to import ${file}:`, err.message);
|
|
1540
1573
|
continue;
|
|
@@ -1689,7 +1722,32 @@ var HEADER = `// AUTO-GENERATED by zodvex \u2014 do not edit
|
|
|
1689
1722
|
`;
|
|
1690
1723
|
function fingerprintCodec(schema) {
|
|
1691
1724
|
if (!(schema instanceof $ZodCodec)) return "";
|
|
1692
|
-
return `${
|
|
1725
|
+
return `${fingerprintLeaf(schema._zod.def.in)}|${fingerprintLeaf(schema._zod.def.out)}`;
|
|
1726
|
+
}
|
|
1727
|
+
function fingerprintLeaf(schema) {
|
|
1728
|
+
return `${zodToSource(schema)}#${fingerprintChecks(schema)}`;
|
|
1729
|
+
}
|
|
1730
|
+
function fingerprintChecks(schema) {
|
|
1731
|
+
const checks = schema?._zod?.def?.checks;
|
|
1732
|
+
if (!Array.isArray(checks) || checks.length === 0) return "";
|
|
1733
|
+
const parts = [];
|
|
1734
|
+
for (const check of checks) {
|
|
1735
|
+
const def = check?._zod?.def;
|
|
1736
|
+
if (!def) continue;
|
|
1737
|
+
const checkType = def.check ?? def.type ?? "check";
|
|
1738
|
+
const data = {};
|
|
1739
|
+
for (const [k, v7] of Object.entries(def)) {
|
|
1740
|
+
if (k === "check" || k === "type" || k === "error" || k === "message") continue;
|
|
1741
|
+
const t = typeof v7;
|
|
1742
|
+
if (t === "string" || t === "number" || t === "boolean" || v7 === null) {
|
|
1743
|
+
data[k] = v7;
|
|
1744
|
+
} else if (v7 instanceof RegExp) {
|
|
1745
|
+
data[k] = v7.source;
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
parts.push(`${checkType}(${JSON.stringify(data)})`);
|
|
1749
|
+
}
|
|
1750
|
+
return parts.sort().join("&");
|
|
1693
1751
|
}
|
|
1694
1752
|
function generateSchemaFile(models) {
|
|
1695
1753
|
const exports$1 = models.map((m) => {
|
|
@@ -1776,6 +1834,30 @@ function deriveCodecVarName(modelExportName, accessPath) {
|
|
|
1776
1834
|
return `_${prefix}${fieldPart[0].toUpperCase() + fieldPart.slice(1)}`;
|
|
1777
1835
|
}
|
|
1778
1836
|
function generateApiFile(functions, models, codecs, modelCodecs, functionCodecs, options) {
|
|
1837
|
+
const sortedModels = [...models].sort(
|
|
1838
|
+
(a, b) => `${a.sourceFile}|${a.exportName}`.localeCompare(`${b.sourceFile}|${b.exportName}`)
|
|
1839
|
+
);
|
|
1840
|
+
const sortedFunctions = [...functions].sort(
|
|
1841
|
+
(a, b) => a.functionPath.localeCompare(b.functionPath)
|
|
1842
|
+
);
|
|
1843
|
+
const sortedCodecs = codecs ? [...codecs].sort(
|
|
1844
|
+
(a, b) => `${a.sourceFile}|${a.exportName}`.localeCompare(`${b.sourceFile}|${b.exportName}`)
|
|
1845
|
+
) : void 0;
|
|
1846
|
+
const sortedModelCodecs = modelCodecs ? [...modelCodecs].sort(
|
|
1847
|
+
(a, b) => `${a.modelSourceFile}|${a.modelExportName}|${a.schemaKey}|${a.accessPath}`.localeCompare(
|
|
1848
|
+
`${b.modelSourceFile}|${b.modelExportName}|${b.schemaKey}|${b.accessPath}`
|
|
1849
|
+
)
|
|
1850
|
+
) : void 0;
|
|
1851
|
+
const sortedFunctionCodecs = functionCodecs ? [...functionCodecs].sort(
|
|
1852
|
+
(a, b) => `${a.functionSourceFile}|${a.functionExportName}|${a.schemaSource}|${a.accessPath}`.localeCompare(
|
|
1853
|
+
`${b.functionSourceFile}|${b.functionExportName}|${b.schemaSource}|${b.accessPath}`
|
|
1854
|
+
)
|
|
1855
|
+
) : void 0;
|
|
1856
|
+
models = sortedModels;
|
|
1857
|
+
functions = sortedFunctions;
|
|
1858
|
+
codecs = sortedCodecs;
|
|
1859
|
+
modelCodecs = sortedModelCodecs;
|
|
1860
|
+
functionCodecs = sortedFunctionCodecs;
|
|
1779
1861
|
const identityMap = /* @__PURE__ */ new Map();
|
|
1780
1862
|
const neededModelImports = /* @__PURE__ */ new Set();
|
|
1781
1863
|
for (const model of models) {
|
|
@@ -1859,20 +1941,49 @@ function generateApiFile(functions, models, codecs, modelCodecs, functionCodecs,
|
|
|
1859
1941
|
}
|
|
1860
1942
|
if (functionCodecs) {
|
|
1861
1943
|
const fingerprintMap = /* @__PURE__ */ new Map();
|
|
1944
|
+
const codecSchemaToSourceFile = /* @__PURE__ */ new Map();
|
|
1945
|
+
for (const c of codecs ?? []) {
|
|
1946
|
+
codecSchemaToSourceFile.set(c.schema, c.sourceFile);
|
|
1947
|
+
}
|
|
1948
|
+
for (const mc of modelCodecs ?? []) {
|
|
1949
|
+
codecSchemaToSourceFile.set(mc.codec, mc.modelSourceFile);
|
|
1950
|
+
}
|
|
1862
1951
|
for (const [codecSchema, ref] of codecMap) {
|
|
1863
1952
|
const fp = fingerprintCodec(codecSchema);
|
|
1864
|
-
if (fp)
|
|
1953
|
+
if (!fp) continue;
|
|
1954
|
+
const sourceFile = codecSchemaToSourceFile.get(codecSchema);
|
|
1955
|
+
const existing = fingerprintMap.get(fp);
|
|
1956
|
+
if (existing) {
|
|
1957
|
+
existing.push({ ref, sourceFile });
|
|
1958
|
+
} else {
|
|
1959
|
+
fingerprintMap.set(fp, [{ ref, sourceFile }]);
|
|
1960
|
+
}
|
|
1865
1961
|
}
|
|
1866
1962
|
for (const fc of functionCodecs) {
|
|
1867
1963
|
if (codecMap.has(fc.codec)) continue;
|
|
1868
1964
|
const fp = fingerprintCodec(fc.codec);
|
|
1869
|
-
const
|
|
1870
|
-
if (
|
|
1871
|
-
codecMap.set(fc.codec, matchingRef);
|
|
1872
|
-
} else {
|
|
1965
|
+
const candidates = fp ? fingerprintMap.get(fp) : void 0;
|
|
1966
|
+
if (!candidates || candidates.length === 0) {
|
|
1873
1967
|
console.warn(
|
|
1874
1968
|
`[zodvex] Warning: Codec in ${fc.functionExportName}() (${fc.accessPath}) has no matching model or exported codec. Export it standalone for full client-side codec support.`
|
|
1875
1969
|
);
|
|
1970
|
+
continue;
|
|
1971
|
+
}
|
|
1972
|
+
let chosen;
|
|
1973
|
+
if (candidates.length === 1) {
|
|
1974
|
+
chosen = candidates[0].ref;
|
|
1975
|
+
} else {
|
|
1976
|
+
const sameFile = candidates.filter((c) => c.sourceFile === fc.functionSourceFile);
|
|
1977
|
+
if (sameFile.length === 1) {
|
|
1978
|
+
chosen = sameFile[0].ref;
|
|
1979
|
+
} else {
|
|
1980
|
+
console.warn(
|
|
1981
|
+
`[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.`
|
|
1982
|
+
);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
if (chosen) {
|
|
1986
|
+
codecMap.set(fc.codec, chosen);
|
|
1876
1987
|
}
|
|
1877
1988
|
}
|
|
1878
1989
|
}
|
|
@@ -1932,15 +2043,17 @@ function generateApiFile(functions, models, codecs, modelCodecs, functionCodecs,
|
|
|
1932
2043
|
if (coreImports.length > 0) {
|
|
1933
2044
|
imports.push(`import { ${coreImports.join(", ")} } from '${zodvexImport}'`);
|
|
1934
2045
|
}
|
|
1935
|
-
for (const exportName of neededModelImports) {
|
|
2046
|
+
for (const exportName of [...neededModelImports].sort()) {
|
|
1936
2047
|
const model = models.find((m) => m.exportName === exportName);
|
|
1937
2048
|
if (model) {
|
|
1938
2049
|
const importPath = `../${model.sourceFile.replace(/\.ts$/, ".js")}`;
|
|
1939
2050
|
imports.push(`import { ${exportName} } from '${importPath}'`);
|
|
1940
2051
|
}
|
|
1941
2052
|
}
|
|
1942
|
-
|
|
2053
|
+
const sortedCodecImportPaths = [...zodToSourceCtx.neededCodecImports.keys()].sort();
|
|
2054
|
+
for (const importPath of sortedCodecImportPaths) {
|
|
1943
2055
|
if (importPath === MODEL_CODEC_SENTINEL) continue;
|
|
2056
|
+
const exportNames = zodToSourceCtx.neededCodecImports.get(importPath) ?? /* @__PURE__ */ new Set();
|
|
1944
2057
|
const names = Array.from(exportNames).sort().join(", ");
|
|
1945
2058
|
imports.push(`import { ${names} } from '${importPath}'`);
|
|
1946
2059
|
}
|
|
@@ -2043,7 +2156,7 @@ async function generate(convexDir2, options) {
|
|
|
2043
2156
|
const resolved = resolveConvexDir(convexDir2);
|
|
2044
2157
|
const zodvexDir = path3.join(resolved, "_zodvex");
|
|
2045
2158
|
writeStubApi(zodvexDir);
|
|
2046
|
-
const result = await discoverModules(resolved);
|
|
2159
|
+
const result = await discoverModules(resolved, { freshImports: options?.freshImports });
|
|
2047
2160
|
const schemaContent = generateSchemaFile(result.models);
|
|
2048
2161
|
const apiContent = generateApiFile(
|
|
2049
2162
|
result.functions,
|
|
@@ -2083,7 +2196,7 @@ async function dev(convexDir2, options) {
|
|
|
2083
2196
|
debounceTimer = setTimeout(async () => {
|
|
2084
2197
|
console.log("[zodvex] Regenerating...");
|
|
2085
2198
|
try {
|
|
2086
|
-
await generate(resolved, options);
|
|
2199
|
+
await generate(resolved, { ...options, freshImports: true });
|
|
2087
2200
|
} catch (err) {
|
|
2088
2201
|
console.error("[zodvex] Generation failed:", err.message);
|
|
2089
2202
|
}
|