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.
- package/dist/cjs/generate-helpers.test.cjs +0 -38
- package/dist/cjs/generate-helpers.test.native.js +0 -38
- package/dist/cjs/generate-helpers.test.native.js.map +1 -1
- package/dist/cjs/generate-lite.test.cjs +0 -522
- package/dist/cjs/generate-lite.test.native.js +0 -525
- package/dist/cjs/generate-lite.test.native.js.map +1 -1
- package/dist/cjs/generate.test.cjs +0 -691
- package/dist/cjs/generate.test.native.js +0 -691
- package/dist/cjs/generate.test.native.js.map +1 -1
- package/dist/cjs/mutations.test.cjs +0 -11
- package/dist/cjs/mutations.test.native.js +0 -18
- package/dist/cjs/mutations.test.native.js.map +1 -1
- package/dist/cjs/server.test.cjs +0 -93
- package/dist/cjs/server.test.native.js +0 -101
- package/dist/cjs/server.test.native.js.map +1 -1
- package/dist/cjs/serverWhere.test.cjs +0 -45
- package/dist/cjs/serverWhere.test.native.js +0 -51
- package/dist/cjs/serverWhere.test.native.js.map +1 -1
- package/dist/esm/generate-helpers.test.mjs +1 -39
- package/dist/esm/generate-helpers.test.mjs.map +1 -1
- package/dist/esm/generate-helpers.test.native.js +1 -39
- package/dist/esm/generate-helpers.test.native.js.map +1 -1
- package/dist/esm/generate-lite.test.mjs +0 -522
- package/dist/esm/generate-lite.test.mjs.map +1 -1
- package/dist/esm/generate-lite.test.native.js +0 -525
- package/dist/esm/generate-lite.test.native.js.map +1 -1
- package/dist/esm/generate.test.mjs +2 -668
- package/dist/esm/generate.test.mjs.map +1 -1
- package/dist/esm/generate.test.native.js +2 -668
- package/dist/esm/generate.test.native.js.map +1 -1
- package/dist/esm/mutations.test.mjs +0 -11
- package/dist/esm/mutations.test.mjs.map +1 -1
- package/dist/esm/mutations.test.native.js +0 -18
- package/dist/esm/mutations.test.native.js.map +1 -1
- package/dist/esm/server.test.mjs +0 -93
- package/dist/esm/server.test.mjs.map +1 -1
- package/dist/esm/server.test.native.js +0 -101
- package/dist/esm/server.test.native.js.map +1 -1
- package/dist/esm/serverWhere.test.mjs +0 -45
- package/dist/esm/serverWhere.test.mjs.map +1 -1
- package/dist/esm/serverWhere.test.native.js +0 -51
- package/dist/esm/serverWhere.test.native.js.map +1 -1
- package/package.json +1 -1
- package/src/generate-helpers.test.ts +0 -40
- package/src/generate-lite.test.ts +0 -586
- package/src/generate.test.ts +0 -844
- package/src/mutations.test.ts +0 -21
- package/src/server.test.ts +0 -75
- 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()`,
|