on-zero 0.1.22 → 0.1.24

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 (56) 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/generate.test.cjs +113 -0
  12. package/dist/cjs/generate.test.js +126 -0
  13. package/dist/cjs/generate.test.js.map +6 -0
  14. package/dist/cjs/generate.test.native.js +116 -0
  15. package/dist/cjs/generate.test.native.js.map +1 -0
  16. package/dist/cjs/vite-plugin.cjs +37 -121
  17. package/dist/cjs/vite-plugin.js +41 -100
  18. package/dist/cjs/vite-plugin.js.map +1 -1
  19. package/dist/cjs/vite-plugin.native.js +47 -157
  20. package/dist/cjs/vite-plugin.native.js.map +1 -1
  21. package/dist/esm/cli.js +8 -388
  22. package/dist/esm/cli.js.map +2 -2
  23. package/dist/esm/cli.mjs +17 -402
  24. package/dist/esm/cli.mjs.map +1 -1
  25. package/dist/esm/cli.native.js +15 -497
  26. package/dist/esm/cli.native.js.map +1 -1
  27. package/dist/esm/generate.js +317 -0
  28. package/dist/esm/generate.js.map +6 -0
  29. package/dist/esm/generate.mjs +335 -0
  30. package/dist/esm/generate.mjs.map +1 -0
  31. package/dist/esm/generate.native.js +426 -0
  32. package/dist/esm/generate.native.js.map +1 -0
  33. package/dist/esm/generate.test.js +130 -0
  34. package/dist/esm/generate.test.js.map +6 -0
  35. package/dist/esm/generate.test.mjs +114 -0
  36. package/dist/esm/generate.test.mjs.map +1 -0
  37. package/dist/esm/generate.test.native.js +114 -0
  38. package/dist/esm/generate.test.native.js.map +1 -0
  39. package/dist/esm/vite-plugin.js +42 -102
  40. package/dist/esm/vite-plugin.js.map +1 -1
  41. package/dist/esm/vite-plugin.mjs +37 -121
  42. package/dist/esm/vite-plugin.mjs.map +1 -1
  43. package/dist/esm/vite-plugin.native.js +47 -157
  44. package/dist/esm/vite-plugin.native.js.map +1 -1
  45. package/package.json +6 -3
  46. package/readme.md +0 -29
  47. package/src/cli.ts +9 -646
  48. package/src/generate.test.ts +201 -0
  49. package/src/generate.ts +491 -0
  50. package/src/vite-plugin.ts +61 -189
  51. package/types/generate.d.ts +21 -0
  52. package/types/generate.d.ts.map +1 -0
  53. package/types/generate.test.d.ts +2 -0
  54. package/types/generate.test.d.ts.map +1 -0
  55. package/types/vite-plugin.d.ts +6 -29
  56. package/types/vite-plugin.d.ts.map +1 -1
@@ -1,238 +1,110 @@
1
- import { createHash } from 'node:crypto'
2
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
3
- import { basename, resolve } from 'node:path'
1
+ import { isAbsolute, relative, resolve } from 'node:path'
4
2
 
5
- import type { Plugin } from 'vite'
6
-
7
- const hash = (s: string) => createHash('sha256').update(s).digest('hex')
8
-
9
- // cache to avoid unnecessary writes
10
- let generateCache: Record<string, string> = {}
11
-
12
- function writeFileIfChanged(filePath: string, content: string): boolean {
13
- const contentHash = hash(content)
14
- if (generateCache[filePath] === contentHash && existsSync(filePath)) {
15
- return false
16
- }
17
- writeFileSync(filePath, content, 'utf-8')
18
- generateCache[filePath] = contentHash
19
- return true
20
- }
21
-
22
- function generateModelsFile(modelFiles: string[]) {
23
- const modelNames = modelFiles.map((f) => basename(f, '.ts')).sort()
24
- const getImportName = (name: string) => (name === 'user' ? 'userPublic' : name)
25
-
26
- const imports = modelNames
27
- .map((name) => `import * as ${getImportName(name)} from '../models/${name}'`)
28
- .join('\n')
29
-
30
- const sortedByImportName = [...modelNames].sort((a, b) =>
31
- getImportName(a).localeCompare(getImportName(b))
32
- )
33
- const modelsObj = `export const models = {\n${sortedByImportName.map((name) => ` ${getImportName(name)},`).join('\n')}\n}`
34
-
35
- const hmrBoundary = `
36
- if (import.meta.hot) {
37
- import.meta.hot.accept()
38
- }
39
- `
40
-
41
- return `// auto-generated by on-zero vite plugin\n${imports}\n\n${modelsObj}\n${hmrBoundary}`
42
- }
3
+ import { generate, type GenerateOptions } from './generate'
43
4
 
44
- function generateTypesFile(modelFiles: string[]) {
45
- const modelNames = modelFiles.map((f) => basename(f, '.ts')).sort()
46
- const getSchemaName = (name: string) => (name === 'user' ? 'userPublic' : name)
47
-
48
- const typeExports = modelNames
49
- .map((name) => {
50
- const pascalName = name.charAt(0).toUpperCase() + name.slice(1)
51
- const schemaName = getSchemaName(name)
52
- return `export type ${pascalName} = TableInsertRow<typeof schema.${schemaName}>\nexport type ${pascalName}Update = TableUpdateRow<typeof schema.${schemaName}>`
53
- })
54
- .join('\n\n')
55
-
56
- return `import type { TableInsertRow, TableUpdateRow } from 'on-zero'\nimport type * as schema from './tables'\n\n${typeExports}\n`
57
- }
58
-
59
- function generateTablesFile(modelFiles: string[]) {
60
- const modelNames = modelFiles.map((f) => basename(f, '.ts')).sort()
61
- const getExportName = (name: string) => (name === 'user' ? 'userPublic' : name)
62
-
63
- const exports = modelNames
64
- .map((name) => `export { schema as ${getExportName(name)} } from '../models/${name}'`)
65
- .join('\n')
5
+ import type { Plugin } from 'vite'
66
6
 
67
- return `// auto-generated by on-zero vite plugin\n\n${exports}\n`
7
+ export interface OnZeroPluginOptions extends Omit<GenerateOptions, 'dir' | 'silent'> {
8
+ /** base data directory. defaults to src/data */
9
+ dir?: string
10
+ /** additional paths to apply HMR fix to */
11
+ hmrInclude?: string[]
12
+ /** disable code generation (HMR only) */
13
+ disableGenerate?: boolean
68
14
  }
69
15
 
70
- async function runGenerate(options: {
71
- modelsDir: string
72
- generatedDir: string
73
- silent?: boolean
74
- }) {
75
- const { modelsDir, generatedDir, silent } = options
76
-
77
- if (!existsSync(generatedDir)) {
78
- mkdirSync(generatedDir, { recursive: true })
79
- }
80
-
81
- const allModelFiles = readdirSync(modelsDir)
82
- .filter((f) => f.endsWith('.ts'))
83
- .sort()
84
-
85
- const filesWithSchema = allModelFiles.filter((f) =>
86
- readFileSync(resolve(modelsDir, f), 'utf-8').includes('export const schema = table(')
87
- )
88
-
89
- const writeResults = [
90
- writeFileIfChanged(
91
- resolve(generatedDir, 'models.ts'),
92
- generateModelsFile(allModelFiles)
93
- ),
94
- writeFileIfChanged(
95
- resolve(generatedDir, 'types.ts'),
96
- generateTypesFile(filesWithSchema)
97
- ),
98
- writeFileIfChanged(
99
- resolve(generatedDir, 'tables.ts'),
100
- generateTablesFile(filesWithSchema)
101
- ),
102
- ]
103
-
104
- const filesChanged = writeResults.filter(Boolean).length
105
- if (filesChanged > 0 && !silent) {
106
- console.info(`[on-zero] generated ${filesChanged} file(s)`)
16
+ function createOnZeroHmrPlugin(hmrInclude: string[] = []): Plugin {
17
+ const hmrPaths = ['/models/', '/generated/', '/queries/', ...hmrInclude]
18
+
19
+ return {
20
+ name: 'on-zero:hmr',
21
+ apply: 'serve',
22
+ enforce: 'post',
23
+
24
+ transform(code, id) {
25
+ if (!hmrPaths.some((p) => id.includes(p)) || !/\.tsx?$/.test(id)) return
26
+ if (!code.includes('import.meta.hot.invalidate')) return
27
+
28
+ return {
29
+ code: code.replace(
30
+ /if\s*\(invalidateMessage\)\s*import\.meta\.hot\.invalidate\(invalidateMessage\);?/g,
31
+ '/* on-zero: HMR invalidate disabled */'
32
+ ),
33
+ map: null,
34
+ }
35
+ },
107
36
  }
108
-
109
- return filesChanged
110
37
  }
111
38
 
112
- export interface OnZeroPluginOptions {
113
- /**
114
- * Path to data directory containing models/ and generated/
115
- * @default 'src/data'
116
- */
117
- dataDir?: string
118
-
119
- /**
120
- * Additional paths to apply HMR fix to
121
- */
122
- hmrInclude?: string[]
123
-
124
- /**
125
- * Disable code generation (HMR only)
126
- */
127
- disableGenerate?: boolean
39
+ function isWithinDirectory(file: string, dir: string): boolean {
40
+ const rel = relative(dir, file)
41
+ return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel)
128
42
  }
129
43
 
130
- /**
131
- * Vite plugin for on-zero that handles:
132
- * 1. Code generation (models.ts, types.ts, tables.ts)
133
- * 2. HMR support for model files (prevents cascade, enables hot-swap)
134
- *
135
- * @example
136
- * ```ts
137
- * import { onZeroPlugin } from 'on-zero/vite'
138
- *
139
- * export default {
140
- * plugins: [
141
- * onZeroPlugin(),
142
- * // ... other plugins
143
- * ]
144
- * }
145
- * ```
146
- */
147
44
  export function onZeroPlugin(options: OnZeroPluginOptions = {}): Plugin[] {
148
- const dataDir = options.dataDir || 'src/data'
149
- const hmrPaths = ['/models/', '/generated/', ...(options.hmrInclude || [])]
45
+ const dir = options.dir ?? 'src/data'
150
46
 
47
+ let dataDir: string
151
48
  let modelsDir: string
152
- let generatedDir: string
49
+ let queriesDir: string
50
+
51
+ const runGenerate = (silent: boolean) =>
52
+ generate({
53
+ dir: dataDir,
54
+ after: options.after,
55
+ silent,
56
+ })
153
57
 
154
58
  return [
155
- // generation plugin
156
59
  {
157
- name: 'on-zero:generate',
60
+ name: 'on-zero:serve',
158
61
  apply: 'serve',
159
62
 
160
63
  configResolved(config) {
161
- modelsDir = resolve(config.root, dataDir, 'models')
162
- generatedDir = resolve(config.root, dataDir, 'generated')
64
+ dataDir = resolve(config.root, dir)
65
+ modelsDir = resolve(dataDir, 'models')
66
+ queriesDir = resolve(dataDir, 'queries')
163
67
  },
164
68
 
165
69
  async buildStart() {
166
- if (options.disableGenerate) return
167
- await runGenerate({ modelsDir, generatedDir, silent: false })
70
+ if (!options.disableGenerate) await runGenerate(false)
168
71
  },
169
72
 
170
73
  configureServer(server) {
171
74
  if (options.disableGenerate) return
172
75
 
173
- // watch for model changes
174
- server.watcher.on('change', async (file) => {
175
- if (file.includes(modelsDir)) {
176
- await runGenerate({ modelsDir, generatedDir, silent: false })
177
- }
178
- })
179
-
180
- server.watcher.on('add', async (file) => {
181
- if (file.includes(modelsDir)) {
182
- await runGenerate({ modelsDir, generatedDir, silent: false })
183
- }
184
- })
185
-
186
- server.watcher.on('unlink', async (file) => {
187
- if (file.includes(modelsDir)) {
188
- await runGenerate({ modelsDir, generatedDir, silent: false })
189
- }
190
- })
191
- },
192
- },
193
-
194
- // HMR plugin - removes invalidate calls to prevent cascade
195
- {
196
- name: 'on-zero:hmr',
197
- apply: 'serve',
198
- enforce: 'post',
199
-
200
- transform(code, id) {
201
- const shouldTransform = hmrPaths.some((p) => id.includes(p)) && /\.tsx?$/.test(id)
202
-
203
- if (shouldTransform && code.includes('import.meta.hot.invalidate')) {
204
- return {
205
- code: code.replace(
206
- /if\s*\(invalidateMessage\)\s*import\.meta\.hot\.invalidate\(invalidateMessage\);?/g,
207
- '/* on-zero: HMR invalidate disabled */'
208
- ),
209
- map: null,
76
+ const handler = async (file: string) => {
77
+ if (!/\.tsx?$/.test(file)) return
78
+ if (isWithinDirectory(file, modelsDir) || isWithinDirectory(file, queriesDir)) {
79
+ await runGenerate(false)
210
80
  }
211
81
  }
82
+
83
+ server.watcher.on('change', handler)
84
+ server.watcher.on('add', handler)
85
+ server.watcher.on('unlink', handler)
212
86
  },
213
87
  },
214
88
 
215
- // build plugin - generate once before build
216
89
  {
217
90
  name: 'on-zero:build',
218
91
  apply: 'build',
219
92
 
220
93
  configResolved(config) {
221
- modelsDir = resolve(config.root, dataDir, 'models')
222
- generatedDir = resolve(config.root, dataDir, 'generated')
94
+ dataDir = resolve(config.root, dir)
223
95
  },
224
96
 
225
97
  async buildStart() {
226
- if (options.disableGenerate) return
227
- await runGenerate({ modelsDir, generatedDir, silent: true })
98
+ if (!options.disableGenerate) await runGenerate(true)
228
99
  },
229
100
  },
101
+
102
+ createOnZeroHmrPlugin(options.hmrInclude),
230
103
  ]
231
104
  }
232
105
 
233
- // legacy export for backwards compat
234
106
  export const onZeroHmrPlugin = (options?: { include?: string[] }): Plugin => {
235
- return onZeroPlugin({ hmrInclude: options?.include, disableGenerate: true })[1]
107
+ return createOnZeroHmrPlugin(options?.include)
236
108
  }
237
109
 
238
110
  export default onZeroPlugin
@@ -0,0 +1,21 @@
1
+ export interface GenerateOptions {
2
+ /** base data directory */
3
+ dir: string;
4
+ /** run after generation */
5
+ after?: string;
6
+ /** suppress output */
7
+ silent?: boolean;
8
+ }
9
+ export interface WatchOptions extends GenerateOptions {
10
+ /** debounce delay in ms */
11
+ debounce?: number;
12
+ }
13
+ export interface GenerateResult {
14
+ filesChanged: number;
15
+ modelCount: number;
16
+ schemaCount: number;
17
+ queryCount: number;
18
+ }
19
+ export declare function generate(options: GenerateOptions): Promise<GenerateResult>;
20
+ export declare function watch(options: WatchOptions): Promise<import("chokidar").FSWatcher>;
21
+ //# sourceMappingURL=generate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAsRA,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;CACnB;AAED,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CA2JhF;AAED,wBAAsB,KAAK,CAAC,OAAO,EAAE,YAAY,yCAkChD"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=generate.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.test.d.ts","sourceRoot":"","sources":["../src/generate.test.ts"],"names":[],"mappings":""}
@@ -1,36 +1,13 @@
1
+ import { type GenerateOptions } from './generate';
1
2
  import type { Plugin } from 'vite';
2
- export interface OnZeroPluginOptions {
3
- /**
4
- * Path to data directory containing models/ and generated/
5
- * @default 'src/data'
6
- */
7
- dataDir?: string;
8
- /**
9
- * Additional paths to apply HMR fix to
10
- */
3
+ export interface OnZeroPluginOptions extends Omit<GenerateOptions, 'dir' | 'silent'> {
4
+ /** base data directory. defaults to src/data */
5
+ dir?: string;
6
+ /** additional paths to apply HMR fix to */
11
7
  hmrInclude?: string[];
12
- /**
13
- * Disable code generation (HMR only)
14
- */
8
+ /** disable code generation (HMR only) */
15
9
  disableGenerate?: boolean;
16
10
  }
17
- /**
18
- * Vite plugin for on-zero that handles:
19
- * 1. Code generation (models.ts, types.ts, tables.ts)
20
- * 2. HMR support for model files (prevents cascade, enables hot-swap)
21
- *
22
- * @example
23
- * ```ts
24
- * import { onZeroPlugin } from 'on-zero/vite'
25
- *
26
- * export default {
27
- * plugins: [
28
- * onZeroPlugin(),
29
- * // ... other plugins
30
- * ]
31
- * }
32
- * ```
33
- */
34
11
  export declare function onZeroPlugin(options?: OnZeroPluginOptions): Plugin[];
35
12
  export declare const onZeroHmrPlugin: (options?: {
36
13
  include?: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../src/vite-plugin.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AA2GlC,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IAErB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,MAAM,EAAE,CAoFxE;AAGD,eAAO,MAAM,eAAe,GAAI,UAAU;IAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,KAAG,MAElE,CAAA;AAED,eAAe,YAAY,CAAA"}
1
+ {"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../src/vite-plugin.ts"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,eAAe,EAAE,MAAM,YAAY,CAAA;AAE3D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAElC,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAAC,eAAe,EAAE,KAAK,GAAG,QAAQ,CAAC;IAClF,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,2CAA2C;IAC3C,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB,yCAAyC;IACzC,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B;AA8BD,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,MAAM,EAAE,CA4DxE;AAED,eAAO,MAAM,eAAe,GAAI,UAAU;IAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,KAAG,MAElE,CAAA;AAED,eAAe,YAAY,CAAA"}