zodvex 0.7.7-beta.1 → 0.7.7
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 +296 -81
- package/dist/cli/index.js.map +1 -1
- package/dist/client/index.js +2 -2
- package/dist/client/index.js.map +1 -1
- package/dist/codegen/index.js +211 -28
- package/dist/codegen/index.js.map +1 -1
- package/dist/core/index.js +33 -16
- package/dist/core/index.js.map +1 -1
- package/dist/index.js +33 -16
- package/dist/index.js.map +1 -1
- package/dist/internal/boundaryHelpers.d.ts +2 -15
- package/dist/internal/boundaryHelpers.d.ts.map +1 -1
- package/dist/internal/registry.d.ts.map +1 -1
- package/dist/internal/zx.d.ts.map +1 -1
- package/dist/internal/zxDateBrand.d.ts +5 -0
- package/dist/internal/zxDateBrand.d.ts.map +1 -0
- package/dist/legacy/index.js +21 -10
- package/dist/legacy/index.js.map +1 -1
- package/dist/mini/client/index.js +2 -2
- package/dist/mini/client/index.js.map +1 -1
- package/dist/mini/index.js +33 -16
- package/dist/mini/index.js.map +1 -1
- package/dist/mini/react/index.js +2 -2
- package/dist/mini/react/index.js.map +1 -1
- package/dist/mini/server/index.js +23 -12
- package/dist/mini/server/index.js.map +1 -1
- package/dist/public/cli/commands.d.ts.map +1 -1
- package/dist/public/codegen/discover.d.ts.map +1 -1
- package/dist/public/codegen/discovery-hooks.d.ts +2 -1
- package/dist/public/codegen/discovery-hooks.d.ts.map +1 -1
- package/dist/public/codegen/extractCodec.d.ts.map +1 -1
- package/dist/public/codegen/tsconfigPaths.d.ts +41 -0
- package/dist/public/codegen/tsconfigPaths.d.ts.map +1 -0
- package/dist/public/codegen/zodToSource.d.ts.map +1 -1
- package/dist/react/index.js +2 -2
- package/dist/react/index.js.map +1 -1
- package/dist/server/index.js +23 -12
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
- package/src/internal/boundaryHelpers.ts +15 -3
- package/src/internal/registry.ts +12 -9
- package/src/internal/zx.ts +12 -9
- package/src/internal/zxDateBrand.ts +34 -0
- package/src/public/cli/commands.ts +59 -17
- package/src/public/codegen/discover.ts +27 -15
- package/src/public/codegen/discovery-hooks.ts +42 -3
- package/src/public/codegen/extractCodec.ts +5 -11
- package/src/public/codegen/tsconfigPaths.ts +224 -0
- package/src/public/codegen/zodToSource.ts +5 -6
package/dist/cli/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import fs3, { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
3
|
+
import path4, { resolve, relative, join } from 'path';
|
|
4
4
|
import { Project, SyntaxKind } from 'ts-morph';
|
|
5
5
|
import { globSync } from 'tinyglobby';
|
|
6
6
|
import { spawn } from 'child_process';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import { z } from 'zod';
|
|
9
|
-
import { $ZodCodec, $
|
|
9
|
+
import { $ZodCodec, $ZodType, $ZodOptional, $ZodNullable, $ZodObject, $ZodUnion, $ZodArray, $ZodRecord, $ZodTuple, clone, $ZodString, $ZodNumber, $ZodBoolean, $ZodNull, $ZodUndefined, $ZodAny, $ZodEnum, $ZodLiteral, $ZodDiscriminatedUnion } from 'zod/v4/core';
|
|
10
10
|
import 'convex/values';
|
|
11
11
|
|
|
12
12
|
var __defProp = Object.defineProperty;
|
|
@@ -62,12 +62,12 @@ function gitignoreEntry(content) {
|
|
|
62
62
|
return lines.join("\n");
|
|
63
63
|
}
|
|
64
64
|
function generateStubs(convexDir2) {
|
|
65
|
-
const zodvexDir =
|
|
66
|
-
|
|
65
|
+
const zodvexDir = path4.join(convexDir2, "_zodvex");
|
|
66
|
+
fs3.mkdirSync(zodvexDir, { recursive: true });
|
|
67
67
|
const apiStub = `// Auto-generated stub. Run \`zodvex generate\` to populate.
|
|
68
68
|
export const zodvexRegistry = {} as const
|
|
69
69
|
`;
|
|
70
|
-
|
|
70
|
+
fs3.writeFileSync(path4.join(zodvexDir, "api.ts"), apiStub);
|
|
71
71
|
const clientStub = `// Auto-generated stub. Run \`zodvex generate\` to populate.
|
|
72
72
|
import { zodvexRegistry } from './api'
|
|
73
73
|
|
|
@@ -75,11 +75,11 @@ export const useZodQuery = undefined as any
|
|
|
75
75
|
export const useZodMutation = undefined as any
|
|
76
76
|
export const createClient = undefined as any
|
|
77
77
|
`;
|
|
78
|
-
|
|
78
|
+
fs3.writeFileSync(path4.join(zodvexDir, "client.ts"), clientStub);
|
|
79
79
|
}
|
|
80
80
|
async function init() {
|
|
81
|
-
const convexDir2 =
|
|
82
|
-
if (!
|
|
81
|
+
const convexDir2 = path4.resolve("convex");
|
|
82
|
+
if (!fs3.existsSync(convexDir2)) {
|
|
83
83
|
console.error("[zodvex] No convex/ directory found. Run this from your project root.");
|
|
84
84
|
return;
|
|
85
85
|
}
|
|
@@ -100,14 +100,14 @@ __export(migrate_exports, {
|
|
|
100
100
|
function collectFiles(dir) {
|
|
101
101
|
const results = [];
|
|
102
102
|
function walk(current) {
|
|
103
|
-
const entries =
|
|
103
|
+
const entries = fs3.readdirSync(current, { withFileTypes: true });
|
|
104
104
|
for (const entry of entries) {
|
|
105
105
|
if (entry.isDirectory()) {
|
|
106
106
|
if (!SKIP_DIRS.has(entry.name)) {
|
|
107
|
-
walk(
|
|
107
|
+
walk(path4.join(current, entry.name));
|
|
108
108
|
}
|
|
109
|
-
} else if (entry.isFile() && TS_EXTENSIONS.has(
|
|
110
|
-
results.push(
|
|
109
|
+
} else if (entry.isFile() && TS_EXTENSIONS.has(path4.extname(entry.name))) {
|
|
110
|
+
results.push(path4.join(current, entry.name));
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
}
|
|
@@ -204,7 +204,7 @@ function migrate(dir, options) {
|
|
|
204
204
|
let wouldChange = 0;
|
|
205
205
|
const allDeprecations = [];
|
|
206
206
|
for (const filePath of files) {
|
|
207
|
-
const original =
|
|
207
|
+
const original = fs3.readFileSync(filePath, "utf-8");
|
|
208
208
|
let content = original;
|
|
209
209
|
content = applyIdentifierRenames(content);
|
|
210
210
|
content = applyZidTransform(content);
|
|
@@ -214,7 +214,7 @@ function migrate(dir, options) {
|
|
|
214
214
|
if (options.dryRun) {
|
|
215
215
|
wouldChange++;
|
|
216
216
|
} else {
|
|
217
|
-
|
|
217
|
+
fs3.writeFileSync(filePath, content);
|
|
218
218
|
filesChanged++;
|
|
219
219
|
}
|
|
220
220
|
}
|
|
@@ -1054,18 +1054,34 @@ function zodvexCodec(wire, runtime, transforms) {
|
|
|
1054
1054
|
return z.codec(wire, runtime, transforms);
|
|
1055
1055
|
}
|
|
1056
1056
|
|
|
1057
|
+
// src/internal/zxDateBrand.ts
|
|
1058
|
+
var ZX_DATE_BRAND = /* @__PURE__ */ Symbol.for("zodvex.zxDate");
|
|
1059
|
+
function brandZxDate(codec2) {
|
|
1060
|
+
codec2[ZX_DATE_BRAND] = true;
|
|
1061
|
+
const def = codec2._zod?.def;
|
|
1062
|
+
if (def) def[ZX_DATE_BRAND] = true;
|
|
1063
|
+
return codec2;
|
|
1064
|
+
}
|
|
1065
|
+
function isZxDateCodec(schema) {
|
|
1066
|
+
if (!schema || typeof schema !== "object") return false;
|
|
1067
|
+
const s = schema;
|
|
1068
|
+
return s[ZX_DATE_BRAND] === true || s._zod?.def?.[ZX_DATE_BRAND] === true;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1057
1071
|
// src/internal/zx.ts
|
|
1058
1072
|
function date() {
|
|
1059
|
-
return
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1073
|
+
return brandZxDate(
|
|
1074
|
+
zodvexCodec(
|
|
1075
|
+
z.number(),
|
|
1076
|
+
// Wire: timestamp
|
|
1077
|
+
z.custom((val) => val instanceof Date, {
|
|
1078
|
+
message: "Expected Date instance"
|
|
1079
|
+
}),
|
|
1080
|
+
{
|
|
1081
|
+
decode: (timestamp) => new Date(timestamp),
|
|
1082
|
+
encode: (date2) => date2.getTime()
|
|
1083
|
+
}
|
|
1084
|
+
)
|
|
1069
1085
|
);
|
|
1070
1086
|
}
|
|
1071
1087
|
function id(tableName) {
|
|
@@ -1304,9 +1320,35 @@ function createSchemaUpdateSchema(name, inputSchema) {
|
|
|
1304
1320
|
}
|
|
1305
1321
|
return inputSchema;
|
|
1306
1322
|
}
|
|
1307
|
-
|
|
1323
|
+
function buildHooksSource(aliases) {
|
|
1324
|
+
return `
|
|
1325
|
+
import { pathToFileURL } from 'node:url';
|
|
1326
|
+
|
|
1308
1327
|
const EXT_CANDIDATES = ['.ts', '.tsx', '.mts', '.js', '.mjs', '.jsx', '/index.ts', '/index.js'];
|
|
1309
1328
|
|
|
1329
|
+
// tsconfig path aliases compiled by loadTsconfigAliases (#99). Bun resolves
|
|
1330
|
+
// these natively; Node's ESM loader needs them replayed here.
|
|
1331
|
+
const ALIASES = ${JSON.stringify(aliases)};
|
|
1332
|
+
|
|
1333
|
+
function aliasCandidates(specifier) {
|
|
1334
|
+
const out = [];
|
|
1335
|
+
for (const a of ALIASES) {
|
|
1336
|
+
if (a.star) {
|
|
1337
|
+
if (
|
|
1338
|
+
specifier.length >= a.prefix.length + a.suffix.length &&
|
|
1339
|
+
specifier.startsWith(a.prefix) &&
|
|
1340
|
+
specifier.endsWith(a.suffix)
|
|
1341
|
+
) {
|
|
1342
|
+
const captured = specifier.slice(a.prefix.length, specifier.length - a.suffix.length);
|
|
1343
|
+
for (const t of a.targets) out.push(t.prefix + captured + t.suffix);
|
|
1344
|
+
}
|
|
1345
|
+
} else if (specifier === a.prefix) {
|
|
1346
|
+
for (const t of a.targets) out.push(t.prefix);
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
return out;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1310
1352
|
export async function resolve(specifier, context, nextResolve) {
|
|
1311
1353
|
if (/_generated\\/api(\\.[mc]?[jt]sx?)?$/.test(specifier)) {
|
|
1312
1354
|
return { shortCircuit: true, url: 'zodvex-stub://api' };
|
|
@@ -1314,6 +1356,15 @@ export async function resolve(specifier, context, nextResolve) {
|
|
|
1314
1356
|
try {
|
|
1315
1357
|
return await nextResolve(specifier, context);
|
|
1316
1358
|
} catch (err) {
|
|
1359
|
+
// tsconfig path aliases (e.g. '@/convex/...'). Try each mapped absolute
|
|
1360
|
+
// path as-is, then with the usual extension/index candidates.
|
|
1361
|
+
for (const base of aliasCandidates(specifier)) {
|
|
1362
|
+
for (const ext of ['', ...EXT_CANDIDATES]) {
|
|
1363
|
+
try {
|
|
1364
|
+
return await nextResolve(pathToFileURL(base + ext).href, context);
|
|
1365
|
+
} catch {}
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1317
1368
|
// Convex code uses extensionless relative imports (bundler resolution).
|
|
1318
1369
|
// Bun resolves those natively; Node's ESM loader does not \u2014 retry with
|
|
1319
1370
|
// the usual extension candidates before giving up.
|
|
@@ -1358,8 +1409,9 @@ export function load(url, context, nextLoad) {
|
|
|
1358
1409
|
return nextLoad(url, context);
|
|
1359
1410
|
}
|
|
1360
1411
|
`;
|
|
1412
|
+
}
|
|
1361
1413
|
var hooksRegistered = false;
|
|
1362
|
-
function registerDiscoveryHooks() {
|
|
1414
|
+
function registerDiscoveryHooks(aliases = []) {
|
|
1363
1415
|
if (hooksRegistered) return true;
|
|
1364
1416
|
try {
|
|
1365
1417
|
const nodeModule = typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("node:module") : (
|
|
@@ -1368,7 +1420,7 @@ function registerDiscoveryHooks() {
|
|
|
1368
1420
|
);
|
|
1369
1421
|
const { register } = nodeModule;
|
|
1370
1422
|
if (typeof register !== "function") return false;
|
|
1371
|
-
register(`data:text/javascript,${encodeURIComponent(
|
|
1423
|
+
register(`data:text/javascript,${encodeURIComponent(buildHooksSource(aliases))}`);
|
|
1372
1424
|
hooksRegistered = true;
|
|
1373
1425
|
return true;
|
|
1374
1426
|
} catch {
|
|
@@ -1393,22 +1445,22 @@ export const components = p;
|
|
|
1393
1445
|
export const httpRouter = p;
|
|
1394
1446
|
`;
|
|
1395
1447
|
function writeGeneratedStubs(convexDir2) {
|
|
1396
|
-
const generatedDir =
|
|
1397
|
-
const apiPath =
|
|
1448
|
+
const generatedDir = path4.join(convexDir2, "_generated");
|
|
1449
|
+
const apiPath = path4.join(generatedDir, "api.ts");
|
|
1398
1450
|
let original;
|
|
1399
1451
|
try {
|
|
1400
|
-
original =
|
|
1452
|
+
original = fs3.readFileSync(apiPath, "utf8");
|
|
1401
1453
|
} catch {
|
|
1402
1454
|
original = null;
|
|
1403
1455
|
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1456
|
+
fs3.mkdirSync(generatedDir, { recursive: true });
|
|
1457
|
+
fs3.writeFileSync(apiPath, PROXY_STUB_API);
|
|
1406
1458
|
return () => {
|
|
1407
1459
|
if (original !== null) {
|
|
1408
|
-
|
|
1460
|
+
fs3.writeFileSync(apiPath, original);
|
|
1409
1461
|
} else {
|
|
1410
1462
|
try {
|
|
1411
|
-
|
|
1463
|
+
fs3.unlinkSync(apiPath);
|
|
1412
1464
|
} catch {
|
|
1413
1465
|
}
|
|
1414
1466
|
}
|
|
@@ -1420,8 +1472,7 @@ function findCodec(schema) {
|
|
|
1420
1472
|
let current = schema;
|
|
1421
1473
|
for (let i = 0; i < 10; i++) {
|
|
1422
1474
|
if (current instanceof $ZodCodec) {
|
|
1423
|
-
|
|
1424
|
-
if (isZxDate) return void 0;
|
|
1475
|
+
if (isZxDateCodec(current)) return void 0;
|
|
1425
1476
|
return current;
|
|
1426
1477
|
}
|
|
1427
1478
|
if (current instanceof $ZodOptional || current instanceof $ZodNullable) {
|
|
@@ -1432,6 +1483,134 @@ function findCodec(schema) {
|
|
|
1432
1483
|
}
|
|
1433
1484
|
return void 0;
|
|
1434
1485
|
}
|
|
1486
|
+
function stripJsonComments(input) {
|
|
1487
|
+
let out = "";
|
|
1488
|
+
let inString = false;
|
|
1489
|
+
let inLine = false;
|
|
1490
|
+
let inBlock = false;
|
|
1491
|
+
for (let i = 0; i < input.length; i++) {
|
|
1492
|
+
const ch = input[i];
|
|
1493
|
+
const next = input[i + 1];
|
|
1494
|
+
if (inLine) {
|
|
1495
|
+
if (ch === "\n") {
|
|
1496
|
+
inLine = false;
|
|
1497
|
+
out += ch;
|
|
1498
|
+
}
|
|
1499
|
+
continue;
|
|
1500
|
+
}
|
|
1501
|
+
if (inBlock) {
|
|
1502
|
+
if (ch === "*" && next === "/") {
|
|
1503
|
+
inBlock = false;
|
|
1504
|
+
i++;
|
|
1505
|
+
}
|
|
1506
|
+
continue;
|
|
1507
|
+
}
|
|
1508
|
+
if (inString) {
|
|
1509
|
+
out += ch;
|
|
1510
|
+
if (ch === "\\") {
|
|
1511
|
+
out += next ?? "";
|
|
1512
|
+
i++;
|
|
1513
|
+
} else if (ch === '"') {
|
|
1514
|
+
inString = false;
|
|
1515
|
+
}
|
|
1516
|
+
continue;
|
|
1517
|
+
}
|
|
1518
|
+
if (ch === '"') {
|
|
1519
|
+
inString = true;
|
|
1520
|
+
out += ch;
|
|
1521
|
+
continue;
|
|
1522
|
+
}
|
|
1523
|
+
if (ch === "/" && next === "/") {
|
|
1524
|
+
inLine = true;
|
|
1525
|
+
i++;
|
|
1526
|
+
continue;
|
|
1527
|
+
}
|
|
1528
|
+
if (ch === "/" && next === "*") {
|
|
1529
|
+
inBlock = true;
|
|
1530
|
+
i++;
|
|
1531
|
+
continue;
|
|
1532
|
+
}
|
|
1533
|
+
out += ch;
|
|
1534
|
+
}
|
|
1535
|
+
return out.replace(/,\s*([}\]])/g, "$1");
|
|
1536
|
+
}
|
|
1537
|
+
function readTsconfig(configPath) {
|
|
1538
|
+
try {
|
|
1539
|
+
const raw = fs3.readFileSync(configPath, "utf8");
|
|
1540
|
+
return JSON.parse(stripJsonComments(raw));
|
|
1541
|
+
} catch {
|
|
1542
|
+
return null;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
function resolvePathsFromConfig(configPath, seen) {
|
|
1546
|
+
const abs = path4.resolve(configPath);
|
|
1547
|
+
if (seen.has(abs)) return null;
|
|
1548
|
+
seen.add(abs);
|
|
1549
|
+
const config = readTsconfig(abs);
|
|
1550
|
+
if (!config) return null;
|
|
1551
|
+
const dir = path4.dirname(abs);
|
|
1552
|
+
const own = config.compilerOptions?.paths;
|
|
1553
|
+
if (own) {
|
|
1554
|
+
const baseUrl = config.compilerOptions?.baseUrl;
|
|
1555
|
+
return { paths: own, baseDir: baseUrl ? path4.resolve(dir, baseUrl) : dir };
|
|
1556
|
+
}
|
|
1557
|
+
const parents = config.extends == null ? [] : [].concat(config.extends);
|
|
1558
|
+
for (const ref of parents) {
|
|
1559
|
+
if (!ref.startsWith(".") && !path4.isAbsolute(ref)) continue;
|
|
1560
|
+
let target = path4.resolve(dir, ref);
|
|
1561
|
+
if (!target.endsWith(".json")) target += ".json";
|
|
1562
|
+
const found = resolvePathsFromConfig(target, seen);
|
|
1563
|
+
if (found) {
|
|
1564
|
+
const childBase = config.compilerOptions?.baseUrl;
|
|
1565
|
+
return childBase ? { paths: found.paths, baseDir: path4.resolve(dir, childBase) } : found;
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
return null;
|
|
1569
|
+
}
|
|
1570
|
+
function compileAliases(paths, baseDir) {
|
|
1571
|
+
const entries = [];
|
|
1572
|
+
for (const [pattern, targets] of Object.entries(paths)) {
|
|
1573
|
+
const starIdx = pattern.indexOf("*");
|
|
1574
|
+
const star = starIdx !== -1;
|
|
1575
|
+
const prefix = star ? pattern.slice(0, starIdx) : pattern;
|
|
1576
|
+
const suffix = star ? pattern.slice(starIdx + 1) : "";
|
|
1577
|
+
const compiled = targets.map((t) => {
|
|
1578
|
+
const tStar = t.indexOf("*");
|
|
1579
|
+
const tPrefix = path4.resolve(baseDir, tStar === -1 ? t : t.slice(0, tStar));
|
|
1580
|
+
const sep = tStar !== -1 && t.slice(0, tStar).endsWith("/") ? path4.sep : "";
|
|
1581
|
+
return {
|
|
1582
|
+
prefix: tPrefix + sep,
|
|
1583
|
+
suffix: tStar === -1 ? "" : t.slice(tStar + 1)
|
|
1584
|
+
};
|
|
1585
|
+
});
|
|
1586
|
+
entries.push({ prefix, suffix, star, targets: compiled });
|
|
1587
|
+
}
|
|
1588
|
+
return entries;
|
|
1589
|
+
}
|
|
1590
|
+
function loadTsconfigAliases(convexDir2) {
|
|
1591
|
+
const entries = [];
|
|
1592
|
+
const seenPatterns = /* @__PURE__ */ new Set();
|
|
1593
|
+
let dir = path4.resolve(convexDir2);
|
|
1594
|
+
for (let depth = 0; depth < 10; depth++) {
|
|
1595
|
+
const configPath = path4.join(dir, "tsconfig.json");
|
|
1596
|
+
if (fs3.existsSync(configPath)) {
|
|
1597
|
+
const found = resolvePathsFromConfig(configPath, /* @__PURE__ */ new Set());
|
|
1598
|
+
if (found) {
|
|
1599
|
+
for (const entry of compileAliases(found.paths, found.baseDir)) {
|
|
1600
|
+
const key = `${entry.prefix}*${entry.suffix}`;
|
|
1601
|
+
if (!seenPatterns.has(key)) {
|
|
1602
|
+
seenPatterns.add(key);
|
|
1603
|
+
entries.push(entry);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
const parent = path4.dirname(dir);
|
|
1609
|
+
if (parent === dir) break;
|
|
1610
|
+
dir = parent;
|
|
1611
|
+
}
|
|
1612
|
+
return entries;
|
|
1613
|
+
}
|
|
1435
1614
|
|
|
1436
1615
|
// src/public/codegen/discover.ts
|
|
1437
1616
|
function walkSchemaRecursive(schema, accessPath, visited, seenCodecs, results) {
|
|
@@ -1575,7 +1754,7 @@ async function discoverModules(convexDir2) {
|
|
|
1575
1754
|
const models = [];
|
|
1576
1755
|
const functions = [];
|
|
1577
1756
|
const codecs = [];
|
|
1578
|
-
registerDiscoveryHooks();
|
|
1757
|
+
registerDiscoveryHooks(loadTsconfigAliases(convexDir2));
|
|
1579
1758
|
const cleanupStubs = writeGeneratedStubs(convexDir2);
|
|
1580
1759
|
const files = globSync(["**/*.{ts,js}"], {
|
|
1581
1760
|
cwd: convexDir2,
|
|
@@ -1595,15 +1774,15 @@ async function discoverModules(convexDir2) {
|
|
|
1595
1774
|
"crons.js"
|
|
1596
1775
|
]
|
|
1597
1776
|
}).sort();
|
|
1598
|
-
|
|
1777
|
+
const failedImports = [];
|
|
1599
1778
|
try {
|
|
1600
1779
|
for (const file of files) {
|
|
1601
|
-
const absPath =
|
|
1780
|
+
const absPath = path4.resolve(convexDir2, file);
|
|
1602
1781
|
let moduleExports;
|
|
1603
1782
|
try {
|
|
1604
1783
|
moduleExports = await import(absPath);
|
|
1605
1784
|
} catch (err) {
|
|
1606
|
-
|
|
1785
|
+
failedImports.push({ file, message: err.message });
|
|
1607
1786
|
console.warn(`[zodvex] Warning: Failed to import ${file}:`, err.message);
|
|
1608
1787
|
continue;
|
|
1609
1788
|
}
|
|
@@ -1647,8 +1826,7 @@ async function discoverModules(convexDir2) {
|
|
|
1647
1826
|
}
|
|
1648
1827
|
}
|
|
1649
1828
|
if (value instanceof $ZodCodec) {
|
|
1650
|
-
|
|
1651
|
-
if (!isZxDate) {
|
|
1829
|
+
if (!isZxDateCodec(value)) {
|
|
1652
1830
|
if (!codecs.some((c) => c.schema === value)) {
|
|
1653
1831
|
codecs.push({
|
|
1654
1832
|
exportName,
|
|
@@ -1671,9 +1849,14 @@ async function discoverModules(convexDir2) {
|
|
|
1671
1849
|
modelCodecs.push(...found);
|
|
1672
1850
|
}
|
|
1673
1851
|
const functionCodecs = walkFunctionCodecs(functions);
|
|
1674
|
-
if (
|
|
1852
|
+
if (failedImports.length > 0 && !process.env.ZODVEX_ALLOW_IMPORT_FAILURES) {
|
|
1853
|
+
const shown = failedImports.slice(0, 10).map((f) => ` - ${f.file}: ${f.message}`).join("\n");
|
|
1854
|
+
const more = failedImports.length > 10 ? `
|
|
1855
|
+
\u2026 and ${failedImports.length - 10} more` : "";
|
|
1675
1856
|
throw new Error(
|
|
1676
|
-
`[zodvex]
|
|
1857
|
+
`[zodvex] ${failedImports.length} module(s) failed to import during discovery \u2014 the generated registry would be incomplete:
|
|
1858
|
+
${shown}${more}
|
|
1859
|
+
Common causes: Node < 22.18 (no native TypeScript type stripping \u2014 use Bun or upgrade Node), or an import style the discovery resolver does not handle. tsconfig path aliases and extensionless relative imports are supported. Set ZODVEX_ALLOW_IMPORT_FAILURES=1 to generate anyway (skipping the failed modules).`
|
|
1677
1860
|
);
|
|
1678
1861
|
}
|
|
1679
1862
|
return { models, functions, codecs, modelCodecs, functionCodecs };
|
|
@@ -1698,7 +1881,7 @@ function zodToSource(schema, ctx) {
|
|
|
1698
1881
|
return `zx.id("${tableName}")`;
|
|
1699
1882
|
}
|
|
1700
1883
|
}
|
|
1701
|
-
if (schema instanceof $ZodCodec && schema
|
|
1884
|
+
if (schema instanceof $ZodCodec && isZxDateCodec(schema)) {
|
|
1702
1885
|
return "zx.date()";
|
|
1703
1886
|
}
|
|
1704
1887
|
if (schema instanceof $ZodCodec) {
|
|
@@ -2221,29 +2404,39 @@ ${dtsDeclarations.join("\n")}
|
|
|
2221
2404
|
// src/public/cli/commands.ts
|
|
2222
2405
|
async function generate(convexDir2, options) {
|
|
2223
2406
|
const resolved = resolveConvexDir(convexDir2);
|
|
2224
|
-
const zodvexDir =
|
|
2225
|
-
writeStubApi(zodvexDir);
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
result
|
|
2233
|
-
result.
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2407
|
+
const zodvexDir = path4.join(resolved, "_zodvex");
|
|
2408
|
+
const restoreStubbedApi = writeStubApi(zodvexDir);
|
|
2409
|
+
let result;
|
|
2410
|
+
let schemaContent;
|
|
2411
|
+
let apiContent;
|
|
2412
|
+
let clientContent;
|
|
2413
|
+
let serverContent;
|
|
2414
|
+
try {
|
|
2415
|
+
result = await discoverModules(resolved);
|
|
2416
|
+
schemaContent = generateSchemaFile(result.models);
|
|
2417
|
+
apiContent = generateApiFile(
|
|
2418
|
+
result.functions,
|
|
2419
|
+
result.models,
|
|
2420
|
+
result.codecs,
|
|
2421
|
+
result.modelCodecs,
|
|
2422
|
+
result.functionCodecs,
|
|
2423
|
+
{ mini: options?.mini }
|
|
2424
|
+
);
|
|
2425
|
+
clientContent = generateClientFile({ mini: options?.mini });
|
|
2426
|
+
serverContent = generateServerFile();
|
|
2427
|
+
} catch (err) {
|
|
2428
|
+
restoreStubbedApi();
|
|
2429
|
+
throw err;
|
|
2430
|
+
}
|
|
2431
|
+
fs3.mkdirSync(zodvexDir, { recursive: true });
|
|
2432
|
+
writeIfChanged(path4.join(zodvexDir, "schema.js"), schemaContent.js);
|
|
2433
|
+
writeIfChanged(path4.join(zodvexDir, "schema.d.ts"), schemaContent.dts);
|
|
2434
|
+
writeIfChanged(path4.join(zodvexDir, "api.js"), apiContent.js);
|
|
2435
|
+
writeIfChanged(path4.join(zodvexDir, "api.d.ts"), apiContent.dts);
|
|
2436
|
+
writeIfChanged(path4.join(zodvexDir, "client.js"), clientContent.js);
|
|
2437
|
+
writeIfChanged(path4.join(zodvexDir, "client.d.ts"), clientContent.dts);
|
|
2438
|
+
writeIfChanged(path4.join(zodvexDir, "server.js"), serverContent.js);
|
|
2439
|
+
writeIfChanged(path4.join(zodvexDir, "server.d.ts"), serverContent.dts);
|
|
2247
2440
|
const totalCodecs = result.codecs.length + result.modelCodecs.length + result.functionCodecs.length;
|
|
2248
2441
|
console.log(
|
|
2249
2442
|
`[zodvex] Generated ${result.models.length} model(s), ${result.functions.length} function(s), ${totalCodecs} codec(s)`
|
|
@@ -2254,7 +2447,7 @@ async function dev(convexDir2, options) {
|
|
|
2254
2447
|
console.log("[zodvex] Starting watch mode...");
|
|
2255
2448
|
await generate(resolved, options);
|
|
2256
2449
|
let debounceTimer = null;
|
|
2257
|
-
const watcher =
|
|
2450
|
+
const watcher = fs3.watch(resolved, { recursive: true }, (_event, filename) => {
|
|
2258
2451
|
if (!filename) return;
|
|
2259
2452
|
if (filename.startsWith("_zodvex") || filename.startsWith("_generated") || !filename.endsWith(".ts") && !filename.endsWith(".js")) {
|
|
2260
2453
|
return;
|
|
@@ -2288,34 +2481,56 @@ function regenerate(resolved, options) {
|
|
|
2288
2481
|
});
|
|
2289
2482
|
}
|
|
2290
2483
|
function writeStubApi(zodvexDir) {
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2484
|
+
const stubTargets = ["api.js", "api.d.ts"].map((name) => {
|
|
2485
|
+
const filePath = path4.join(zodvexDir, name);
|
|
2486
|
+
let original;
|
|
2487
|
+
try {
|
|
2488
|
+
original = fs3.readFileSync(filePath, "utf-8");
|
|
2489
|
+
} catch {
|
|
2490
|
+
original = null;
|
|
2491
|
+
}
|
|
2492
|
+
return { filePath, original };
|
|
2493
|
+
});
|
|
2494
|
+
fs3.mkdirSync(zodvexDir, { recursive: true });
|
|
2495
|
+
fs3.writeFileSync(
|
|
2496
|
+
path4.join(zodvexDir, "api.js"),
|
|
2294
2497
|
"// AUTO-GENERATED by zodvex \u2014 do not edit\n// Stub created for codegen bootstrap\n\nexport const zodvexRegistry = {}\n"
|
|
2295
2498
|
);
|
|
2296
|
-
|
|
2297
|
-
|
|
2499
|
+
fs3.writeFileSync(
|
|
2500
|
+
path4.join(zodvexDir, "api.d.ts"),
|
|
2298
2501
|
"// AUTO-GENERATED by zodvex \u2014 do not edit\n// Stub created for codegen bootstrap\n\nexport declare const zodvexRegistry: Record<string, any>\n"
|
|
2299
2502
|
);
|
|
2503
|
+
return () => {
|
|
2504
|
+
for (const { filePath, original } of stubTargets) {
|
|
2505
|
+
try {
|
|
2506
|
+
if (original !== null) {
|
|
2507
|
+
fs3.writeFileSync(filePath, original);
|
|
2508
|
+
} else {
|
|
2509
|
+
fs3.unlinkSync(filePath);
|
|
2510
|
+
}
|
|
2511
|
+
} catch {
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
};
|
|
2300
2515
|
}
|
|
2301
2516
|
function writeIfChanged(filePath, content) {
|
|
2302
2517
|
try {
|
|
2303
|
-
const existing =
|
|
2518
|
+
const existing = fs3.readFileSync(filePath, "utf-8");
|
|
2304
2519
|
if (existing === content) return;
|
|
2305
2520
|
} catch {
|
|
2306
2521
|
}
|
|
2307
|
-
|
|
2522
|
+
fs3.writeFileSync(filePath, content);
|
|
2308
2523
|
}
|
|
2309
2524
|
function resolveConvexDir(dir) {
|
|
2310
2525
|
if (dir) {
|
|
2311
|
-
const resolved =
|
|
2312
|
-
if (!
|
|
2526
|
+
const resolved = path4.resolve(dir);
|
|
2527
|
+
if (!fs3.existsSync(resolved)) {
|
|
2313
2528
|
throw new Error(`Convex directory not found: ${resolved}`);
|
|
2314
2529
|
}
|
|
2315
2530
|
return resolved;
|
|
2316
2531
|
}
|
|
2317
|
-
const defaultDir =
|
|
2318
|
-
if (!
|
|
2532
|
+
const defaultDir = path4.resolve("convex");
|
|
2533
|
+
if (!fs3.existsSync(defaultDir)) {
|
|
2319
2534
|
throw new Error("No convex/ directory found. Specify the path: zodvex generate <path>");
|
|
2320
2535
|
}
|
|
2321
2536
|
return defaultDir;
|