on-zero 0.1.22 → 0.1.23

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 (42) hide show
  1. package/dist/cjs/cli.cjs +17 -424
  2. package/dist/cjs/cli.js +7 -402
  3. package/dist/cjs/cli.js.map +2 -2
  4. package/dist/cjs/cli.native.js +15 -519
  5. package/dist/cjs/cli.native.js.map +1 -1
  6. package/dist/cjs/generate.cjs +370 -0
  7. package/dist/cjs/generate.js +339 -0
  8. package/dist/cjs/generate.js.map +6 -0
  9. package/dist/cjs/generate.native.js +464 -0
  10. package/dist/cjs/generate.native.js.map +1 -0
  11. package/dist/cjs/vite-plugin.cjs +37 -121
  12. package/dist/cjs/vite-plugin.js +41 -100
  13. package/dist/cjs/vite-plugin.js.map +1 -1
  14. package/dist/cjs/vite-plugin.native.js +47 -157
  15. package/dist/cjs/vite-plugin.native.js.map +1 -1
  16. package/dist/esm/cli.js +8 -388
  17. package/dist/esm/cli.js.map +2 -2
  18. package/dist/esm/cli.mjs +17 -402
  19. package/dist/esm/cli.mjs.map +1 -1
  20. package/dist/esm/cli.native.js +15 -497
  21. package/dist/esm/cli.native.js.map +1 -1
  22. package/dist/esm/generate.js +317 -0
  23. package/dist/esm/generate.js.map +6 -0
  24. package/dist/esm/generate.mjs +335 -0
  25. package/dist/esm/generate.mjs.map +1 -0
  26. package/dist/esm/generate.native.js +426 -0
  27. package/dist/esm/generate.native.js.map +1 -0
  28. package/dist/esm/vite-plugin.js +42 -102
  29. package/dist/esm/vite-plugin.js.map +1 -1
  30. package/dist/esm/vite-plugin.mjs +37 -121
  31. package/dist/esm/vite-plugin.mjs.map +1 -1
  32. package/dist/esm/vite-plugin.native.js +47 -157
  33. package/dist/esm/vite-plugin.native.js.map +1 -1
  34. package/package.json +2 -2
  35. package/readme.md +0 -29
  36. package/src/cli.ts +9 -646
  37. package/src/generate.ts +490 -0
  38. package/src/vite-plugin.ts +61 -189
  39. package/types/generate.d.ts +21 -0
  40. package/types/generate.d.ts.map +1 -0
  41. package/types/vite-plugin.d.ts +6 -29
  42. package/types/vite-plugin.d.ts.map +1 -1
package/src/cli.ts CHANGED
@@ -1,164 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { createHash } from 'node:crypto'
3
- import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
4
- import { basename, resolve } from 'node:path'
2
+ import { resolve } from 'node:path'
5
3
 
6
- import { ModelToValibot } from '@sinclair/typebox-codegen/model'
7
- import { TypeScriptToModel } from '@sinclair/typebox-codegen/typescript'
8
4
  import { defineCommand, runMain } from 'citty'
9
- import * as ts from 'typescript'
10
5
 
11
- const hash = (s: string) => createHash('sha256').update(s).digest('hex')
6
+ import { generate, watch } from './generate'
12
7
 
13
- /**
14
- * cache of raw generated content hashes so we can skip writes when
15
- * a formatter (--after) rewrites files to a different style.
16
- * without this, the raw output never matches the formatted file on disk
17
- * and every regeneration triggers unnecessary file watcher events.
18
- */
19
- let generateCache: Record<string, string> = {}
20
- let generateCachePath = ''
21
-
22
- function getCacheDir() {
23
- // walk up from cwd to find nearest node_modules
24
- let dir = process.cwd()
25
- while (dir !== '/') {
26
- const nm = resolve(dir, 'node_modules')
27
- if (existsSync(nm)) {
28
- const cacheDir = resolve(nm, '.on-zero')
29
- if (!existsSync(cacheDir)) {
30
- mkdirSync(cacheDir, { recursive: true })
31
- }
32
- return cacheDir
33
- }
34
- dir = resolve(dir, '..')
35
- }
36
- return null
37
- }
38
-
39
- function loadCache() {
40
- const cacheDir = getCacheDir()
41
- if (!cacheDir) return
42
- generateCachePath = resolve(cacheDir, 'generate-cache.json')
43
- try {
44
- generateCache = JSON.parse(readFileSync(generateCachePath, 'utf-8'))
45
- } catch {
46
- generateCache = {}
47
- }
48
- }
49
-
50
- function saveCache() {
51
- if (generateCachePath) {
52
- writeFileSync(generateCachePath, JSON.stringify(generateCache) + '\n', 'utf-8')
53
- }
54
- }
55
-
56
- /**
57
- * Write file only if the content has changed.
58
- * Uses a hash cache of raw generated content to avoid false positives
59
- * when a formatter rewrites files to a different style.
60
- */
61
- function writeFileIfChanged(filePath: string, content: string): boolean {
62
- const contentHash = hash(content)
63
- const cachedHash = generateCache[filePath]
64
-
65
- if (cachedHash === contentHash && existsSync(filePath)) {
66
- return false // raw content unchanged, formatted file on disk is fine
67
- }
68
-
69
- writeFileSync(filePath, content, 'utf-8')
70
- generateCache[filePath] = contentHash
71
- return true // file was written
72
- }
73
-
74
- const generateQueries = defineCommand({
75
- meta: {
76
- name: 'generate-queries',
77
- description: 'Generate server-side query validators from TypeScript query functions',
78
- },
79
- args: {
80
- dir: {
81
- type: 'positional',
82
- description: 'Directory containing query files',
83
- required: false,
84
- default: '.',
85
- },
86
- },
87
- async run({ args }) {
88
- const dir = resolve(args.dir)
89
-
90
- const { readdirSync } = await import('node:fs')
91
-
92
- const files = readdirSync(dir).filter((f) => f.endsWith('.ts'))
93
-
94
- const allQueries: Array<{ name: string; params: string; valibotCode: string }> = []
95
-
96
- // process files in parallel
97
- const results = await Promise.all(
98
- files.map(async (file) => {
99
- const filePath = resolve(dir, file)
100
- const queries: typeof allQueries = []
101
-
102
- try {
103
- const content = readFileSync(filePath, 'utf-8')
104
-
105
- const sourceFile = ts.createSourceFile(
106
- filePath,
107
- content,
108
- ts.ScriptTarget.Latest,
109
- true
110
- )
111
-
112
- ts.forEachChild(sourceFile, (node) => {
113
- if (ts.isVariableStatement(node)) {
114
- const exportModifier = node.modifiers?.find(
115
- (m) => m.kind === ts.SyntaxKind.ExportKeyword
116
- )
117
- if (!exportModifier) return
118
-
119
- const declaration = node.declarationList.declarations[0]
120
- if (!declaration || !ts.isVariableDeclaration(declaration)) return
121
-
122
- const name = declaration.name.getText(sourceFile)
123
-
124
- if (
125
- declaration.initializer &&
126
- ts.isArrowFunction(declaration.initializer)
127
- ) {
128
- const params = declaration.initializer.parameters
129
- let paramType = 'void'
130
-
131
- if (params.length > 0) {
132
- const param = params[0]!
133
- paramType = param.type?.getText(sourceFile) || 'unknown'
134
- }
135
-
136
- try {
137
- const typeString = `type QueryParams = ${paramType}`
138
- const model = TypeScriptToModel.Generate(typeString)
139
- const valibotCode = ModelToValibot.Generate(model)
140
-
141
- queries.push({ name, params: paramType, valibotCode })
142
- } catch (err) {
143
- console.error(`✗ ${name}: ${err}`)
144
- }
145
- }
146
- }
147
- })
148
- } catch (err) {
149
- console.error(`Error processing ${file}:`, err)
150
- }
151
-
152
- return queries
153
- })
154
- )
155
-
156
- allQueries.push(...results.flat())
157
- console.info(`✓ ${allQueries.length} query validators`)
158
- },
159
- })
160
-
161
- const generate = defineCommand({
8
+ const generateCommand = defineCommand({
162
9
  meta: {
163
10
  name: 'generate',
164
11
  description: 'Generate models, types, tables, and query validators',
@@ -182,510 +29,26 @@ const generate = defineCommand({
182
29
  required: false,
183
30
  },
184
31
  },
185
- async run({ args }) {
186
- const baseDir = resolve(args.dir)
187
- const modelsDir = resolve(baseDir, 'models')
188
- const generatedDir = resolve(baseDir, 'generated')
189
- const queriesDir = resolve(baseDir, 'queries')
190
-
191
- const runGenerate = async (options?: { silent?: boolean; runAfter?: boolean }) => {
192
- const silent = options?.silent ?? false
193
- const runAfter = options?.runAfter ?? !silent
194
- // ensure generated dir exists
195
- if (!existsSync(generatedDir)) {
196
- mkdirSync(generatedDir, { recursive: true })
197
- }
198
-
199
- loadCache()
200
-
201
- // read all model files and check for schemas in parallel
202
- const allModelFiles = readdirSync(modelsDir)
203
- .filter((f) => f.endsWith('.ts'))
204
- .sort()
205
-
206
- const schemaChecks = await Promise.all(
207
- allModelFiles.map(async (f) => ({
208
- file: f,
209
- hasSchema: readFileSync(resolve(modelsDir, f), 'utf-8').includes(
210
- 'export const schema = table('
211
- ),
212
- }))
213
- )
214
-
215
- const filesWithSchema = schemaChecks.filter((c) => c.hasSchema).map((c) => c.file)
216
-
217
- // generate all files in parallel
218
- const [modelsOutput, typesOutput, tablesOutput, readmeOutput] = await Promise.all([
219
- Promise.resolve(generateModelsFile(allModelFiles)),
220
- Promise.resolve(generateTypesFile(filesWithSchema)),
221
- Promise.resolve(generateTablesFile(filesWithSchema)),
222
- Promise.resolve(generateReadmeFile()),
223
- ])
224
-
225
- // write all generated files in parallel
226
- const writeResults = await Promise.all([
227
- Promise.resolve(
228
- writeFileIfChanged(resolve(generatedDir, 'models.ts'), modelsOutput)
229
- ),
230
- Promise.resolve(
231
- writeFileIfChanged(resolve(generatedDir, 'types.ts'), typesOutput)
232
- ),
233
- Promise.resolve(
234
- writeFileIfChanged(resolve(generatedDir, 'tables.ts'), tablesOutput)
235
- ),
236
- Promise.resolve(
237
- writeFileIfChanged(resolve(generatedDir, 'README.md'), readmeOutput)
238
- ),
239
- ])
240
-
241
- const filesChanged = writeResults.filter(Boolean).length
242
- if (filesChanged > 0 && !silent) {
243
- console.info(` 📝 Updated ${filesChanged} file(s)`)
244
- }
245
-
246
- // generate synced queries
247
- if (existsSync(queriesDir)) {
248
- const queryFiles = readdirSync(queriesDir).filter((f) => f.endsWith('.ts'))
249
-
250
- // process query files in parallel
251
- const queryResults = await Promise.all(
252
- queryFiles.map(async (file) => {
253
- const filePath = resolve(queriesDir, file)
254
- const fileBaseName = basename(file, '.ts')
255
- const queries: Array<{
256
- name: string
257
- params: string
258
- valibotCode: string
259
- sourceFile: string
260
- }> = []
261
-
262
- try {
263
- const content = readFileSync(filePath, 'utf-8')
264
-
265
- const sourceFile = ts.createSourceFile(
266
- filePath,
267
- content,
268
- ts.ScriptTarget.Latest,
269
- true
270
- )
271
-
272
- ts.forEachChild(sourceFile, (node) => {
273
- if (ts.isVariableStatement(node)) {
274
- const exportModifier = node.modifiers?.find(
275
- (m) => m.kind === ts.SyntaxKind.ExportKeyword
276
- )
277
- if (!exportModifier) return
278
-
279
- const declaration = node.declarationList.declarations[0]
280
- if (!declaration || !ts.isVariableDeclaration(declaration)) return
281
-
282
- const name = declaration.name.getText(sourceFile)
283
-
284
- // skip 'permission' exports
285
- if (name === 'permission') return
286
-
287
- if (
288
- declaration.initializer &&
289
- ts.isArrowFunction(declaration.initializer)
290
- ) {
291
- const params = declaration.initializer.parameters
292
- let paramType = 'void'
293
-
294
- if (params.length > 0) {
295
- const param = params[0]!
296
- paramType = param.type?.getText(sourceFile) || 'unknown'
297
- }
298
-
299
- try {
300
- const typeString = `type QueryParams = ${paramType}`
301
- const model = TypeScriptToModel.Generate(typeString)
302
- const valibotCode = ModelToValibot.Generate(model)
303
-
304
- queries.push({
305
- name,
306
- params: paramType,
307
- valibotCode,
308
- sourceFile: fileBaseName,
309
- })
310
- } catch (err) {
311
- console.error(`✗ ${name}: ${err}`)
312
- }
313
- }
314
- }
315
- })
316
- } catch (err) {
317
- console.error(`Error processing ${file}:`, err)
318
- }
319
-
320
- return queries
321
- })
322
- )
323
-
324
- const allQueries = queryResults.flat()
325
- const groupedQueriesOutput = generateGroupedQueriesFile(allQueries)
326
- const syncedQueriesOutput = generateSyncedQueriesFile(allQueries)
327
-
328
- const groupedChanged = writeFileIfChanged(
329
- resolve(generatedDir, 'groupedQueries.ts'),
330
- groupedQueriesOutput
331
- )
332
- const syncedChanged = writeFileIfChanged(
333
- resolve(generatedDir, 'syncedQueries.ts'),
334
- syncedQueriesOutput
335
- )
336
-
337
- const queryFilesChanged = (groupedChanged ? 1 : 0) + (syncedChanged ? 1 : 0)
338
- const totalFilesChanged = filesChanged + queryFilesChanged
339
-
340
- if (totalFilesChanged > 0 && !silent) {
341
- if (groupedChanged) {
342
- console.info(` 📝 Updated groupedQueries.ts`)
343
- }
344
- if (syncedChanged) {
345
- console.info(` 📝 Updated syncedQueries.ts`)
346
- }
347
- console.info(
348
- `✓ ${allModelFiles.length} models (${filesWithSchema.length} schemas), ${allQueries.length} queries`
349
- )
350
- }
351
-
352
- // run after command only if files changed
353
- if (totalFilesChanged > 0 && runAfter && args.after) {
354
- try {
355
- const { execSync } = await import('node:child_process')
356
- execSync(args.after, {
357
- stdio: 'inherit',
358
- env: { ...process.env, ON_ZERO_GENERATED_DIR: generatedDir },
359
- })
360
- } catch (err) {
361
- console.error(`Error running after command: ${err}`)
362
- }
363
- }
364
-
365
- saveCache()
366
- } else {
367
- if (filesChanged > 0 && !silent) {
368
- console.info(
369
- `✓ ${allModelFiles.length} models (${filesWithSchema.length} schemas)`
370
- )
371
- }
372
-
373
- // run after command only if files changed
374
- if (filesChanged > 0 && runAfter && args.after) {
375
- try {
376
- const { execSync } = await import('node:child_process')
377
- execSync(args.after, {
378
- stdio: 'inherit',
379
- env: { ...process.env, ON_ZERO_GENERATED_DIR: generatedDir },
380
- })
381
- } catch (err) {
382
- console.error(`Error running after command: ${err}`)
383
- }
384
- }
385
-
386
- saveCache()
387
- }
388
- }
389
32
 
390
- // run once (silent in watch mode for clean startup)
391
- await runGenerate({ silent: args.watch, runAfter: true })
33
+ async run({ args }) {
34
+ const opts = { dir: resolve(args.dir), after: args.after }
392
35
 
393
- // watch mode
394
36
  if (args.watch) {
395
- console.info('👀 watching...\n')
396
- const chokidar = await import('chokidar')
397
-
398
- let debounceTimer: ReturnType<typeof setTimeout> | null = null
399
-
400
- const debouncedRegenerate = (path: string, event: string) => {
401
- if (debounceTimer) {
402
- clearTimeout(debounceTimer)
403
- }
404
-
405
- console.info(`\n${event} ${path}`)
406
-
407
- debounceTimer = setTimeout(() => {
408
- runGenerate()
409
- }, 1000)
410
- }
411
-
412
- const watcher = chokidar.watch([modelsDir, queriesDir], {
413
- persistent: true,
414
- ignoreInitial: true,
415
- ignored: [generatedDir],
416
- })
417
-
418
- watcher.on('change', (path) => debouncedRegenerate(path, '📝'))
419
- watcher.on('add', (path) => debouncedRegenerate(path, '➕'))
420
- watcher.on('unlink', (path) => debouncedRegenerate(path, '🗑️ '))
421
-
422
- // keep process alive
37
+ await watch(opts)
423
38
  await new Promise(() => {})
39
+ } else {
40
+ await generate(opts)
424
41
  }
425
42
  },
426
43
  })
427
44
 
428
- function generateModelsFile(modelFiles: string[]) {
429
- const modelNames = modelFiles.map((f) => basename(f, '.ts')).sort()
430
-
431
- // special case: user.ts should be imported as userPublic
432
- const getImportName = (name: string) => (name === 'user' ? 'userPublic' : name)
433
-
434
- // generate imports (sorted)
435
- const imports = modelNames
436
- .map((name) => {
437
- const importName = getImportName(name)
438
- return `import * as ${importName} from '../models/${name}'`
439
- })
440
- .join('\n')
441
-
442
- // generate models object (sorted by import name)
443
- const sortedByImportName = [...modelNames].sort((a, b) =>
444
- getImportName(a).localeCompare(getImportName(b))
445
- )
446
- const modelsObj = `export const models = {\n${sortedByImportName.map((name) => ` ${getImportName(name)},`).join('\n')}\n}`
447
-
448
- // HMR boundary - accept updates without cascading
449
- // mutations use dynamic lookup so updates take effect immediately
450
- const hmrBoundary = `
451
- if (import.meta.hot) {
452
- import.meta.hot.accept()
453
- }
454
- `
455
-
456
- return `// auto-generated by: on-zero generate\n${imports}\n\n${modelsObj}\n${hmrBoundary}`
457
- }
458
-
459
- function generateTypesFile(modelFiles: string[]) {
460
- const modelNames = modelFiles.map((f) => basename(f, '.ts')).sort()
461
-
462
- // special case: user.ts should reference userPublic in schema
463
- const getSchemaName = (name: string) => (name === 'user' ? 'userPublic' : name)
464
-
465
- // generate type exports using TableInsertRow and TableUpdateRow (sorted)
466
- const typeExports = modelNames
467
- .map((name) => {
468
- const pascalName = name.charAt(0).toUpperCase() + name.slice(1)
469
- const schemaName = getSchemaName(name)
470
- return `export type ${pascalName} = TableInsertRow<typeof schema.${schemaName}>\nexport type ${pascalName}Update = TableUpdateRow<typeof schema.${schemaName}>`
471
- })
472
- .join('\n\n')
473
-
474
- return `import type { TableInsertRow, TableUpdateRow } from 'on-zero'\nimport type * as schema from './tables'\n\n${typeExports}\n`
475
- }
476
-
477
- function generateTablesFile(modelFiles: string[]) {
478
- const modelNames = modelFiles.map((f) => basename(f, '.ts')).sort()
479
-
480
- // special case: user.ts should be exported as userPublic
481
- const getExportName = (name: string) => (name === 'user' ? 'userPublic' : name)
482
-
483
- // generate schema exports (sorted)
484
- const exports = modelNames
485
- .map((name) => `export { schema as ${getExportName(name)} } from '../models/${name}'`)
486
- .join('\n')
487
-
488
- return `// auto-generated by: on-zero generate\n// this is separate from models as otherwise you end up with circular types :/\n\n${exports}\n`
489
- }
490
-
491
- function generateGroupedQueriesFile(
492
- queries: Array<{
493
- name: string
494
- params: string
495
- valibotCode: string
496
- sourceFile: string
497
- }>
498
- ) {
499
- // get unique source files sorted
500
- const sortedFiles = [...new Set(queries.map((q) => q.sourceFile))].sort()
501
-
502
- // generate re-exports
503
- const exports = sortedFiles
504
- .map((file) => `export * as ${file} from '../queries/${file}'`)
505
- .join('\n')
506
-
507
- return `/**
508
- * auto-generated by: on-zero generate
509
- *
510
- * grouped query re-exports for minification-safe query identity.
511
- * this file re-exports all query modules - while this breaks tree-shaking,
512
- * queries are typically small and few in number even in larger apps.
513
- */
514
- ${exports}
515
- `
516
- }
517
-
518
- function generateSyncedQueriesFile(
519
- queries: Array<{
520
- name: string
521
- params: string
522
- valibotCode: string
523
- sourceFile: string
524
- }>
525
- ) {
526
- // group queries by source file
527
- const queryByFile = new Map<string, typeof queries>()
528
- for (const q of queries) {
529
- if (!queryByFile.has(q.sourceFile)) {
530
- queryByFile.set(q.sourceFile, [])
531
- }
532
- queryByFile.get(q.sourceFile)!.push(q)
533
- }
534
-
535
- // sort file names for consistent output
536
- const sortedFiles = Array.from(queryByFile.keys()).sort()
537
-
538
- const imports = `// auto-generated by: on-zero generate
539
- // server-side query definitions with validators
540
- import { defineQuery, defineQueries } from '@rocicorp/zero'
541
- import * as v from 'valibot'
542
- import * as Queries from './groupedQueries'
543
- `
544
-
545
- // generate grouped definitions by namespace
546
- const namespaceDefs = sortedFiles
547
- .map((file) => {
548
- const fileQueries = queryByFile
549
- .get(file)!
550
- .sort((a, b) => a.name.localeCompare(b.name))
551
-
552
- const queryDefs = fileQueries
553
- .map((q) => {
554
- // extract validator schema
555
- const lines = q.valibotCode.split('\n').filter((l) => l.trim())
556
- const schemaLineIndex = lines.findIndex((l) =>
557
- l.startsWith('export const QueryParams')
558
- )
559
-
560
- let validatorDef = ''
561
- if (schemaLineIndex !== -1) {
562
- const schemaLines: string[] = []
563
- let openBraces = 0
564
- let started = false
565
-
566
- for (let i = schemaLineIndex; i < lines.length; i++) {
567
- const line = lines[i]!
568
- const cleaned = started
569
- ? line
570
- : line.replace('export const QueryParams = ', '')
571
- schemaLines.push(cleaned)
572
- started = true
573
-
574
- openBraces += (cleaned.match(/\{/g) || []).length
575
- openBraces -= (cleaned.match(/\}/g) || []).length
576
- openBraces += (cleaned.match(/\(/g) || []).length
577
- openBraces -= (cleaned.match(/\)/g) || []).length
578
-
579
- if (openBraces === 0 && schemaLines.length > 0) {
580
- break
581
- }
582
- }
583
- validatorDef = schemaLines.join('\n')
584
- }
585
-
586
- // for void queries, use the no-validator overload
587
- if (q.params === 'void' || !validatorDef) {
588
- return ` ${q.name}: defineQuery(() => Queries.${file}.${q.name}()),`
589
- }
590
-
591
- // indent the validator for proper formatting
592
- const indentedValidator = validatorDef
593
- .split('\n')
594
- .map((line, i) => (i === 0 ? line : ` ${line}`))
595
- .join('\n')
596
-
597
- // defineQuery with validator and args
598
- return ` ${q.name}: defineQuery(
599
- ${indentedValidator},
600
- ({ args }) => Queries.${file}.${q.name}(args)
601
- ),`
602
- })
603
- .join('\n')
604
-
605
- return `const ${file} = {\n${queryDefs}\n}`
606
- })
607
- .join('\n\n')
608
-
609
- // build the defineQueries call with all namespaces
610
- const queriesObject = sortedFiles.map((file) => ` ${file},`).join('\n')
611
-
612
- return `${imports}
613
- ${namespaceDefs}
614
-
615
- export const queries = defineQueries({
616
- ${queriesObject}
617
- })
618
- `
619
- }
620
-
621
- function generateReadmeFile() {
622
- return `# generated
623
-
624
- this folder is auto-generated by on-zero. do not edit files here directly.
625
-
626
- ## what's generated
627
-
628
- - \`models.ts\` - exports all models from ../models
629
- - \`types.ts\` - typescript types derived from table schemas
630
- - \`tables.ts\` - exports table schemas for type inference
631
- - \`groupedQueries.ts\` - namespaced query re-exports for client setup
632
- - \`syncedQueries.ts\` - namespaced syncedQuery wrappers for server setup
633
-
634
- ## usage guidelines
635
-
636
- **do not import generated files outside of the data folder.**
637
-
638
- ### queries
639
-
640
- write your queries as plain functions in \`../queries/\` and import them directly:
641
-
642
- \`\`\`ts
643
- // ✅ good - import from queries
644
- import { channelMessages } from '~/data/queries/message'
645
- \`\`\`
646
-
647
- the generated query files are only used internally by zero client/server setup.
648
-
649
- ### types
650
-
651
- you can import types from this folder, but prefer re-exporting from \`../types.ts\`:
652
-
653
- \`\`\`ts
654
- // ❌ okay but not preferred
655
- import type { Message } from '~/data/generated/types'
656
-
657
- // ✅ better - re-export from types.ts
658
- import type { Message } from '~/data/types'
659
- \`\`\`
660
-
661
- ## regeneration
662
-
663
- files are regenerated when you run:
664
-
665
- \`\`\`bash
666
- bun on-zero generate
667
- \`\`\`
668
-
669
- or in watch mode:
670
-
671
- \`\`\`bash
672
- bun on-zero generate --watch
673
- \`\`\`
674
-
675
- ## more info
676
-
677
- see the [on-zero readme](./node_modules/on-zero/README.md) for full documentation.
678
- `
679
- }
680
-
681
45
  const main = defineCommand({
682
46
  meta: {
683
47
  name: 'on-zero',
684
48
  description: 'on-zero CLI tools',
685
49
  },
686
50
  subCommands: {
687
- generate: generate,
688
- 'generate-queries': generateQueries,
51
+ generate: generateCommand,
689
52
  },
690
53
  })
691
54