on-zero 0.9.7 → 0.10.0

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 (49) hide show
  1. package/dist/cjs/generate-helpers.test.cjs +0 -38
  2. package/dist/cjs/generate-helpers.test.native.js +0 -38
  3. package/dist/cjs/generate-helpers.test.native.js.map +1 -1
  4. package/dist/cjs/generate-lite.test.cjs +0 -522
  5. package/dist/cjs/generate-lite.test.native.js +0 -525
  6. package/dist/cjs/generate-lite.test.native.js.map +1 -1
  7. package/dist/cjs/generate.test.cjs +0 -691
  8. package/dist/cjs/generate.test.native.js +0 -691
  9. package/dist/cjs/generate.test.native.js.map +1 -1
  10. package/dist/cjs/mutations.test.cjs +0 -11
  11. package/dist/cjs/mutations.test.native.js +0 -18
  12. package/dist/cjs/mutations.test.native.js.map +1 -1
  13. package/dist/cjs/server.test.cjs +0 -93
  14. package/dist/cjs/server.test.native.js +0 -101
  15. package/dist/cjs/server.test.native.js.map +1 -1
  16. package/dist/cjs/serverWhere.test.cjs +0 -45
  17. package/dist/cjs/serverWhere.test.native.js +0 -51
  18. package/dist/cjs/serverWhere.test.native.js.map +1 -1
  19. package/dist/esm/generate-helpers.test.mjs +1 -39
  20. package/dist/esm/generate-helpers.test.mjs.map +1 -1
  21. package/dist/esm/generate-helpers.test.native.js +1 -39
  22. package/dist/esm/generate-helpers.test.native.js.map +1 -1
  23. package/dist/esm/generate-lite.test.mjs +0 -522
  24. package/dist/esm/generate-lite.test.mjs.map +1 -1
  25. package/dist/esm/generate-lite.test.native.js +0 -525
  26. package/dist/esm/generate-lite.test.native.js.map +1 -1
  27. package/dist/esm/generate.test.mjs +2 -668
  28. package/dist/esm/generate.test.mjs.map +1 -1
  29. package/dist/esm/generate.test.native.js +2 -668
  30. package/dist/esm/generate.test.native.js.map +1 -1
  31. package/dist/esm/mutations.test.mjs +0 -11
  32. package/dist/esm/mutations.test.mjs.map +1 -1
  33. package/dist/esm/mutations.test.native.js +0 -18
  34. package/dist/esm/mutations.test.native.js.map +1 -1
  35. package/dist/esm/server.test.mjs +0 -93
  36. package/dist/esm/server.test.mjs.map +1 -1
  37. package/dist/esm/server.test.native.js +0 -101
  38. package/dist/esm/server.test.native.js.map +1 -1
  39. package/dist/esm/serverWhere.test.mjs +0 -45
  40. package/dist/esm/serverWhere.test.mjs.map +1 -1
  41. package/dist/esm/serverWhere.test.native.js +0 -51
  42. package/dist/esm/serverWhere.test.native.js.map +1 -1
  43. package/package.json +1 -1
  44. package/src/generate-helpers.test.ts +0 -40
  45. package/src/generate-lite.test.ts +0 -586
  46. package/src/generate.test.ts +0 -844
  47. package/src/mutations.test.ts +0 -21
  48. package/src/server.test.ts +0 -75
  49. package/src/serverWhere.test.ts +0 -42
@@ -30,38 +30,6 @@ function expectNoDuplicates(source: string) {
30
30
  }
31
31
 
32
32
  describe('generated module identifiers', () => {
33
- test('grouped queries keep distinct aliases when one namespace is another plus Source', () => {
34
- const source = generateGroupedQueriesFile([
35
- { name: 'appById', sourceFile: 'app', importPath: '../app/queries' },
36
- {
37
- name: 'appSourceByAppId',
38
- sourceFile: 'appSource',
39
- importPath: '../appSource/queries',
40
- },
41
- ])
42
-
43
- expectNoDuplicates(source)
44
- // each namespace still exports its own queries, reading from its own module
45
- const appExport = source.match(/export const app = \{([^}]*)\}/)![1]!
46
- const appSourceExport = source.match(/export const appSource = \{([^}]*)\}/)![1]!
47
- expect(appExport).toContain('appById')
48
- expect(appSourceExport).toContain('appSourceByAppId')
49
- expect(appExport).not.toContain('appSourceByAppId')
50
- })
51
-
52
- test('models keep distinct aliases when user and userPublic both exist', () => {
53
- const source = generateModelsFile([
54
- { name: 'user', importPath: '../user' },
55
- { name: 'userPublic', importPath: '../userPublic' },
56
- ] as Parameters<typeof generateModelsFile>[0])
57
-
58
- expectNoDuplicates(source)
59
- // both namespaces survive as distinct keys bound to their own module
60
- const modelsObject = source.match(/export const models = \{([\s\S]*)\}/)![1]!
61
- expect(modelsObject).toMatch(/\buser:/)
62
- expect(modelsObject).toMatch(/\buserPublic\b/)
63
- })
64
-
65
33
  test('table re-exports keep distinct names when user and userPublic both exist', () => {
66
34
  const source = generateTablesFile([
67
35
  { name: 'user', importPath: '../user' },
@@ -70,12 +38,4 @@ describe('generated module identifiers', () => {
70
38
 
71
39
  expectNoDuplicates(source)
72
40
  })
73
-
74
- test('a namespace with no collision keeps its plain alias', () => {
75
- const source = generateGroupedQueriesFile([
76
- { name: 'todoById', sourceFile: 'todo', importPath: '../todo/queries' },
77
- ])
78
-
79
- expect(source).toContain(`import * as todoSource from '../todo/queries'`)
80
- })
81
41
  })
@@ -27,463 +27,6 @@ afterEach(() => {
27
27
  })
28
28
 
29
29
  describe('generateLite', () => {
30
- test('emits models.ts, syncedMutations.ts, and README.md from inline types', () => {
31
- const files: Record<string, string> = {
32
- [`${DIR}/todo.ts`]: '// fake source, parser returns fixture',
33
- [`${DIR}/user.ts`]: '// fake source, parser returns fixture',
34
- }
35
-
36
- const fixtures: Record<string, LiteParsedFile> = {
37
- [`${DIR}/todo.ts`]: {
38
- mutations: [
39
- {
40
- modelName: 'todo',
41
- handlers: [
42
- {
43
- name: 'toggle',
44
- paramTypeText: '{ id: string; isActive: boolean }',
45
- },
46
- {
47
- name: 'rename',
48
- paramTypeText: '{ id: string; title: string }',
49
- },
50
- ],
51
- schema: null,
52
- },
53
- ],
54
- queries: [],
55
- },
56
- [`${DIR}/user.ts`]: {
57
- mutations: [
58
- {
59
- modelName: 'user',
60
- handlers: [
61
- {
62
- name: 'finishOnboarding',
63
- // null = no second param / void
64
- paramTypeText: null,
65
- },
66
- ],
67
- schema: null,
68
- },
69
- ],
70
- queries: [],
71
- },
72
- }
73
-
74
- const result = generateLite({
75
- files,
76
- dir: DIR,
77
- parse: makeParse(fixtures),
78
- })
79
-
80
- // output file set
81
- expect(Object.keys(result.files).sort()).toEqual([
82
- 'README.md',
83
- 'groupedQueries.ts',
84
- 'instances.ts',
85
- 'models.ts',
86
- 'syncedMutations.ts',
87
- 'syncedQueries.ts',
88
- ])
89
-
90
- // models.ts: aliases the user import without changing the model key
91
- const models = result.files['models.ts']!
92
- expect(models).toContain("import * as todo from '../todo'")
93
- expect(models).toContain("import * as userPublic from '../user'")
94
- expect(models).toContain('user: userPublic,')
95
- expect(models).not.toContain('\n userPublic,')
96
- expect(models).toContain('export const models = {')
97
-
98
- // syncedMutations.ts: inline types resolve to v.object validators
99
- const syncedMutations = result.files['syncedMutations.ts']!
100
- expect(syncedMutations).toContain('toggle:')
101
- expect(syncedMutations).toContain('v.object({')
102
- expect(syncedMutations).toContain('id: v.string()')
103
- expect(syncedMutations).toContain('isActive: v.boolean()')
104
- expect(syncedMutations).toContain('rename:')
105
- expect(syncedMutations).toContain('title: v.string()')
106
- // null param → v.void_()
107
- expect(syncedMutations).toContain('finishOnboarding: v.void_()')
108
-
109
- expect(result.modelCount).toBe(2)
110
- expect(result.schemaCount).toBe(0)
111
- expect(result.mutationCount).toBe(3)
112
- })
113
-
114
- test('parses newline-separated mutation object types', () => {
115
- const files: Record<string, string> = {
116
- [`${DIR}/agentEvent.ts`]: '// fake',
117
- [`${DIR}/deployment.ts`]: '// fake',
118
- }
119
-
120
- const fixtures: Record<string, LiteParsedFile> = {
121
- [`${DIR}/agentEvent.ts`]: {
122
- mutations: [
123
- {
124
- modelName: 'agentEvent',
125
- handlers: [
126
- {
127
- name: 'claimReviewLease',
128
- paramTypeText: `{
129
- id: string
130
- projectId: string
131
- createdAt: number
132
- }`,
133
- },
134
- ],
135
- schema: null,
136
- },
137
- ],
138
- queries: [],
139
- },
140
- [`${DIR}/deployment.ts`]: {
141
- mutations: [
142
- {
143
- modelName: 'deployment',
144
- handlers: [
145
- {
146
- name: 'deploy',
147
- paramTypeText: `{
148
- id: string
149
- platform?: string // 'web' | 'ios'
150
- buildTier?: string // 'dev' | 'testflight' | 'production'
151
- }`,
152
- },
153
- ],
154
- schema: null,
155
- },
156
- ],
157
- queries: [],
158
- },
159
- }
160
-
161
- const result = generateLite({
162
- files,
163
- dir: DIR,
164
- parse: makeParse(fixtures),
165
- })
166
-
167
- const synced = result.files['syncedMutations.ts']!
168
- expect(synced).toContain('claimReviewLease:')
169
- expect(synced).toContain('projectId: v.string()')
170
- expect(synced).toContain('createdAt: v.number()')
171
- expect(synced).toContain('deploy:')
172
- expect(synced).toContain('platform: v.optional(v.string())')
173
- expect(synced).toContain('buildTier: v.optional(v.string())')
174
- expect(synced).not.toContain('claimReviewLease: v.object({})')
175
- expect(synced).not.toContain('deploy: v.object({})')
176
- })
177
-
178
- test('parses object intersections and skips index signatures', () => {
179
- const files: Record<string, string> = {
180
- [`${DIR}/agent.ts`]: '// fake',
181
- }
182
-
183
- const fixtures: Record<string, LiteParsedFile> = {
184
- [`${DIR}/agent.ts`]: {
185
- mutations: [
186
- {
187
- modelName: 'agent',
188
- handlers: [
189
- {
190
- name: 'update',
191
- paramTypeText: `{
192
- id: string
193
- } & {
194
- status?: string
195
- currentTaskId?: string
196
- [key: string]: unknown
197
- }`,
198
- },
199
- ],
200
- schema: null,
201
- },
202
- ],
203
- queries: [],
204
- },
205
- }
206
-
207
- const result = generateLite({
208
- files,
209
- dir: DIR,
210
- parse: makeParse(fixtures),
211
- })
212
-
213
- const synced = result.files['syncedMutations.ts']!
214
- expect(synced).toContain('update:')
215
- expect(synced).toContain('id: v.string()')
216
- expect(synced).toContain('status: v.optional(v.string())')
217
- expect(synced).toContain('currentTaskId: v.optional(v.string())')
218
- expect(synced).not.toContain('[key')
219
- expect(synced).not.toContain('update: v.object({})')
220
- })
221
-
222
- test('falls back to v.unknown() for type references', () => {
223
- const files: Record<string, string> = {
224
- [`${DIR}/post.ts`]: `export const allPosts = () => zql.post`,
225
- }
226
-
227
- const fixtures: Record<string, LiteParsedFile> = {
228
- [`${DIR}/post.ts`]: {
229
- mutations: [
230
- {
231
- modelName: 'post',
232
- handlers: [
233
- {
234
- name: 'archive',
235
- // bare type reference — parseTypeString returns null, so
236
- // generate-lite should fall back to v.unknown() rather than
237
- // attempting cross-file type resolution.
238
- paramTypeText: 'ArchiveParams',
239
- },
240
- {
241
- name: 'publish',
242
- // primitive, should resolve
243
- paramTypeText: 'string',
244
- },
245
- ],
246
- schema: null,
247
- },
248
- ],
249
- queries: [],
250
- },
251
- }
252
-
253
- const result = generateLite({
254
- files,
255
- dir: DIR,
256
- parse: makeParse(fixtures),
257
- })
258
-
259
- const synced = result.files['syncedMutations.ts']!
260
- expect(synced).toContain('archive: v.unknown()')
261
- expect(synced).toContain('publish: v.string()')
262
- })
263
-
264
- test('uses v.unknown() for explicitly unknown mutation params', () => {
265
- const files: Record<string, string> = {
266
- [`${DIR}/project.ts`]: '// fake',
267
- }
268
-
269
- const fixtures: Record<string, LiteParsedFile> = {
270
- [`${DIR}/project.ts`]: {
271
- mutations: [
272
- {
273
- modelName: 'project',
274
- handlers: [{ name: 'insert', paramTypeText: 'unknown' }],
275
- schema: null,
276
- },
277
- ],
278
- queries: [],
279
- },
280
- }
281
-
282
- const result = generateLite({
283
- files,
284
- dir: DIR,
285
- parse: makeParse(fixtures),
286
- })
287
-
288
- expect(result.files['syncedMutations.ts']!).toContain('insert: v.unknown()')
289
- })
290
-
291
- test('emits query files with v.unknown() fallback for references', () => {
292
- const files: Record<string, string> = {
293
- [`${DIR}/post.ts`]: '// fake',
294
- }
295
-
296
- const fixtures: Record<string, LiteParsedFile> = {
297
- [`${DIR}/post.ts`]: {
298
- mutations: [],
299
- queries: [
300
- // no-arg query → void
301
- { name: 'allPosts', rootTable: 'post', paramTypeText: null, relatedPaths: [] },
302
- // inline object → real validator
303
- {
304
- name: 'postById',
305
- rootTable: 'post',
306
- paramTypeText: '{ id: string }',
307
- relatedPaths: [],
308
- },
309
- // primitive
310
- {
311
- name: 'byAuthorId',
312
- rootTable: 'post',
313
- paramTypeText: 'string',
314
- relatedPaths: [],
315
- },
316
- // optional nullable inline object
317
- {
318
- name: 'paged',
319
- rootTable: 'post',
320
- paramTypeText:
321
- '{ pageSize: number; cursor?: { id: string; createdAt: number } | null }',
322
- relatedPaths: [],
323
- },
324
- // type reference → fallback
325
- {
326
- name: 'filtered',
327
- rootTable: 'post',
328
- paramTypeText: 'PostFilter',
329
- relatedPaths: [],
330
- },
331
- // permission should be skipped
332
- {
333
- name: 'permission',
334
- rootTable: 'post',
335
- paramTypeText: null,
336
- relatedPaths: [],
337
- },
338
- ],
339
- },
340
- }
341
-
342
- const result = generateLite({
343
- files,
344
- dir: DIR,
345
- parse: makeParse(fixtures),
346
- })
347
-
348
- // expect both query files
349
- expect(result.files['groupedQueries.ts']).toBeDefined()
350
- expect(result.files['syncedQueries.ts']).toBeDefined()
351
-
352
- const grouped = result.files['groupedQueries.ts']!
353
- expect(grouped).toContain("import * as postSource from '../post'")
354
- expect(grouped).toContain('postById: postSource.postById')
355
-
356
- const synced = result.files['syncedQueries.ts']!
357
- expect(synced).toContain('allPosts: defineQuery(() => Queries.post.allPosts())')
358
- expect(synced).toContain('postById: defineQuery(')
359
- expect(synced).toContain('id: v.string()')
360
- expect(synced).toContain('byAuthorId: defineQuery(')
361
- // primitive string param shows up as v.string() validator
362
- expect(synced).toMatch(/byAuthorId: defineQuery\(\s*v\.string\(\)/)
363
- expect(synced).toContain('paged: defineQuery(')
364
- expect(synced).toContain('cursor: v.optional(v.nullable(v.object({')
365
- // type reference fallback
366
- expect(synced).toContain('filtered: defineQuery(')
367
- expect(synced).toMatch(/filtered: defineQuery\(\s*v\.unknown\(\)/)
368
- // permission export skipped
369
- expect(synced).not.toContain('permission: defineQuery')
370
-
371
- expect(result.queryCount).toBe(5) // permission excluded
372
- })
373
-
374
- test('emits types.ts and tables.ts when a model declares a schema inline', () => {
375
- const files: Record<string, string> = {
376
- [`${DIR}/task.ts`]: '// fake',
377
- }
378
-
379
- const fixtures: Record<string, LiteParsedFile> = {
380
- [`${DIR}/task.ts`]: {
381
- mutations: [
382
- {
383
- modelName: 'task',
384
- handlers: [],
385
- schema: {
386
- tableName: 'task',
387
- primaryKeys: ['id'],
388
- columns: [
389
- { name: 'id', builderText: 'string()' },
390
- { name: 'title', builderText: 'string()' },
391
- { name: 'priority', builderText: 'number()' },
392
- { name: 'done', builderText: 'boolean()' },
393
- { name: 'note', builderText: 'string().optional()' },
394
- ],
395
- },
396
- },
397
- ],
398
- queries: [],
399
- },
400
- }
401
-
402
- const result = generateLite({
403
- files,
404
- dir: DIR,
405
- parse: makeParse(fixtures),
406
- })
407
-
408
- expect(result.schemaCount).toBe(1)
409
- expect(result.files['types.ts']).toBeDefined()
410
- expect(result.files['tables.ts']).toBeDefined()
411
-
412
- const types = result.files['types.ts']!
413
- expect(types).toContain('export type Task = TableInsertRow<typeof schema.task>')
414
-
415
- const tables = result.files['tables.ts']!
416
- expect(tables).toContain("export { schema as task } from '../task'")
417
-
418
- // crud validators emitted in syncedMutations.ts
419
- const synced = result.files['syncedMutations.ts']!
420
- expect(synced).toContain('insert:')
421
- expect(synced).toContain('update:')
422
- expect(synced).toContain('delete:')
423
- // crud count is 3
424
- expect(result.mutationCount).toBe(3)
425
- })
426
-
427
- test('ignores private files inside a namespace folder', () => {
428
- const files: Record<string, string> = {
429
- [`${DIR}/post/mutations.ts`]: '// fake',
430
- [`${DIR}/post/README.md`]: 'not a model',
431
- [`${DIR}/post/helpers/util.ts`]: 'private helper, ignored',
432
- [`${DIR}/post/post.d.ts`]: 'declaration file, ignored',
433
- [`${DIR}/post/post.test.ts`]: 'test file, ignored',
434
- [`${DIR}/post/post.spec.ts`]: 'spec file, ignored',
435
- }
436
-
437
- const fixtures: Record<string, LiteParsedFile> = {
438
- [`${DIR}/post/mutations.ts`]: {
439
- mutations: [{ modelName: 'post', handlers: [], schema: null }],
440
- queries: [],
441
- },
442
- }
443
-
444
- const result = generateLite({
445
- files,
446
- dir: DIR,
447
- parse: makeParse(fixtures),
448
- })
449
-
450
- expect(result.modelCount).toBe(1)
451
- const models = result.files['models.ts']!
452
- expect(models).toContain("import * as post from '../post/mutations'")
453
- expect(models).not.toContain('util')
454
- expect(models).not.toContain('README')
455
- expect(models).not.toContain('post.test')
456
- expect(models).not.toContain('post.spec')
457
- })
458
-
459
- test('supports a split namespace folder', () => {
460
- const files: Record<string, string> = {
461
- [`${DIR}/post/mutations.ts`]: '// fake',
462
- }
463
-
464
- const fixtures: Record<string, LiteParsedFile> = {
465
- [`${DIR}/post/mutations.ts`]: {
466
- mutations: [
467
- {
468
- modelName: 'post',
469
- handlers: [{ name: 'publish', paramTypeText: '{ id: string }' }],
470
- schema: null,
471
- },
472
- ],
473
- queries: [],
474
- },
475
- }
476
-
477
- const result = generateLite({
478
- files,
479
- dir: DIR,
480
- parse: makeParse(fixtures),
481
- })
482
-
483
- const models = result.files['models.ts']!
484
- expect(models).toContain("from '../post/mutations'")
485
- })
486
-
487
30
  test('derives single-file namespaces from data exports', () => {
488
31
  const files = {
489
32
  [`${DIR}/server.ts`]: `export const serverRows = () => zql.server`,
@@ -559,135 +102,6 @@ describe('generateLite', () => {
559
102
  ).toThrow(/removed top-level queries\/ layout/)
560
103
  })
561
104
 
562
- test('derives scoped related-table closure from lite metadata', () => {
563
- const files = {
564
- [`${DIR}/on-zero.config.ts`]: `export default defineConfig({ instances: { project: { scope: 'projectId' } } })`,
565
- [`${DIR}/project/message/queries.ts`]: `export const messages = () => zql.message.related('comments')`,
566
- [`${DIR}/project/message/mutations.ts`]: '// fake mutation',
567
- ['/proj/src/database/relations.ts']: '// fake relations',
568
- ['/proj/src/database/schema.ts']: '// fake tables',
569
- }
570
- const empty = { mutations: [], queries: [] }
571
- const result = generateLite({
572
- files,
573
- dir: DIR,
574
- parse: makeParse({
575
- [`${DIR}/on-zero.config.ts`]: {
576
- ...empty,
577
- dataConfig: { instances: { project: { scope: 'projectId' } } },
578
- },
579
- [`${DIR}/project/message/queries.ts`]: {
580
- ...empty,
581
- queries: [
582
- {
583
- name: 'messages',
584
- rootTable: 'message',
585
- paramTypeText: null,
586
- relatedPaths: [['comments', 'author']],
587
- },
588
- ],
589
- },
590
- [`${DIR}/project/message/mutations.ts`]: {
591
- ...empty,
592
- mutations: [
593
- {
594
- modelName: 'message',
595
- handlers: [],
596
- schema: {
597
- tableName: 'message',
598
- primaryKeys: ['id'],
599
- columns: [
600
- { name: 'id', builderText: 'string()' },
601
- { name: 'projectId', builderText: 'string()' },
602
- ],
603
- },
604
- },
605
- ],
606
- },
607
- '/proj/src/database/relations.ts': {
608
- ...empty,
609
- relations: [
610
- { sourceTable: 'message', name: 'comments', targetTable: 'comment' },
611
- { sourceTable: 'comment', name: 'author', targetTable: 'userPublic' },
612
- ],
613
- },
614
- '/proj/src/database/schema.ts': {
615
- ...empty,
616
- tables: [
617
- { name: 'comment', columns: ['id', 'projectId'] },
618
- { name: 'userPublic', columns: ['id', 'projectId'] },
619
- ],
620
- },
621
- }),
622
- })
623
-
624
- expect(result.files['instances.ts']).toContain(
625
- 'syncTables: ["comment","message","userPublic"]'
626
- )
627
- // the returned membership mirrors what instances.ts serializes, so hosts
628
- // read it structurally instead of re-parsing the emitted source
629
- const instance = result.instances.find((entry) => entry.name === 'project')
630
- expect(instance?.syncTables).toEqual(['comment', 'message', 'userPublic'])
631
- expect(instance?.supportTables).toEqual([])
632
- })
633
-
634
- test('derives query membership from the query root instead of its namespace', () => {
635
- const files = {
636
- [`${DIR}/category/mutations.ts`]: '// fake category model',
637
- [`${DIR}/dashboard/queries.ts`]: `export const monthSummary = () => zql.category.related('expenses')`,
638
- ['/proj/src/database/relations.ts']: '// fake relations',
639
- ['/proj/src/database/schema.ts']: '// fake tables',
640
- }
641
- const empty = { mutations: [], queries: [] }
642
- const result = generateLite({
643
- files,
644
- dir: DIR,
645
- parse: makeParse({
646
- [`${DIR}/category/mutations.ts`]: {
647
- ...empty,
648
- mutations: [
649
- {
650
- modelName: 'category',
651
- handlers: [],
652
- schema: null,
653
- },
654
- ],
655
- },
656
- [`${DIR}/dashboard/queries.ts`]: {
657
- ...empty,
658
- queries: [
659
- {
660
- name: 'monthSummary',
661
- rootTable: 'category',
662
- paramTypeText: null,
663
- relatedPaths: [['expenses']],
664
- },
665
- ],
666
- },
667
- '/proj/src/database/relations.ts': {
668
- ...empty,
669
- relations: [
670
- {
671
- sourceTable: 'category',
672
- name: 'expenses',
673
- targetTable: 'expense',
674
- },
675
- ],
676
- },
677
- '/proj/src/database/schema.ts': {
678
- ...empty,
679
- tables: [
680
- { name: 'category', columns: ['id'] },
681
- { name: 'expense', columns: ['id', 'categoryId'] },
682
- ],
683
- },
684
- }),
685
- })
686
-
687
- expect(result.instances[0]?.syncTables).toEqual(['category', 'expense'])
688
- expect(result.instances[0]?.syncTables).not.toContain('dashboard')
689
- })
690
-
691
105
  test('ignores related syntax mentioned only in a query-file comment', () => {
692
106
  const files = {
693
107
  [`${DIR}/dashboard/queries.ts`]: `// monthSummary moved to category/queries.ts because it uses .related()`,