on-zero 0.4.1 → 0.4.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/dist/cjs/generate-helpers.cjs +309 -0
- package/dist/cjs/generate-helpers.native.js +451 -0
- package/dist/cjs/generate-helpers.native.js.map +1 -0
- package/dist/cjs/generate-lite.cjs +150 -0
- package/dist/cjs/generate-lite.native.js +269 -0
- package/dist/cjs/generate-lite.native.js.map +1 -0
- package/dist/cjs/generate-lite.test.cjs +229 -0
- package/dist/cjs/generate-lite.test.native.js +234 -0
- package/dist/cjs/generate-lite.test.native.js.map +1 -0
- package/dist/cjs/generate.cjs +16 -285
- package/dist/cjs/generate.native.js +18 -432
- package/dist/cjs/generate.native.js.map +1 -1
- package/dist/esm/generate-helpers.mjs +272 -0
- package/dist/esm/generate-helpers.mjs.map +1 -0
- package/dist/esm/generate-helpers.native.js +411 -0
- package/dist/esm/generate-helpers.native.js.map +1 -0
- package/dist/esm/generate-lite.mjs +127 -0
- package/dist/esm/generate-lite.mjs.map +1 -0
- package/dist/esm/generate-lite.native.js +243 -0
- package/dist/esm/generate-lite.native.js.map +1 -0
- package/dist/esm/generate-lite.test.mjs +230 -0
- package/dist/esm/generate-lite.test.mjs.map +1 -0
- package/dist/esm/generate-lite.test.native.js +232 -0
- package/dist/esm/generate-lite.test.native.js.map +1 -0
- package/dist/esm/generate.mjs +6 -275
- package/dist/esm/generate.mjs.map +1 -1
- package/dist/esm/generate.native.js +9 -423
- package/dist/esm/generate.native.js.map +1 -1
- package/package.json +7 -2
- package/src/generate-helpers.ts +440 -0
- package/src/generate-lite.test.ts +310 -0
- package/src/generate-lite.ts +333 -0
- package/src/generate.ts +23 -415
- package/types/generate-helpers.d.ts +42 -0
- package/types/generate-helpers.d.ts.map +1 -0
- package/types/generate-lite.d.ts +40 -0
- package/types/generate-lite.d.ts.map +1 -0
- package/types/generate-lite.test.d.ts +2 -0
- package/types/generate-lite.test.d.ts.map +1 -0
- package/types/generate.d.ts +1 -6
- 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":[]}
|
package/dist/esm/generate.mjs
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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"),
|