zodvex 0.7.7-beta.0 → 0.7.7-beta.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.
- package/README.md +55 -107
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +281 -68
- package/dist/cli/index.js.map +1 -1
- package/dist/client/index.js +5 -7
- package/dist/client/index.js.map +1 -1
- package/dist/codegen/index.js +240 -27
- package/dist/codegen/index.js.map +1 -1
- package/dist/core/index.js +58 -20
- package/dist/core/index.js.map +1 -1
- package/dist/index.js +58 -20
- 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/custom.d.ts.map +1 -1
- package/dist/internal/registry.d.ts +7 -2
- 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 +27 -12
- package/dist/legacy/index.js.map +1 -1
- package/dist/mini/client/index.js +5 -7
- package/dist/mini/client/index.js.map +1 -1
- package/dist/mini/index.js +58 -20
- package/dist/mini/index.js.map +1 -1
- package/dist/mini/react/index.js +5 -7
- package/dist/mini/react/index.js.map +1 -1
- package/dist/mini/server/index.js +32 -19
- package/dist/mini/server/index.js.map +1 -1
- package/dist/public/cli/index.d.ts +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 +5 -7
- package/dist/react/index.js.map +1 -1
- package/dist/server/index.js +32 -19
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/index.ts +1 -1
- package/src/internal/boundaryHelpers.ts +19 -9
- package/src/internal/custom.ts +11 -2
- package/src/internal/db.ts +1 -1
- package/src/internal/mapping/types.ts +2 -2
- package/src/internal/registry.ts +50 -3
- package/src/internal/types.ts +2 -2
- package/src/internal/zx.ts +12 -9
- package/src/internal/zxDateBrand.ts +34 -0
- package/src/public/cli/index.ts +1 -1
- package/src/public/codegen/discover.ts +31 -5
- package/src/public/codegen/discovery-hooks.ts +73 -8
- 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
|
-
#!/usr/bin/env
|
|
2
|
-
import
|
|
3
|
-
import
|
|
1
|
+
#!/usr/bin/env node
|
|
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,12 +1320,66 @@ function createSchemaUpdateSchema(name, inputSchema) {
|
|
|
1304
1320
|
}
|
|
1305
1321
|
return inputSchema;
|
|
1306
1322
|
}
|
|
1307
|
-
|
|
1308
|
-
|
|
1323
|
+
function buildHooksSource(aliases) {
|
|
1324
|
+
return `
|
|
1325
|
+
import { pathToFileURL } from 'node:url';
|
|
1326
|
+
|
|
1327
|
+
const EXT_CANDIDATES = ['.ts', '.tsx', '.mts', '.js', '.mjs', '.jsx', '/index.ts', '/index.js'];
|
|
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
|
+
|
|
1352
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
1309
1353
|
if (/_generated\\/api(\\.[mc]?[jt]sx?)?$/.test(specifier)) {
|
|
1310
1354
|
return { shortCircuit: true, url: 'zodvex-stub://api' };
|
|
1311
1355
|
}
|
|
1312
|
-
|
|
1356
|
+
try {
|
|
1357
|
+
return await nextResolve(specifier, context);
|
|
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
|
+
}
|
|
1368
|
+
// Convex code uses extensionless relative imports (bundler resolution).
|
|
1369
|
+
// Bun resolves those natively; Node's ESM loader does not \u2014 retry with
|
|
1370
|
+
// the usual extension candidates before giving up.
|
|
1371
|
+
if (
|
|
1372
|
+
(specifier.startsWith('./') || specifier.startsWith('../')) &&
|
|
1373
|
+
!/\\.[cm]?[jt]sx?$/.test(specifier)
|
|
1374
|
+
) {
|
|
1375
|
+
for (const ext of EXT_CANDIDATES) {
|
|
1376
|
+
try {
|
|
1377
|
+
return await nextResolve(specifier + ext, context);
|
|
1378
|
+
} catch {}
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
throw err;
|
|
1382
|
+
}
|
|
1313
1383
|
}
|
|
1314
1384
|
|
|
1315
1385
|
export function load(url, context, nextLoad) {
|
|
@@ -1339,13 +1409,18 @@ export function load(url, context, nextLoad) {
|
|
|
1339
1409
|
return nextLoad(url, context);
|
|
1340
1410
|
}
|
|
1341
1411
|
`;
|
|
1412
|
+
}
|
|
1342
1413
|
var hooksRegistered = false;
|
|
1343
|
-
function registerDiscoveryHooks() {
|
|
1414
|
+
function registerDiscoveryHooks(aliases = []) {
|
|
1344
1415
|
if (hooksRegistered) return true;
|
|
1345
1416
|
try {
|
|
1346
|
-
const
|
|
1417
|
+
const nodeModule = typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("node:module") : (
|
|
1418
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1419
|
+
__require("module")
|
|
1420
|
+
);
|
|
1421
|
+
const { register } = nodeModule;
|
|
1347
1422
|
if (typeof register !== "function") return false;
|
|
1348
|
-
register(`data:text/javascript,${encodeURIComponent(
|
|
1423
|
+
register(`data:text/javascript,${encodeURIComponent(buildHooksSource(aliases))}`);
|
|
1349
1424
|
hooksRegistered = true;
|
|
1350
1425
|
return true;
|
|
1351
1426
|
} catch {
|
|
@@ -1370,22 +1445,22 @@ export const components = p;
|
|
|
1370
1445
|
export const httpRouter = p;
|
|
1371
1446
|
`;
|
|
1372
1447
|
function writeGeneratedStubs(convexDir2) {
|
|
1373
|
-
const generatedDir =
|
|
1374
|
-
const apiPath =
|
|
1448
|
+
const generatedDir = path4.join(convexDir2, "_generated");
|
|
1449
|
+
const apiPath = path4.join(generatedDir, "api.ts");
|
|
1375
1450
|
let original;
|
|
1376
1451
|
try {
|
|
1377
|
-
original =
|
|
1452
|
+
original = fs3.readFileSync(apiPath, "utf8");
|
|
1378
1453
|
} catch {
|
|
1379
1454
|
original = null;
|
|
1380
1455
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1456
|
+
fs3.mkdirSync(generatedDir, { recursive: true });
|
|
1457
|
+
fs3.writeFileSync(apiPath, PROXY_STUB_API);
|
|
1383
1458
|
return () => {
|
|
1384
1459
|
if (original !== null) {
|
|
1385
|
-
|
|
1460
|
+
fs3.writeFileSync(apiPath, original);
|
|
1386
1461
|
} else {
|
|
1387
1462
|
try {
|
|
1388
|
-
|
|
1463
|
+
fs3.unlinkSync(apiPath);
|
|
1389
1464
|
} catch {
|
|
1390
1465
|
}
|
|
1391
1466
|
}
|
|
@@ -1397,8 +1472,7 @@ function findCodec(schema) {
|
|
|
1397
1472
|
let current = schema;
|
|
1398
1473
|
for (let i = 0; i < 10; i++) {
|
|
1399
1474
|
if (current instanceof $ZodCodec) {
|
|
1400
|
-
|
|
1401
|
-
if (isZxDate) return void 0;
|
|
1475
|
+
if (isZxDateCodec(current)) return void 0;
|
|
1402
1476
|
return current;
|
|
1403
1477
|
}
|
|
1404
1478
|
if (current instanceof $ZodOptional || current instanceof $ZodNullable) {
|
|
@@ -1409,6 +1483,134 @@ function findCodec(schema) {
|
|
|
1409
1483
|
}
|
|
1410
1484
|
return void 0;
|
|
1411
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
|
+
}
|
|
1412
1614
|
|
|
1413
1615
|
// src/public/codegen/discover.ts
|
|
1414
1616
|
function walkSchemaRecursive(schema, accessPath, visited, seenCodecs, results) {
|
|
@@ -1552,7 +1754,7 @@ async function discoverModules(convexDir2) {
|
|
|
1552
1754
|
const models = [];
|
|
1553
1755
|
const functions = [];
|
|
1554
1756
|
const codecs = [];
|
|
1555
|
-
registerDiscoveryHooks();
|
|
1757
|
+
registerDiscoveryHooks(loadTsconfigAliases(convexDir2));
|
|
1556
1758
|
const cleanupStubs = writeGeneratedStubs(convexDir2);
|
|
1557
1759
|
const files = globSync(["**/*.{ts,js}"], {
|
|
1558
1760
|
cwd: convexDir2,
|
|
@@ -1572,13 +1774,15 @@ async function discoverModules(convexDir2) {
|
|
|
1572
1774
|
"crons.js"
|
|
1573
1775
|
]
|
|
1574
1776
|
}).sort();
|
|
1777
|
+
const failedImports = [];
|
|
1575
1778
|
try {
|
|
1576
1779
|
for (const file of files) {
|
|
1577
|
-
const absPath =
|
|
1780
|
+
const absPath = path4.resolve(convexDir2, file);
|
|
1578
1781
|
let moduleExports;
|
|
1579
1782
|
try {
|
|
1580
1783
|
moduleExports = await import(absPath);
|
|
1581
1784
|
} catch (err) {
|
|
1785
|
+
failedImports.push({ file, message: err.message });
|
|
1582
1786
|
console.warn(`[zodvex] Warning: Failed to import ${file}:`, err.message);
|
|
1583
1787
|
continue;
|
|
1584
1788
|
}
|
|
@@ -1622,8 +1826,7 @@ async function discoverModules(convexDir2) {
|
|
|
1622
1826
|
}
|
|
1623
1827
|
}
|
|
1624
1828
|
if (value instanceof $ZodCodec) {
|
|
1625
|
-
|
|
1626
|
-
if (!isZxDate) {
|
|
1829
|
+
if (!isZxDateCodec(value)) {
|
|
1627
1830
|
if (!codecs.some((c) => c.schema === value)) {
|
|
1628
1831
|
codecs.push({
|
|
1629
1832
|
exportName,
|
|
@@ -1646,6 +1849,16 @@ async function discoverModules(convexDir2) {
|
|
|
1646
1849
|
modelCodecs.push(...found);
|
|
1647
1850
|
}
|
|
1648
1851
|
const functionCodecs = walkFunctionCodecs(functions);
|
|
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` : "";
|
|
1856
|
+
throw new Error(
|
|
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).`
|
|
1860
|
+
);
|
|
1861
|
+
}
|
|
1649
1862
|
return { models, functions, codecs, modelCodecs, functionCodecs };
|
|
1650
1863
|
} finally {
|
|
1651
1864
|
cleanupStubs();
|
|
@@ -1668,7 +1881,7 @@ function zodToSource(schema, ctx) {
|
|
|
1668
1881
|
return `zx.id("${tableName}")`;
|
|
1669
1882
|
}
|
|
1670
1883
|
}
|
|
1671
|
-
if (schema instanceof $ZodCodec && schema
|
|
1884
|
+
if (schema instanceof $ZodCodec && isZxDateCodec(schema)) {
|
|
1672
1885
|
return "zx.date()";
|
|
1673
1886
|
}
|
|
1674
1887
|
if (schema instanceof $ZodCodec) {
|
|
@@ -2191,7 +2404,7 @@ ${dtsDeclarations.join("\n")}
|
|
|
2191
2404
|
// src/public/cli/commands.ts
|
|
2192
2405
|
async function generate(convexDir2, options) {
|
|
2193
2406
|
const resolved = resolveConvexDir(convexDir2);
|
|
2194
|
-
const zodvexDir =
|
|
2407
|
+
const zodvexDir = path4.join(resolved, "_zodvex");
|
|
2195
2408
|
writeStubApi(zodvexDir);
|
|
2196
2409
|
const result = await discoverModules(resolved);
|
|
2197
2410
|
const schemaContent = generateSchemaFile(result.models);
|
|
@@ -2205,15 +2418,15 @@ async function generate(convexDir2, options) {
|
|
|
2205
2418
|
);
|
|
2206
2419
|
const clientContent = generateClientFile({ mini: options?.mini });
|
|
2207
2420
|
const serverContent = generateServerFile();
|
|
2208
|
-
|
|
2209
|
-
writeIfChanged(
|
|
2210
|
-
writeIfChanged(
|
|
2211
|
-
writeIfChanged(
|
|
2212
|
-
writeIfChanged(
|
|
2213
|
-
writeIfChanged(
|
|
2214
|
-
writeIfChanged(
|
|
2215
|
-
writeIfChanged(
|
|
2216
|
-
writeIfChanged(
|
|
2421
|
+
fs3.mkdirSync(zodvexDir, { recursive: true });
|
|
2422
|
+
writeIfChanged(path4.join(zodvexDir, "schema.js"), schemaContent.js);
|
|
2423
|
+
writeIfChanged(path4.join(zodvexDir, "schema.d.ts"), schemaContent.dts);
|
|
2424
|
+
writeIfChanged(path4.join(zodvexDir, "api.js"), apiContent.js);
|
|
2425
|
+
writeIfChanged(path4.join(zodvexDir, "api.d.ts"), apiContent.dts);
|
|
2426
|
+
writeIfChanged(path4.join(zodvexDir, "client.js"), clientContent.js);
|
|
2427
|
+
writeIfChanged(path4.join(zodvexDir, "client.d.ts"), clientContent.dts);
|
|
2428
|
+
writeIfChanged(path4.join(zodvexDir, "server.js"), serverContent.js);
|
|
2429
|
+
writeIfChanged(path4.join(zodvexDir, "server.d.ts"), serverContent.dts);
|
|
2217
2430
|
const totalCodecs = result.codecs.length + result.modelCodecs.length + result.functionCodecs.length;
|
|
2218
2431
|
console.log(
|
|
2219
2432
|
`[zodvex] Generated ${result.models.length} model(s), ${result.functions.length} function(s), ${totalCodecs} codec(s)`
|
|
@@ -2224,7 +2437,7 @@ async function dev(convexDir2, options) {
|
|
|
2224
2437
|
console.log("[zodvex] Starting watch mode...");
|
|
2225
2438
|
await generate(resolved, options);
|
|
2226
2439
|
let debounceTimer = null;
|
|
2227
|
-
const watcher =
|
|
2440
|
+
const watcher = fs3.watch(resolved, { recursive: true }, (_event, filename) => {
|
|
2228
2441
|
if (!filename) return;
|
|
2229
2442
|
if (filename.startsWith("_zodvex") || filename.startsWith("_generated") || !filename.endsWith(".ts") && !filename.endsWith(".js")) {
|
|
2230
2443
|
return;
|
|
@@ -2258,34 +2471,34 @@ function regenerate(resolved, options) {
|
|
|
2258
2471
|
});
|
|
2259
2472
|
}
|
|
2260
2473
|
function writeStubApi(zodvexDir) {
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2474
|
+
fs3.mkdirSync(zodvexDir, { recursive: true });
|
|
2475
|
+
fs3.writeFileSync(
|
|
2476
|
+
path4.join(zodvexDir, "api.js"),
|
|
2264
2477
|
"// AUTO-GENERATED by zodvex \u2014 do not edit\n// Stub created for codegen bootstrap\n\nexport const zodvexRegistry = {}\n"
|
|
2265
2478
|
);
|
|
2266
|
-
|
|
2267
|
-
|
|
2479
|
+
fs3.writeFileSync(
|
|
2480
|
+
path4.join(zodvexDir, "api.d.ts"),
|
|
2268
2481
|
"// AUTO-GENERATED by zodvex \u2014 do not edit\n// Stub created for codegen bootstrap\n\nexport declare const zodvexRegistry: Record<string, any>\n"
|
|
2269
2482
|
);
|
|
2270
2483
|
}
|
|
2271
2484
|
function writeIfChanged(filePath, content) {
|
|
2272
2485
|
try {
|
|
2273
|
-
const existing =
|
|
2486
|
+
const existing = fs3.readFileSync(filePath, "utf-8");
|
|
2274
2487
|
if (existing === content) return;
|
|
2275
2488
|
} catch {
|
|
2276
2489
|
}
|
|
2277
|
-
|
|
2490
|
+
fs3.writeFileSync(filePath, content);
|
|
2278
2491
|
}
|
|
2279
2492
|
function resolveConvexDir(dir) {
|
|
2280
2493
|
if (dir) {
|
|
2281
|
-
const resolved =
|
|
2282
|
-
if (!
|
|
2494
|
+
const resolved = path4.resolve(dir);
|
|
2495
|
+
if (!fs3.existsSync(resolved)) {
|
|
2283
2496
|
throw new Error(`Convex directory not found: ${resolved}`);
|
|
2284
2497
|
}
|
|
2285
2498
|
return resolved;
|
|
2286
2499
|
}
|
|
2287
|
-
const defaultDir =
|
|
2288
|
-
if (!
|
|
2500
|
+
const defaultDir = path4.resolve("convex");
|
|
2501
|
+
if (!fs3.existsSync(defaultDir)) {
|
|
2289
2502
|
throw new Error("No convex/ directory found. Specify the path: zodvex generate <path>");
|
|
2290
2503
|
}
|
|
2291
2504
|
return defaultDir;
|