on-zero 0.4.1 → 0.4.3

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 (41) hide show
  1. package/dist/cjs/generate-helpers.cjs +309 -0
  2. package/dist/cjs/generate-helpers.native.js +451 -0
  3. package/dist/cjs/generate-helpers.native.js.map +1 -0
  4. package/dist/cjs/generate-lite.cjs +150 -0
  5. package/dist/cjs/generate-lite.native.js +269 -0
  6. package/dist/cjs/generate-lite.native.js.map +1 -0
  7. package/dist/cjs/generate-lite.test.cjs +229 -0
  8. package/dist/cjs/generate-lite.test.native.js +234 -0
  9. package/dist/cjs/generate-lite.test.native.js.map +1 -0
  10. package/dist/cjs/generate.cjs +16 -285
  11. package/dist/cjs/generate.native.js +18 -432
  12. package/dist/cjs/generate.native.js.map +1 -1
  13. package/dist/esm/generate-helpers.mjs +272 -0
  14. package/dist/esm/generate-helpers.mjs.map +1 -0
  15. package/dist/esm/generate-helpers.native.js +411 -0
  16. package/dist/esm/generate-helpers.native.js.map +1 -0
  17. package/dist/esm/generate-lite.mjs +127 -0
  18. package/dist/esm/generate-lite.mjs.map +1 -0
  19. package/dist/esm/generate-lite.native.js +243 -0
  20. package/dist/esm/generate-lite.native.js.map +1 -0
  21. package/dist/esm/generate-lite.test.mjs +230 -0
  22. package/dist/esm/generate-lite.test.mjs.map +1 -0
  23. package/dist/esm/generate-lite.test.native.js +232 -0
  24. package/dist/esm/generate-lite.test.native.js.map +1 -0
  25. package/dist/esm/generate.mjs +6 -275
  26. package/dist/esm/generate.mjs.map +1 -1
  27. package/dist/esm/generate.native.js +9 -423
  28. package/dist/esm/generate.native.js.map +1 -1
  29. package/package.json +7 -2
  30. package/src/generate-helpers.ts +440 -0
  31. package/src/generate-lite.test.ts +310 -0
  32. package/src/generate-lite.ts +333 -0
  33. package/src/generate.ts +23 -415
  34. package/types/generate-helpers.d.ts +42 -0
  35. package/types/generate-helpers.d.ts.map +1 -0
  36. package/types/generate-lite.d.ts +40 -0
  37. package/types/generate-lite.d.ts.map +1 -0
  38. package/types/generate-lite.test.d.ts +2 -0
  39. package/types/generate-lite.test.d.ts.map +1 -0
  40. package/types/generate.d.ts +1 -6
  41. package/types/generate.d.ts.map +1 -1
@@ -0,0 +1,232 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { generateLite } from "./generate-lite.native.js";
3
+ function makeParse(table) {
4
+ return function (_src, path) {
5
+ var entry = table[path];
6
+ if (!entry) throw new Error(`no lite ast fixture for ${path}`);
7
+ return entry;
8
+ };
9
+ }
10
+ var DIR = "/proj/src/data";
11
+ describe("generateLite", function () {
12
+ test("emits models.ts, syncedMutations.ts, and README.md from inline types", function () {
13
+ var files = {
14
+ [`${DIR}/models/todo.ts`]: "// fake source, parser returns fixture",
15
+ [`${DIR}/models/user.ts`]: "// fake source, parser returns fixture"
16
+ },
17
+ fixtures = {
18
+ [`${DIR}/models/todo.ts`]: {
19
+ mutations: [{
20
+ modelName: "todo",
21
+ handlers: [{
22
+ name: "toggle",
23
+ paramTypeText: "{ id: string; isActive: boolean }"
24
+ }, {
25
+ name: "rename",
26
+ paramTypeText: "{ id: string; title: string }"
27
+ }],
28
+ schema: null
29
+ }],
30
+ queries: []
31
+ },
32
+ [`${DIR}/models/user.ts`]: {
33
+ mutations: [{
34
+ modelName: "user",
35
+ handlers: [{
36
+ name: "finishOnboarding",
37
+ // null = no second param / void
38
+ paramTypeText: null
39
+ }],
40
+ schema: null
41
+ }],
42
+ queries: []
43
+ }
44
+ },
45
+ result = generateLite({
46
+ files,
47
+ dir: DIR,
48
+ parse: makeParse(fixtures)
49
+ });
50
+ expect(Object.keys(result.files).sort()).toEqual(["README.md", "models.ts", "syncedMutations.ts"]);
51
+ var models = result.files["models.ts"];
52
+ expect(models).toContain("import * as todo from '../models/todo'"), expect(models).toContain("import * as userPublic from '../models/user'"), expect(models).toContain("export const models = {");
53
+ var syncedMutations = result.files["syncedMutations.ts"];
54
+ expect(syncedMutations).toContain("toggle:"), expect(syncedMutations).toContain("v.object({"), expect(syncedMutations).toContain("id: v.string()"), expect(syncedMutations).toContain("isActive: v.boolean()"), expect(syncedMutations).toContain("rename:"), expect(syncedMutations).toContain("title: v.string()"), expect(syncedMutations).toContain("finishOnboarding: v.void_()"), expect(result.modelCount).toBe(2), expect(result.schemaCount).toBe(0), expect(result.mutationCount).toBe(3);
55
+ }), test("falls back to v.unknown() for type references", function () {
56
+ var files = {
57
+ [`${DIR}/models/post.ts`]: "// fake"
58
+ },
59
+ fixtures = {
60
+ [`${DIR}/models/post.ts`]: {
61
+ mutations: [{
62
+ modelName: "post",
63
+ handlers: [{
64
+ name: "archive",
65
+ // bare type reference — parseTypeString returns null, so
66
+ // generate-lite should fall back to v.unknown() rather than
67
+ // attempting cross-file type resolution.
68
+ paramTypeText: "ArchiveParams"
69
+ }, {
70
+ name: "publish",
71
+ // primitive, should resolve
72
+ paramTypeText: "string"
73
+ }],
74
+ schema: null
75
+ }],
76
+ queries: []
77
+ }
78
+ },
79
+ result = generateLite({
80
+ files,
81
+ dir: DIR,
82
+ parse: makeParse(fixtures)
83
+ }),
84
+ synced = result.files["syncedMutations.ts"];
85
+ expect(synced).toContain("archive: v.unknown()"), expect(synced).toContain("publish: v.string()");
86
+ }), test("emits query files with v.unknown() fallback for references", function () {
87
+ var files = {
88
+ [`${DIR}/models/post.ts`]: "// fake",
89
+ [`${DIR}/queries/post.ts`]: "// fake"
90
+ },
91
+ fixtures = {
92
+ [`${DIR}/models/post.ts`]: {
93
+ mutations: [],
94
+ queries: []
95
+ },
96
+ [`${DIR}/queries/post.ts`]: {
97
+ mutations: [],
98
+ queries: [
99
+ // no-arg query → void
100
+ {
101
+ name: "allPosts",
102
+ paramTypeText: null
103
+ },
104
+ // inline object → real validator
105
+ {
106
+ name: "postById",
107
+ paramTypeText: "{ id: string }"
108
+ },
109
+ // primitive
110
+ {
111
+ name: "byAuthorId",
112
+ paramTypeText: "string"
113
+ },
114
+ // type reference → fallback
115
+ {
116
+ name: "filtered",
117
+ paramTypeText: "PostFilter"
118
+ },
119
+ // permission should be skipped
120
+ {
121
+ name: "permission",
122
+ paramTypeText: null
123
+ }]
124
+ }
125
+ },
126
+ result = generateLite({
127
+ files,
128
+ dir: DIR,
129
+ parse: makeParse(fixtures)
130
+ });
131
+ expect(result.files["groupedQueries.ts"]).toBeDefined(), expect(result.files["syncedQueries.ts"]).toBeDefined();
132
+ var grouped = result.files["groupedQueries.ts"];
133
+ expect(grouped).toContain("export * as post from '../queries/post'");
134
+ var synced = result.files["syncedQueries.ts"];
135
+ expect(synced).toContain("allPosts: defineQuery(() => Queries.post.allPosts())"), expect(synced).toContain("postById: defineQuery("), expect(synced).toContain("id: v.string()"), expect(synced).toContain("byAuthorId: defineQuery("), expect(synced).toMatch(/byAuthorId: defineQuery\(\s*v\.string\(\)/), expect(synced).toContain("filtered: defineQuery("), expect(synced).toMatch(/filtered: defineQuery\(\s*v\.unknown\(\)/), expect(synced).not.toContain("permission: defineQuery"), expect(result.queryCount).toBe(4);
136
+ }), test("emits types.ts and tables.ts when a model declares a schema inline", function () {
137
+ var files = {
138
+ [`${DIR}/models/task.ts`]: "// fake"
139
+ },
140
+ fixtures = {
141
+ [`${DIR}/models/task.ts`]: {
142
+ mutations: [{
143
+ modelName: "task",
144
+ handlers: [],
145
+ schema: {
146
+ tableName: "task",
147
+ primaryKeys: ["id"],
148
+ columns: [{
149
+ name: "id",
150
+ builderText: "string()"
151
+ }, {
152
+ name: "title",
153
+ builderText: "string()"
154
+ }, {
155
+ name: "priority",
156
+ builderText: "number()"
157
+ }, {
158
+ name: "done",
159
+ builderText: "boolean()"
160
+ }, {
161
+ name: "note",
162
+ builderText: "string().optional()"
163
+ }]
164
+ }
165
+ }],
166
+ queries: []
167
+ }
168
+ },
169
+ result = generateLite({
170
+ files,
171
+ dir: DIR,
172
+ parse: makeParse(fixtures)
173
+ });
174
+ expect(result.schemaCount).toBe(1), expect(result.files["types.ts"]).toBeDefined(), expect(result.files["tables.ts"]).toBeDefined();
175
+ var types = result.files["types.ts"];
176
+ expect(types).toContain("export type Task = TableInsertRow<typeof schema.task>");
177
+ var tables = result.files["tables.ts"];
178
+ expect(tables).toContain("export { schema as task } from '../models/task'");
179
+ var synced = result.files["syncedMutations.ts"];
180
+ expect(synced).toContain("insert:"), expect(synced).toContain("update:"), expect(synced).toContain("delete:"), expect(result.mutationCount).toBe(3);
181
+ }), test("ignores nested files and non-ts files inside the models directory", function () {
182
+ var files = {
183
+ [`${DIR}/models/post.ts`]: "// fake",
184
+ [`${DIR}/models/README.md`]: "not a model",
185
+ [`${DIR}/models/helpers/util.ts`]: "nested should be ignored",
186
+ [`${DIR}/models/post.d.ts`]: "declaration file, ignored"
187
+ },
188
+ fixtures = {
189
+ [`${DIR}/models/post.ts`]: {
190
+ mutations: [{
191
+ modelName: "post",
192
+ handlers: [],
193
+ schema: null
194
+ }],
195
+ queries: []
196
+ }
197
+ },
198
+ result = generateLite({
199
+ files,
200
+ dir: DIR,
201
+ parse: makeParse(fixtures)
202
+ });
203
+ expect(result.modelCount).toBe(1);
204
+ var models = result.files["models.ts"];
205
+ expect(models).toContain("import * as post from '../models/post'"), expect(models).not.toContain("util"), expect(models).not.toContain("README");
206
+ }), test("infers mutations/ directory when present", function () {
207
+ var files = {
208
+ [`${DIR}/mutations/post.ts`]: "// fake"
209
+ },
210
+ fixtures = {
211
+ [`${DIR}/mutations/post.ts`]: {
212
+ mutations: [{
213
+ modelName: "post",
214
+ handlers: [{
215
+ name: "publish",
216
+ paramTypeText: "{ id: string }"
217
+ }],
218
+ schema: null
219
+ }],
220
+ queries: []
221
+ }
222
+ },
223
+ result = generateLite({
224
+ files,
225
+ dir: DIR,
226
+ parse: makeParse(fixtures)
227
+ }),
228
+ models = result.files["models.ts"];
229
+ expect(models).toContain("from '../mutations/post'");
230
+ });
231
+ });
232
+ //# sourceMappingURL=generate-lite.test.native.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["describe","expect","test","generateLite","makeParse","table","_src","path","entry","Error","DIR","files","fixtures","mutations","modelName","handlers","name","paramTypeText","schema","queries","result","dir","parse","Object","keys","sort","toEqual","models","toContain","syncedMutations","modelCount","toBe","schemaCount","mutationCount","synced","toBeDefined","grouped","toMatch","not","queryCount","tableName","primaryKeys","columns","builderText","types","tables"],"sources":["../../src/generate-lite.test.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
3
3
  import { basename, dirname, resolve } from "node:path";
4
+ import { formatObjectKey, generateGroupedQueriesFile, generateModelsFile, generateReadmeFile, generateSyncedMutationsFile, generateSyncedQueriesFile, generateTablesFile, generateTypesFile, parseColumnType, parseTypeString, shouldSkipObjectKey } from "./generate-helpers.mjs";
4
5
  const hash = s => createHash("sha256").update(s).digest("hex");
5
6
  let generateCache = {},
6
7
  generateCachePath = "";
@@ -37,157 +38,6 @@ function writeFileIfChanged(filePath, content) {
37
38
  const contentHash = hash(content);
38
39
  return generateCache[filePath] === contentHash && existsSync(filePath) ? !1 : (writeFileSync(filePath, content, "utf-8"), generateCache[filePath] = contentHash, !0);
39
40
  }
40
- function generateModelsFile(modelFiles, modelsDirName) {
41
- const modelNames = modelFiles.map(f => basename(f, ".ts")).sort(),
42
- getImportName = name => name === "user" ? "userPublic" : name,
43
- imports = modelNames.map(name => `import * as ${getImportName(name)} from '../${modelsDirName}/${name}'`).join(`
44
- `),
45
- modelsObj = `export const models = {
46
- ${[...modelNames].sort((a, b) => getImportName(a).localeCompare(getImportName(b))).map(name => ` ${getImportName(name)},`).join(`
47
- `)}
48
- }`;
49
- return `// auto-generated by: on-zero generate
50
- ${imports}
51
-
52
- ${modelsObj}
53
- `;
54
- }
55
- function generateTypesFile(modelFiles) {
56
- const modelNames = modelFiles.map(f => basename(f, ".ts")).sort(),
57
- getSchemaName = name => name === "user" ? "userPublic" : name;
58
- return `import type { TableInsertRow, TableUpdateRow } from 'on-zero'
59
- import type * as schema from './tables'
60
-
61
- ${modelNames.map(name => {
62
- const pascalName = name.charAt(0).toUpperCase() + name.slice(1),
63
- schemaName = getSchemaName(name);
64
- return `export type ${pascalName} = TableInsertRow<typeof schema.${schemaName}>
65
- export type ${pascalName}Update = TableUpdateRow<typeof schema.${schemaName}>`;
66
- }).join(`
67
-
68
- `)}
69
- `;
70
- }
71
- function generateTablesFile(modelFiles, modelsDirName) {
72
- const modelNames = modelFiles.map(f => basename(f, ".ts")).sort(),
73
- getExportName = name => name === "user" ? "userPublic" : name;
74
- return `// auto-generated by: on-zero generate
75
-
76
- ${modelNames.map(name => `export { schema as ${getExportName(name)} } from '../${modelsDirName}/${name}'`).join(`
77
- `)}
78
- `;
79
- }
80
- function generateReadmeFile() {
81
- return `# generated
82
-
83
- this folder is auto-generated by on-zero. do not edit files here directly.
84
-
85
- ## what's generated
86
-
87
- - \`models.ts\` - exports all models from ../models
88
- - \`types.ts\` - typescript types derived from table schemas
89
- - \`tables.ts\` - exports table schemas for type inference
90
- - \`groupedQueries.ts\` - namespaced query re-exports for client setup
91
- - \`syncedQueries.ts\` - namespaced syncedQuery wrappers for server setup
92
- - \`syncedMutations.ts\` - valibot validators for mutation args (server auto-validation)
93
-
94
- ## usage guidelines
95
-
96
- **do not import generated files outside of the data folder.**
97
-
98
- ### queries
99
-
100
- write your queries as plain functions in \`../queries/\` and import them directly:
101
-
102
- \`\`\`ts
103
- // \u2705 good - import from queries
104
- import { channelMessages } from '~/data/queries/message'
105
- \`\`\`
106
-
107
- the generated query files are only used internally by zero client/server setup.
108
-
109
- ### types
110
-
111
- you can import types from this folder, but prefer re-exporting from \`../types.ts\`:
112
-
113
- \`\`\`ts
114
- // \u274C okay but not preferred
115
- import type { Message } from '~/data/generated/types'
116
-
117
- // \u2705 better - re-export from types.ts
118
- import type { Message } from '~/data/types'
119
- \`\`\`
120
-
121
- ## regeneration
122
-
123
- files are regenerated when you run:
124
-
125
- \`\`\`bash
126
- bun on-zero generate
127
- \`\`\`
128
-
129
- or in watch mode:
130
-
131
- \`\`\`bash
132
- bun on-zero generate --watch
133
- \`\`\`
134
-
135
- ## more info
136
-
137
- see the [on-zero readme](./node_modules/on-zero/README.md) for full documentation.
138
- `;
139
- }
140
- function generateGroupedQueriesFile(queries) {
141
- return `/**
142
- * auto-generated by: on-zero generate
143
- *
144
- * grouped query re-exports for minification-safe query identity.
145
- * this file re-exports all query modules - while this breaks tree-shaking,
146
- * queries are typically small and few in number even in larger apps.
147
- */
148
- ${[...new Set(queries.map(q => q.sourceFile))].sort().map(file => `export * as ${file} from '../queries/${file}'`).join(`
149
- `)}
150
- `;
151
- }
152
- function generateSyncedQueriesFile(queries) {
153
- const queryByFile = /* @__PURE__ */new Map();
154
- for (const q of queries) queryByFile.has(q.sourceFile) || queryByFile.set(q.sourceFile, []), queryByFile.get(q.sourceFile).push(q);
155
- const sortedFiles = Array.from(queryByFile.keys()).sort(),
156
- imports = `// auto-generated by: on-zero generate
157
- // server-side query definitions with validators
158
- import { defineQuery, defineQueries } from '@rocicorp/zero'
159
- import * as v from 'valibot'
160
- import * as Queries from './groupedQueries'
161
- `,
162
- namespaceDefs = sortedFiles.map(file => {
163
- const queryDefs = queryByFile.get(file).sort((a, b) => a.name.localeCompare(b.name)).map(q => {
164
- const validatorDef = q.valibotCode.trim();
165
- if (q.params === "void" || !validatorDef) return ` ${q.name}: defineQuery(() => Queries.${file}.${q.name}()),`;
166
- const indentedValidator = validatorDef.split(`
167
- `).map((line, i) => i === 0 ? line : ` ${line}`).join(`
168
- `);
169
- return ` ${q.name}: defineQuery(
170
- ${indentedValidator},
171
- ({ args }) => Queries.${file}.${q.name}(args)
172
- ),`;
173
- }).join(`
174
- `);
175
- return `const ${file} = {
176
- ${queryDefs}
177
- }`;
178
- }).join(`
179
-
180
- `),
181
- queriesObject = sortedFiles.map(file => ` ${file},`).join(`
182
- `);
183
- return `${imports}
184
- ${namespaceDefs}
185
-
186
- export const queries = defineQueries({
187
- ${queriesObject}
188
- })
189
- `;
190
- }
191
41
  function createTypeResolver(ts, files, dir) {
192
42
  const configPath = ts.findConfigFile(dir, ts.sys.fileExists, "tsconfig.json");
193
43
  let compilerOptions = {
@@ -363,127 +213,6 @@ function extractSchemaColumns(ts, sourceFile, columns, primaryKeys) {
363
213
  }
364
214
  visit(sourceFile);
365
215
  }
366
- function parseColumnType(initText) {
367
- const optional = initText.includes(".optional()");
368
- let type = "string";
369
- return initText.startsWith("number(") ? type = "number" : initText.startsWith("boolean(") ? type = "boolean" : initText.startsWith("json(") || initText.startsWith("json<") ? type = "json" : initText.startsWith("enumeration(") && (type = "enum"), {
370
- type,
371
- optional,
372
- customType: void 0
373
- };
374
- }
375
- function columnTypeToValibot(col) {
376
- let base = "v.string()";
377
- switch (col.type) {
378
- case "string":
379
- base = "v.string()";
380
- break;
381
- case "number":
382
- base = "v.number()";
383
- break;
384
- case "boolean":
385
- base = "v.boolean()";
386
- break;
387
- case "json":
388
- base = "v.unknown()";
389
- break;
390
- case "enum":
391
- base = "v.string()";
392
- break;
393
- }
394
- return col.optional ? `v.optional(v.nullable(${base}))` : base;
395
- }
396
- function shouldSkipObjectKey(name) {
397
- return name.startsWith("__@");
398
- }
399
- function formatObjectKey(name) {
400
- return /^[$A-Z_a-z][$\w]*$/.test(name) ? name : JSON.stringify(name);
401
- }
402
- function schemaColumnsToValibot(columns, primaryKeys, mode) {
403
- const entries = [];
404
- if (mode === "delete") for (const pk of primaryKeys) {
405
- const col = columns[pk];
406
- col && entries.push(`${formatObjectKey(pk)}: ${columnTypeToValibot({
407
- ...col,
408
- optional: !1
409
- })}`);
410
- } else if (mode === "update") for (const [name, col] of Object.entries(columns)) primaryKeys.includes(name) ? entries.push(`${formatObjectKey(name)}: ${columnTypeToValibot({
411
- ...col,
412
- optional: !1
413
- })}`) : entries.push(`${formatObjectKey(name)}: ${columnTypeToValibot({
414
- ...col,
415
- optional: !0
416
- })}`);else for (const [name, col] of Object.entries(columns)) entries.push(`${formatObjectKey(name)}: ${columnTypeToValibot(col)}`);
417
- return `v.object({
418
- ${entries.join(`,
419
- `)},
420
- })`;
421
- }
422
- function generateSyncedMutationsFile(modelMutations) {
423
- return `// auto-generated by: on-zero generate
424
- // mutation validators derived from model schemas and handler types
425
- import * as v from 'valibot'
426
-
427
- export const mutationValidators = {
428
- ${[...modelMutations].sort((a, b) => a.modelName.localeCompare(b.modelName)).map(model => {
429
- const entries = [];
430
- if (model.hasCRUD && Object.keys(model.columns).length > 0) for (const mode of ["insert", "update", "delete"]) if (model.custom.some(m => m.name === mode)) {
431
- const customMut = model.custom.find(m => m.name === mode);
432
- customMut.valibotCode ? entries.push(` ${mode}: ${extractValibotExpression(customMut.valibotCode)},`) : entries.push(` ${mode}: ${schemaColumnsToValibot(model.columns, model.primaryKeys, mode)},`);
433
- } else entries.push(` ${mode}: ${schemaColumnsToValibot(model.columns, model.primaryKeys, mode)},`);
434
- for (const mut of model.custom) if (!(model.hasCRUD && ["insert", "update", "delete", "upsert"].includes(mut.name))) {
435
- if (mut.paramType === "void" || !mut.valibotCode) {
436
- entries.push(` ${mut.name}: v.void_(),`);
437
- continue;
438
- }
439
- entries.push(` ${mut.name}: ${extractValibotExpression(mut.valibotCode)},`);
440
- }
441
- return ` ${model.modelName}: {
442
- ${entries.join(`
443
- `)}
444
- },`;
445
- }).join(`
446
- `)}
447
- }
448
- `;
449
- }
450
- function extractValibotExpression(valibotCode) {
451
- return valibotCode.trim() || "v.unknown()";
452
- }
453
- function parseTypeString(type) {
454
- if (type = type.trim(), type === "string") return "v.string()";
455
- if (type === "number") return "v.number()";
456
- if (type === "boolean") return "v.boolean()";
457
- if (type === "void" || type === "undefined") return "v.void_()";
458
- if (type === "null") return "v.null_()";
459
- if (type === "any" || type === "unknown") return "v.unknown()";
460
- if (type.startsWith("{") && type.endsWith("}")) {
461
- const inner = type.slice(1, -1).trim();
462
- if (!inner) return "v.object({})";
463
- const normalized = inner.replace(/\n/g, "; ").replace(/;\s*;/g, ";"),
464
- entries = [];
465
- for (const part of normalized.split(";")) {
466
- const trimmed = part.trim().replace(/,\s*$/, "");
467
- if (!trimmed) continue;
468
- const match = trimmed.match(/^(?:readonly\s+)?(\w+)(\?)?:\s*(.+)$/);
469
- if (!match) continue;
470
- const [, name, opt, typeStr] = match,
471
- parsed = parseTypeString(typeStr.trim());
472
- if (!parsed) return null;
473
- let val = parsed;
474
- opt && (val = `v.optional(${val})`), entries.push(`${formatObjectKey(name)}: ${val}`);
475
- }
476
- return entries.length === 0 ? "v.object({})" : `v.object({
477
- ${entries.join(`,
478
- `)},
479
- })`;
480
- }
481
- if (type.endsWith("[]")) {
482
- const inner = parseTypeString(type.slice(0, -2).trim());
483
- return inner ? `v.array(${inner})` : null;
484
- }
485
- return null;
486
- }
487
216
  function tsTypeToValibot(ts, checker, type, seen) {
488
217
  seen || (seen = /* @__PURE__ */new Set());
489
218
  const flags = type.getFlags();
@@ -627,12 +356,14 @@ async function generate(options) {
627
356
  recursive: !0
628
357
  }), loadCache();
629
358
  const allModelFiles = readdirSync(modelsDir).filter(f => f.endsWith(".ts")).sort(),
630
- filesWithSchema = allModelFiles.filter(f => readFileSync(resolve(modelsDir, f), "utf-8").includes("export const schema = table("));
631
- let filesChanged = [writeFileIfChanged(resolve(generatedDir, "models.ts"), generateModelsFile(allModelFiles, modelsDirName)),
359
+ filesWithSchema = allModelFiles.filter(f => readFileSync(resolve(modelsDir, f), "utf-8").includes("export const schema = table(")),
360
+ allModelNames = allModelFiles.map(f => basename(f, ".ts")),
361
+ schemaModelNames = filesWithSchema.map(f => basename(f, ".ts"));
362
+ let filesChanged = [writeFileIfChanged(resolve(generatedDir, "models.ts"), generateModelsFile(allModelNames, modelsDirName)),
632
363
  // only generate types.ts and tables.ts when model files define schemas.
633
364
  // when using drizzle-zero CLI for schema generation, these files are
634
365
  // managed externally and should not be overwritten.
635
- ...(filesWithSchema.length > 0 ? [writeFileIfChanged(resolve(generatedDir, "types.ts"), generateTypesFile(filesWithSchema)), writeFileIfChanged(resolve(generatedDir, "tables.ts"), generateTablesFile(filesWithSchema, modelsDirName))] : []), writeFileIfChanged(resolve(generatedDir, "README.md"), generateReadmeFile())].filter(Boolean).length,
366
+ ...(filesWithSchema.length > 0 ? [writeFileIfChanged(resolve(generatedDir, "types.ts"), generateTypesFile(schemaModelNames)), writeFileIfChanged(resolve(generatedDir, "tables.ts"), generateTablesFile(schemaModelNames, modelsDirName))] : []), writeFileIfChanged(resolve(generatedDir, "README.md"), generateReadmeFile())].filter(Boolean).length,
636
367
  queryCount = 0,
637
368
  mutationCount = 0;
638
369
  const ts = await import("typescript"),