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.
- 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
package/src/generate.ts
CHANGED
|
@@ -2,6 +2,21 @@ 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
4
|
|
|
5
|
+
import {
|
|
6
|
+
formatObjectKey,
|
|
7
|
+
generateGroupedQueriesFile,
|
|
8
|
+
generateModelsFile,
|
|
9
|
+
generateReadmeFile,
|
|
10
|
+
generateSyncedMutationsFile,
|
|
11
|
+
generateSyncedQueriesFile,
|
|
12
|
+
generateTablesFile,
|
|
13
|
+
generateTypesFile,
|
|
14
|
+
parseColumnType,
|
|
15
|
+
parseTypeString,
|
|
16
|
+
shouldSkipObjectKey,
|
|
17
|
+
} from './generate-helpers'
|
|
18
|
+
import type { ExtractedMutation, ModelMutations, SchemaColumn } from './generate-helpers'
|
|
19
|
+
|
|
5
20
|
const hash = (s: string) => createHash('sha256').update(s).digest('hex')
|
|
6
21
|
|
|
7
22
|
let generateCache: Record<string, string> = {}
|
|
@@ -53,207 +68,8 @@ function writeFileIfChanged(filePath: string, content: string): boolean {
|
|
|
53
68
|
return true
|
|
54
69
|
}
|
|
55
70
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const getImportName = (name: string) => (name === 'user' ? 'userPublic' : name)
|
|
59
|
-
|
|
60
|
-
const imports = modelNames
|
|
61
|
-
.map(
|
|
62
|
-
(name) => `import * as ${getImportName(name)} from '../${modelsDirName}/${name}'`,
|
|
63
|
-
)
|
|
64
|
-
.join('\n')
|
|
65
|
-
|
|
66
|
-
const sortedByImportName = [...modelNames].sort((a, b) =>
|
|
67
|
-
getImportName(a).localeCompare(getImportName(b)),
|
|
68
|
-
)
|
|
69
|
-
const modelsObj = `export const models = {\n${sortedByImportName.map((name) => ` ${getImportName(name)},`).join('\n')}\n}`
|
|
70
|
-
|
|
71
|
-
return `// auto-generated by: on-zero generate\n${imports}\n\n${modelsObj}\n`
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function generateTypesFile(modelFiles: string[]) {
|
|
75
|
-
const modelNames = modelFiles.map((f) => basename(f, '.ts')).sort()
|
|
76
|
-
const getSchemaName = (name: string) => (name === 'user' ? 'userPublic' : name)
|
|
77
|
-
|
|
78
|
-
const typeExports = modelNames
|
|
79
|
-
.map((name) => {
|
|
80
|
-
const pascalName = name.charAt(0).toUpperCase() + name.slice(1)
|
|
81
|
-
const schemaName = getSchemaName(name)
|
|
82
|
-
return `export type ${pascalName} = TableInsertRow<typeof schema.${schemaName}>\nexport type ${pascalName}Update = TableUpdateRow<typeof schema.${schemaName}>`
|
|
83
|
-
})
|
|
84
|
-
.join('\n\n')
|
|
85
|
-
|
|
86
|
-
return `import type { TableInsertRow, TableUpdateRow } from 'on-zero'\nimport type * as schema from './tables'\n\n${typeExports}\n`
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function generateTablesFile(modelFiles: string[], modelsDirName: string) {
|
|
90
|
-
const modelNames = modelFiles.map((f) => basename(f, '.ts')).sort()
|
|
91
|
-
const getExportName = (name: string) => (name === 'user' ? 'userPublic' : name)
|
|
92
|
-
|
|
93
|
-
const exports = modelNames
|
|
94
|
-
.map(
|
|
95
|
-
(name) =>
|
|
96
|
-
`export { schema as ${getExportName(name)} } from '../${modelsDirName}/${name}'`,
|
|
97
|
-
)
|
|
98
|
-
.join('\n')
|
|
99
|
-
|
|
100
|
-
return `// auto-generated by: on-zero generate\n\n${exports}\n`
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function generateReadmeFile() {
|
|
104
|
-
return `# generated
|
|
105
|
-
|
|
106
|
-
this folder is auto-generated by on-zero. do not edit files here directly.
|
|
107
|
-
|
|
108
|
-
## what's generated
|
|
109
|
-
|
|
110
|
-
- \`models.ts\` - exports all models from ../models
|
|
111
|
-
- \`types.ts\` - typescript types derived from table schemas
|
|
112
|
-
- \`tables.ts\` - exports table schemas for type inference
|
|
113
|
-
- \`groupedQueries.ts\` - namespaced query re-exports for client setup
|
|
114
|
-
- \`syncedQueries.ts\` - namespaced syncedQuery wrappers for server setup
|
|
115
|
-
- \`syncedMutations.ts\` - valibot validators for mutation args (server auto-validation)
|
|
116
|
-
|
|
117
|
-
## usage guidelines
|
|
118
|
-
|
|
119
|
-
**do not import generated files outside of the data folder.**
|
|
120
|
-
|
|
121
|
-
### queries
|
|
122
|
-
|
|
123
|
-
write your queries as plain functions in \`../queries/\` and import them directly:
|
|
124
|
-
|
|
125
|
-
\`\`\`ts
|
|
126
|
-
// ✅ good - import from queries
|
|
127
|
-
import { channelMessages } from '~/data/queries/message'
|
|
128
|
-
\`\`\`
|
|
129
|
-
|
|
130
|
-
the generated query files are only used internally by zero client/server setup.
|
|
131
|
-
|
|
132
|
-
### types
|
|
133
|
-
|
|
134
|
-
you can import types from this folder, but prefer re-exporting from \`../types.ts\`:
|
|
135
|
-
|
|
136
|
-
\`\`\`ts
|
|
137
|
-
// ❌ okay but not preferred
|
|
138
|
-
import type { Message } from '~/data/generated/types'
|
|
139
|
-
|
|
140
|
-
// ✅ better - re-export from types.ts
|
|
141
|
-
import type { Message } from '~/data/types'
|
|
142
|
-
\`\`\`
|
|
143
|
-
|
|
144
|
-
## regeneration
|
|
145
|
-
|
|
146
|
-
files are regenerated when you run:
|
|
147
|
-
|
|
148
|
-
\`\`\`bash
|
|
149
|
-
bun on-zero generate
|
|
150
|
-
\`\`\`
|
|
151
|
-
|
|
152
|
-
or in watch mode:
|
|
153
|
-
|
|
154
|
-
\`\`\`bash
|
|
155
|
-
bun on-zero generate --watch
|
|
156
|
-
\`\`\`
|
|
157
|
-
|
|
158
|
-
## more info
|
|
159
|
-
|
|
160
|
-
see the [on-zero readme](./node_modules/on-zero/README.md) for full documentation.
|
|
161
|
-
`
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function generateGroupedQueriesFile(
|
|
165
|
-
queries: Array<{ name: string; sourceFile: string }>,
|
|
166
|
-
) {
|
|
167
|
-
const sortedFiles = [...new Set(queries.map((q) => q.sourceFile))].sort()
|
|
168
|
-
|
|
169
|
-
const exports = sortedFiles
|
|
170
|
-
.map((file) => `export * as ${file} from '../queries/${file}'`)
|
|
171
|
-
.join('\n')
|
|
172
|
-
|
|
173
|
-
return `/**
|
|
174
|
-
* auto-generated by: on-zero generate
|
|
175
|
-
*
|
|
176
|
-
* grouped query re-exports for minification-safe query identity.
|
|
177
|
-
* this file re-exports all query modules - while this breaks tree-shaking,
|
|
178
|
-
* queries are typically small and few in number even in larger apps.
|
|
179
|
-
*/
|
|
180
|
-
${exports}
|
|
181
|
-
`
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function generateSyncedQueriesFile(
|
|
185
|
-
queries: Array<{
|
|
186
|
-
name: string
|
|
187
|
-
params: string
|
|
188
|
-
valibotCode: string
|
|
189
|
-
sourceFile: string
|
|
190
|
-
}>,
|
|
191
|
-
) {
|
|
192
|
-
const queryByFile = new Map<string, typeof queries>()
|
|
193
|
-
for (const q of queries) {
|
|
194
|
-
if (!queryByFile.has(q.sourceFile)) {
|
|
195
|
-
queryByFile.set(q.sourceFile, [])
|
|
196
|
-
}
|
|
197
|
-
queryByFile.get(q.sourceFile)!.push(q)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const sortedFiles = Array.from(queryByFile.keys()).sort()
|
|
201
|
-
|
|
202
|
-
const imports = `// auto-generated by: on-zero generate
|
|
203
|
-
// server-side query definitions with validators
|
|
204
|
-
import { defineQuery, defineQueries } from '@rocicorp/zero'
|
|
205
|
-
import * as v from 'valibot'
|
|
206
|
-
import * as Queries from './groupedQueries'
|
|
207
|
-
`
|
|
208
|
-
|
|
209
|
-
const namespaceDefs = sortedFiles
|
|
210
|
-
.map((file) => {
|
|
211
|
-
const fileQueries = queryByFile
|
|
212
|
-
.get(file)!
|
|
213
|
-
.sort((a, b) => a.name.localeCompare(b.name))
|
|
214
|
-
|
|
215
|
-
const queryDefs = fileQueries
|
|
216
|
-
.map((q) => {
|
|
217
|
-
const validatorDef = q.valibotCode.trim()
|
|
218
|
-
|
|
219
|
-
if (q.params === 'void' || !validatorDef) {
|
|
220
|
-
return ` ${q.name}: defineQuery(() => Queries.${file}.${q.name}()),`
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const indentedValidator = validatorDef
|
|
224
|
-
.split('\n')
|
|
225
|
-
.map((line, i) => (i === 0 ? line : ` ${line}`))
|
|
226
|
-
.join('\n')
|
|
227
|
-
|
|
228
|
-
return ` ${q.name}: defineQuery(
|
|
229
|
-
${indentedValidator},
|
|
230
|
-
({ args }) => Queries.${file}.${q.name}(args)
|
|
231
|
-
),`
|
|
232
|
-
})
|
|
233
|
-
.join('\n')
|
|
234
|
-
|
|
235
|
-
return `const ${file} = {\n${queryDefs}\n}`
|
|
236
|
-
})
|
|
237
|
-
.join('\n\n')
|
|
238
|
-
|
|
239
|
-
const queriesObject = sortedFiles.map((file) => ` ${file},`).join('\n')
|
|
240
|
-
|
|
241
|
-
return `${imports}
|
|
242
|
-
${namespaceDefs}
|
|
243
|
-
|
|
244
|
-
export const queries = defineQueries({
|
|
245
|
-
${queriesObject}
|
|
246
|
-
})
|
|
247
|
-
`
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// used by valibot mutation validator codegen (subset of SchemaColumn below)
|
|
251
|
-
|
|
252
|
-
type ExtractedMutation = {
|
|
253
|
-
name: string
|
|
254
|
-
paramType: string // 'void' | inline type string | unresolved reference
|
|
255
|
-
valibotCode: string // generated valibot code, or '' if skipped
|
|
256
|
-
}
|
|
71
|
+
// file-content emitters and valibot helpers are imported from ./generate-helpers
|
|
72
|
+
// so they can be shared with the browser-safe generate-lite entry point.
|
|
257
73
|
|
|
258
74
|
// creates a TypeChecker that can resolve type references across files
|
|
259
75
|
function createTypeResolver(
|
|
@@ -419,14 +235,6 @@ function resolveMutationParamTypes(
|
|
|
419
235
|
return resolved
|
|
420
236
|
}
|
|
421
237
|
|
|
422
|
-
type ModelMutations = {
|
|
423
|
-
modelName: string
|
|
424
|
-
hasCRUD: boolean
|
|
425
|
-
columns: Record<string, SchemaColumn> // populated when hasCRUD
|
|
426
|
-
primaryKeys: string[]
|
|
427
|
-
custom: ExtractedMutation[]
|
|
428
|
-
}
|
|
429
|
-
|
|
430
238
|
function extractMutationsFromModel(
|
|
431
239
|
ts: typeof import('typescript'),
|
|
432
240
|
sourceFile: ReturnType<typeof ts.createSourceFile>,
|
|
@@ -594,202 +402,6 @@ function extractSchemaColumns(
|
|
|
594
402
|
visit(sourceFile)
|
|
595
403
|
}
|
|
596
404
|
|
|
597
|
-
function parseColumnType(initText: string): SchemaColumn {
|
|
598
|
-
const optional = initText.includes('.optional()')
|
|
599
|
-
let type: SchemaColumn['type'] = 'string'
|
|
600
|
-
|
|
601
|
-
if (initText.startsWith('number(')) type = 'number'
|
|
602
|
-
else if (initText.startsWith('boolean(')) type = 'boolean'
|
|
603
|
-
else if (initText.startsWith('json(') || initText.startsWith('json<')) type = 'json'
|
|
604
|
-
else if (initText.startsWith('enumeration(')) type = 'enum'
|
|
605
|
-
|
|
606
|
-
return { type, optional, customType: undefined }
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
function columnTypeToValibot(col: SchemaColumn): string {
|
|
610
|
-
let base = 'v.string()'
|
|
611
|
-
switch (col.type) {
|
|
612
|
-
case 'string':
|
|
613
|
-
base = 'v.string()'
|
|
614
|
-
break
|
|
615
|
-
case 'number':
|
|
616
|
-
base = 'v.number()'
|
|
617
|
-
break
|
|
618
|
-
case 'boolean':
|
|
619
|
-
base = 'v.boolean()'
|
|
620
|
-
break
|
|
621
|
-
case 'json':
|
|
622
|
-
base = 'v.unknown()'
|
|
623
|
-
break
|
|
624
|
-
case 'enum':
|
|
625
|
-
base = 'v.string()'
|
|
626
|
-
break
|
|
627
|
-
}
|
|
628
|
-
return col.optional ? `v.optional(v.nullable(${base}))` : base
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
function shouldSkipObjectKey(name: string): boolean {
|
|
632
|
-
// TypeScript exposes symbol keys as synthetic names like "__@iterator@851".
|
|
633
|
-
// These cannot come from JSON mutation payloads and break codegen if emitted.
|
|
634
|
-
return name.startsWith('__@')
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
function formatObjectKey(name: string): string {
|
|
638
|
-
return /^[$A-Z_a-z][$\w]*$/.test(name) ? name : JSON.stringify(name)
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
function schemaColumnsToValibot(
|
|
642
|
-
columns: Record<string, SchemaColumn>,
|
|
643
|
-
primaryKeys: string[],
|
|
644
|
-
mode: 'insert' | 'update' | 'delete',
|
|
645
|
-
): string {
|
|
646
|
-
const entries: string[] = []
|
|
647
|
-
|
|
648
|
-
if (mode === 'delete') {
|
|
649
|
-
// only PKs
|
|
650
|
-
for (const pk of primaryKeys) {
|
|
651
|
-
const col = columns[pk]
|
|
652
|
-
if (col)
|
|
653
|
-
entries.push(
|
|
654
|
-
`${formatObjectKey(pk)}: ${columnTypeToValibot({ ...col, optional: false })}`,
|
|
655
|
-
)
|
|
656
|
-
}
|
|
657
|
-
} else if (mode === 'update') {
|
|
658
|
-
// PKs required, rest optional
|
|
659
|
-
for (const [name, col] of Object.entries(columns)) {
|
|
660
|
-
const isPK = primaryKeys.includes(name)
|
|
661
|
-
if (isPK) {
|
|
662
|
-
entries.push(
|
|
663
|
-
`${formatObjectKey(name)}: ${columnTypeToValibot({ ...col, optional: false })}`,
|
|
664
|
-
)
|
|
665
|
-
} else {
|
|
666
|
-
entries.push(
|
|
667
|
-
`${formatObjectKey(name)}: ${columnTypeToValibot({ ...col, optional: true })}`,
|
|
668
|
-
)
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
} else {
|
|
672
|
-
// insert: all columns as-is
|
|
673
|
-
for (const [name, col] of Object.entries(columns)) {
|
|
674
|
-
entries.push(`${formatObjectKey(name)}: ${columnTypeToValibot(col)}`)
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
return `v.object({\n ${entries.join(',\n ')},\n })`
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
function generateSyncedMutationsFile(modelMutations: ModelMutations[]) {
|
|
682
|
-
const sorted = [...modelMutations].sort((a, b) =>
|
|
683
|
-
a.modelName.localeCompare(b.modelName),
|
|
684
|
-
)
|
|
685
|
-
|
|
686
|
-
const modelDefs = sorted
|
|
687
|
-
.map((model) => {
|
|
688
|
-
const entries: string[] = []
|
|
689
|
-
|
|
690
|
-
// CRUD validators from schema
|
|
691
|
-
if (model.hasCRUD && Object.keys(model.columns).length > 0) {
|
|
692
|
-
for (const mode of ['insert', 'update', 'delete'] as const) {
|
|
693
|
-
// skip if custom mutation overrides this CRUD op
|
|
694
|
-
const hasCustomOverride = model.custom.some((m) => m.name === mode)
|
|
695
|
-
if (hasCustomOverride) {
|
|
696
|
-
// use the custom version's validator instead
|
|
697
|
-
const customMut = model.custom.find((m) => m.name === mode)!
|
|
698
|
-
if (customMut.valibotCode) {
|
|
699
|
-
entries.push(
|
|
700
|
-
` ${mode}: ${extractValibotExpression(customMut.valibotCode)},`,
|
|
701
|
-
)
|
|
702
|
-
} else {
|
|
703
|
-
// fall back to schema-derived
|
|
704
|
-
entries.push(
|
|
705
|
-
` ${mode}: ${schemaColumnsToValibot(model.columns, model.primaryKeys, mode)},`,
|
|
706
|
-
)
|
|
707
|
-
}
|
|
708
|
-
} else {
|
|
709
|
-
entries.push(
|
|
710
|
-
` ${mode}: ${schemaColumnsToValibot(model.columns, model.primaryKeys, mode)},`,
|
|
711
|
-
)
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
// custom mutations (excluding CRUD overrides already handled)
|
|
717
|
-
for (const mut of model.custom) {
|
|
718
|
-
if (model.hasCRUD && ['insert', 'update', 'delete', 'upsert'].includes(mut.name))
|
|
719
|
-
continue
|
|
720
|
-
if (mut.paramType === 'void' || !mut.valibotCode) {
|
|
721
|
-
entries.push(` ${mut.name}: v.void_(),`)
|
|
722
|
-
continue
|
|
723
|
-
}
|
|
724
|
-
entries.push(` ${mut.name}: ${extractValibotExpression(mut.valibotCode)},`)
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
return ` ${model.modelName}: {\n${entries.join('\n')}\n },`
|
|
728
|
-
})
|
|
729
|
-
.join('\n')
|
|
730
|
-
|
|
731
|
-
return `// auto-generated by: on-zero generate
|
|
732
|
-
// mutation validators derived from model schemas and handler types
|
|
733
|
-
import * as v from 'valibot'
|
|
734
|
-
|
|
735
|
-
export const mutationValidators = {
|
|
736
|
-
${modelDefs}
|
|
737
|
-
}
|
|
738
|
-
`
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
// returns as-is since output is already a valibot expression
|
|
742
|
-
function extractValibotExpression(valibotCode: string): string {
|
|
743
|
-
return valibotCode.trim() || 'v.unknown()'
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
// simple string-based type parser for inline type annotations from source text
|
|
747
|
-
// handles: primitives, inline objects { ... }, arrays, unions
|
|
748
|
-
// returns null for type references that need the TS checker
|
|
749
|
-
function parseTypeString(type: string): string | null {
|
|
750
|
-
type = type.trim()
|
|
751
|
-
|
|
752
|
-
// primitives
|
|
753
|
-
if (type === 'string') return 'v.string()'
|
|
754
|
-
if (type === 'number') return 'v.number()'
|
|
755
|
-
if (type === 'boolean') return 'v.boolean()'
|
|
756
|
-
if (type === 'void' || type === 'undefined') return 'v.void_()'
|
|
757
|
-
if (type === 'null') return 'v.null_()'
|
|
758
|
-
if (type === 'any' || type === 'unknown') return 'v.unknown()'
|
|
759
|
-
|
|
760
|
-
// inline object: { key: type; ... }
|
|
761
|
-
if (type.startsWith('{') && type.endsWith('}')) {
|
|
762
|
-
const inner = type.slice(1, -1).trim()
|
|
763
|
-
if (!inner) return 'v.object({})'
|
|
764
|
-
// normalize newlines/commas to semicolons for splitting
|
|
765
|
-
const normalized = inner.replace(/\n/g, '; ').replace(/;\s*;/g, ';')
|
|
766
|
-
const entries: string[] = []
|
|
767
|
-
for (const part of normalized.split(';')) {
|
|
768
|
-
const trimmed = part.trim().replace(/,\s*$/, '')
|
|
769
|
-
if (!trimmed) continue
|
|
770
|
-
const match = trimmed.match(/^(?:readonly\s+)?(\w+)(\?)?:\s*(.+)$/)
|
|
771
|
-
if (!match) continue
|
|
772
|
-
const [, name, opt, typeStr] = match
|
|
773
|
-
const parsed = parseTypeString(typeStr!.trim())
|
|
774
|
-
if (!parsed) return null // can't resolve inner type
|
|
775
|
-
let val = parsed
|
|
776
|
-
if (opt) val = `v.optional(${val})`
|
|
777
|
-
entries.push(`${formatObjectKey(name)}: ${val}`)
|
|
778
|
-
}
|
|
779
|
-
if (entries.length === 0) return 'v.object({})'
|
|
780
|
-
return `v.object({\n ${entries.join(',\n ')},\n })`
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
// array: T[]
|
|
784
|
-
if (type.endsWith('[]')) {
|
|
785
|
-
const inner = parseTypeString(type.slice(0, -2).trim())
|
|
786
|
-
return inner ? `v.array(${inner})` : null
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
// unrecognized (type reference, complex generic, etc)
|
|
790
|
-
return null
|
|
791
|
-
}
|
|
792
|
-
|
|
793
405
|
// convert a ts.Type to valibot code by walking the type checker AST
|
|
794
406
|
function tsTypeToValibot(
|
|
795
407
|
ts: typeof import('typescript'),
|
|
@@ -952,13 +564,6 @@ function tsTypeToValibot(
|
|
|
952
564
|
return 'v.unknown()'
|
|
953
565
|
}
|
|
954
566
|
|
|
955
|
-
type SchemaColumn = {
|
|
956
|
-
type: string
|
|
957
|
-
optional: boolean
|
|
958
|
-
customType: unknown
|
|
959
|
-
serverName?: string
|
|
960
|
-
}
|
|
961
|
-
|
|
962
567
|
type SchemaTable = {
|
|
963
568
|
name: string
|
|
964
569
|
columns: Record<string, SchemaColumn>
|
|
@@ -1145,10 +750,13 @@ export async function generate(options: GenerateOptions): Promise<GenerateResult
|
|
|
1145
750
|
readFileSync(resolve(modelsDir, f), 'utf-8').includes('export const schema = table('),
|
|
1146
751
|
)
|
|
1147
752
|
|
|
753
|
+
const allModelNames = allModelFiles.map((f) => basename(f, '.ts'))
|
|
754
|
+
const schemaModelNames = filesWithSchema.map((f) => basename(f, '.ts'))
|
|
755
|
+
|
|
1148
756
|
const writeResults = [
|
|
1149
757
|
writeFileIfChanged(
|
|
1150
758
|
resolve(generatedDir, 'models.ts'),
|
|
1151
|
-
generateModelsFile(
|
|
759
|
+
generateModelsFile(allModelNames, modelsDirName),
|
|
1152
760
|
),
|
|
1153
761
|
// only generate types.ts and tables.ts when model files define schemas.
|
|
1154
762
|
// when using drizzle-zero CLI for schema generation, these files are
|
|
@@ -1157,11 +765,11 @@ export async function generate(options: GenerateOptions): Promise<GenerateResult
|
|
|
1157
765
|
? [
|
|
1158
766
|
writeFileIfChanged(
|
|
1159
767
|
resolve(generatedDir, 'types.ts'),
|
|
1160
|
-
generateTypesFile(
|
|
768
|
+
generateTypesFile(schemaModelNames),
|
|
1161
769
|
),
|
|
1162
770
|
writeFileIfChanged(
|
|
1163
771
|
resolve(generatedDir, 'tables.ts'),
|
|
1164
|
-
generateTablesFile(
|
|
772
|
+
generateTablesFile(schemaModelNames, modelsDirName),
|
|
1165
773
|
),
|
|
1166
774
|
]
|
|
1167
775
|
: []),
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type SchemaColumn = {
|
|
2
|
+
type: string;
|
|
3
|
+
optional: boolean;
|
|
4
|
+
customType: unknown;
|
|
5
|
+
serverName?: string;
|
|
6
|
+
};
|
|
7
|
+
export type ExtractedMutation = {
|
|
8
|
+
name: string;
|
|
9
|
+
paramType: string;
|
|
10
|
+
valibotCode: string;
|
|
11
|
+
};
|
|
12
|
+
export type ModelMutations = {
|
|
13
|
+
modelName: string;
|
|
14
|
+
hasCRUD: boolean;
|
|
15
|
+
columns: Record<string, SchemaColumn>;
|
|
16
|
+
primaryKeys: string[];
|
|
17
|
+
custom: ExtractedMutation[];
|
|
18
|
+
};
|
|
19
|
+
export declare function shouldSkipObjectKey(name: string): boolean;
|
|
20
|
+
export declare function formatObjectKey(name: string): string;
|
|
21
|
+
export declare function getModelImportName(name: string): string;
|
|
22
|
+
export declare function parseTypeString(type: string): string | null;
|
|
23
|
+
export declare function generateModelsFile(modelNames: string[], modelsDirName: string): string;
|
|
24
|
+
export declare function generateTypesFile(modelNames: string[]): string;
|
|
25
|
+
export declare function generateTablesFile(modelNames: string[], modelsDirName: string): string;
|
|
26
|
+
export declare function generateReadmeFile(): string;
|
|
27
|
+
export declare function generateGroupedQueriesFile(queries: Array<{
|
|
28
|
+
name: string;
|
|
29
|
+
sourceFile: string;
|
|
30
|
+
}>): string;
|
|
31
|
+
export declare function generateSyncedQueriesFile(queries: Array<{
|
|
32
|
+
name: string;
|
|
33
|
+
params: string;
|
|
34
|
+
valibotCode: string;
|
|
35
|
+
sourceFile: string;
|
|
36
|
+
}>): string;
|
|
37
|
+
export declare function columnTypeToValibot(col: SchemaColumn): string;
|
|
38
|
+
export declare function schemaColumnsToValibot(columns: Record<string, SchemaColumn>, primaryKeys: string[], mode: 'insert' | 'update' | 'delete'): string;
|
|
39
|
+
export declare function extractValibotExpression(valibotCode: string): string;
|
|
40
|
+
export declare function parseColumnType(initText: string): SchemaColumn;
|
|
41
|
+
export declare function generateSyncedMutationsFile(modelMutations: ModelMutations[]): string;
|
|
42
|
+
//# sourceMappingURL=generate-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-helpers.d.ts","sourceRoot":"","sources":["../src/generate-helpers.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,OAAO,CAAA;IACjB,UAAU,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAA;IAEZ,SAAS,EAAE,MAAM,CAAA;IAEjB,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAEhB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IACrC,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,MAAM,EAAE,iBAAiB,EAAE,CAAA;CAC5B,CAAA;AAID,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAIzD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAGD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAQD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA0C3D;AAMD,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAkBtF;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAY9D;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAWtF;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CA2D3C;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,GACnD,MAAM,CAgBR;AAED,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,KAAK,CAAC;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;CACnB,CAAC,GACD,MAAM,CAyDR;AAID,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAoB7D;AAED,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,EACrC,WAAW,EAAE,MAAM,EAAE,EACrB,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GACnC,MAAM,CAkCR;AAGD,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEpE;AAKD,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,CAU9D;AAED,wBAAgB,2BAA2B,CAAC,cAAc,EAAE,cAAc,EAAE,GAAG,MAAM,CA0DpF"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type LiteMutationExport = {
|
|
2
|
+
modelName: string;
|
|
3
|
+
handlers: Array<{
|
|
4
|
+
name: string;
|
|
5
|
+
paramTypeText: string | null;
|
|
6
|
+
}>;
|
|
7
|
+
schema: LiteSchemaInfo | null;
|
|
8
|
+
};
|
|
9
|
+
export type LiteSchemaInfo = {
|
|
10
|
+
tableName: string;
|
|
11
|
+
primaryKeys: string[];
|
|
12
|
+
columns: Array<{
|
|
13
|
+
name: string;
|
|
14
|
+
builderText: string;
|
|
15
|
+
}>;
|
|
16
|
+
};
|
|
17
|
+
export type LiteQueryExport = {
|
|
18
|
+
name: string;
|
|
19
|
+
paramTypeText: string | null;
|
|
20
|
+
};
|
|
21
|
+
export type LiteParsedFile = {
|
|
22
|
+
mutations: LiteMutationExport[];
|
|
23
|
+
queries: LiteQueryExport[];
|
|
24
|
+
};
|
|
25
|
+
export type LiteParseFn = (sourceCode: string, filePath: string) => LiteParsedFile;
|
|
26
|
+
export type LiteGenerateOptions = {
|
|
27
|
+
files: Record<string, string>;
|
|
28
|
+
dir: string;
|
|
29
|
+
modelsDir?: 'mutations' | 'models';
|
|
30
|
+
parse: LiteParseFn;
|
|
31
|
+
};
|
|
32
|
+
export type LiteGenerateResult = {
|
|
33
|
+
files: Record<string, string>;
|
|
34
|
+
modelCount: number;
|
|
35
|
+
queryCount: number;
|
|
36
|
+
mutationCount: number;
|
|
37
|
+
schemaCount: number;
|
|
38
|
+
};
|
|
39
|
+
export declare function generateLite(opts: LiteGenerateOptions): LiteGenerateResult;
|
|
40
|
+
//# sourceMappingURL=generate-lite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-lite.d.ts","sourceRoot":"","sources":["../src/generate-lite.ts"],"names":[],"mappings":"AAgCA,MAAM,MAAM,kBAAkB,GAAG;IAK/B,SAAS,EAAE,MAAM,CAAA;IAEjB,QAAQ,EAAE,KAAK,CAAC;QAEd,IAAI,EAAE,MAAM,CAAA;QAMZ,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;KAC7B,CAAC,CAAA;IAKF,MAAM,EAAE,cAAc,GAAG,IAAI,CAAA;CAC9B,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,EAAE,CAAA;IAGrB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACtD,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAE5B,IAAI,EAAE,MAAM,CAAA;IAIZ,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B,CAAA;AAGD,MAAM,MAAM,cAAc,GAAG;IAG3B,SAAS,EAAE,kBAAkB,EAAE,CAAA;IAC/B,OAAO,EAAE,eAAe,EAAE,CAAA;CAC3B,CAAA;AAID,MAAM,MAAM,WAAW,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,cAAc,CAAA;AAElF,MAAM,MAAM,mBAAmB,GAAG;IAKhC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAG7B,GAAG,EAAE,MAAM,CAAA;IAIX,SAAS,CAAC,EAAE,WAAW,GAAG,QAAQ,CAAA;IAClC,KAAK,EAAE,WAAW,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAG/B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAoCD,wBAAgB,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,kBAAkB,CA2L1E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-lite.test.d.ts","sourceRoot":"","sources":["../src/generate-lite.test.ts"],"names":[],"mappings":""}
|
package/types/generate.d.ts
CHANGED
package/types/generate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAqC,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAqiBzF,KAAK,WAAW,GAAG;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IACrC,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB,CAAA;AAED,KAAK,iBAAiB,GAAG;IACvB,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,KAAK,GAAG,MAAM,CAAA;CAC5B,CAAA;AAED,KAAK,iBAAiB,GAAG;IACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;IACnC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAA;CACnE,CAAA;AAkCD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAiF3E;AAED,MAAM,WAAW,eAAe;IAC9B,0BAA0B;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,2BAA2B;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,sBAAsB;IACtB,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,YAAa,SAAQ,eAAe;IACnD,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAiVhF;AAED,wBAAsB,KAAK,CAAC,OAAO,EAAE,YAAY,yCAmChD"}
|