on-zero 0.16.17 → 0.16.19
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/createZeroClient.cjs +44 -11
- package/dist/cjs/createZeroClient.native.js +54 -17
- package/dist/cjs/createZeroClient.native.js.map +1 -1
- package/dist/cjs/createZeroClient.recovery.test.cjs +49 -0
- package/dist/cjs/createZeroClient.recovery.test.native.js +53 -0
- package/dist/cjs/createZeroClient.recovery.test.native.js.map +1 -1
- package/dist/cjs/generate-helpers.cjs +28 -0
- package/dist/cjs/generate-helpers.native.js +36 -0
- package/dist/cjs/generate-helpers.native.js.map +1 -1
- package/dist/cjs/generate-layout.cjs +62 -53
- package/dist/cjs/generate-layout.native.js +62 -54
- package/dist/cjs/generate-layout.native.js.map +1 -1
- package/dist/cjs/generate-lite.cjs +28 -0
- package/dist/cjs/generate-lite.native.js +89 -33
- package/dist/cjs/generate-lite.native.js.map +1 -1
- package/dist/cjs/generate-lite.test.cjs +46 -0
- package/dist/cjs/generate-lite.test.native.js +46 -0
- package/dist/cjs/generate-lite.test.native.js.map +1 -1
- package/dist/cjs/generate.cjs +237 -243
- package/dist/cjs/generate.native.js +352 -353
- package/dist/cjs/generate.native.js.map +1 -1
- package/dist/cjs/generate.test.cjs +35 -0
- package/dist/cjs/generate.test.native.js +35 -0
- package/dist/cjs/generate.test.native.js.map +1 -1
- package/dist/cjs/helpers/createMutators.cjs +2 -1
- package/dist/cjs/helpers/createMutators.native.js +3 -1
- package/dist/cjs/helpers/createMutators.native.js.map +1 -1
- package/dist/cjs/helpers/recoverZeroClient.cjs +8 -2
- package/dist/cjs/helpers/recoverZeroClient.native.js +10 -2
- package/dist/cjs/helpers/recoverZeroClient.native.js.map +1 -1
- package/dist/cjs/helpers/recoverZeroClient.test.cjs +53 -0
- package/dist/cjs/helpers/recoverZeroClient.test.native.js +55 -0
- package/dist/cjs/helpers/recoverZeroClient.test.native.js.map +1 -1
- package/dist/cjs/native-typescript.cjs +190 -0
- package/dist/cjs/native-typescript.native.js +255 -0
- package/dist/cjs/native-typescript.native.js.map +1 -0
- package/dist/esm/createZeroClient.mjs +44 -11
- package/dist/esm/createZeroClient.mjs.map +1 -1
- package/dist/esm/createZeroClient.native.js +54 -17
- package/dist/esm/createZeroClient.native.js.map +1 -1
- package/dist/esm/createZeroClient.recovery.test.mjs +50 -1
- package/dist/esm/createZeroClient.recovery.test.mjs.map +1 -1
- package/dist/esm/createZeroClient.recovery.test.native.js +54 -1
- package/dist/esm/createZeroClient.recovery.test.native.js.map +1 -1
- package/dist/esm/generate-helpers.mjs +27 -1
- package/dist/esm/generate-helpers.mjs.map +1 -1
- package/dist/esm/generate-helpers.native.js +35 -1
- package/dist/esm/generate-helpers.native.js.map +1 -1
- package/dist/esm/generate-layout.mjs +52 -54
- package/dist/esm/generate-layout.mjs.map +1 -1
- package/dist/esm/generate-layout.native.js +52 -55
- package/dist/esm/generate-layout.native.js.map +1 -1
- package/dist/esm/generate-lite.mjs +29 -1
- package/dist/esm/generate-lite.mjs.map +1 -1
- package/dist/esm/generate-lite.native.js +90 -34
- package/dist/esm/generate-lite.native.js.map +1 -1
- package/dist/esm/generate-lite.test.mjs +46 -0
- package/dist/esm/generate-lite.test.mjs.map +1 -1
- package/dist/esm/generate-lite.test.native.js +46 -0
- package/dist/esm/generate-lite.test.native.js.map +1 -1
- package/dist/esm/generate.mjs +238 -244
- package/dist/esm/generate.mjs.map +1 -1
- package/dist/esm/generate.native.js +353 -354
- package/dist/esm/generate.native.js.map +1 -1
- package/dist/esm/generate.test.mjs +35 -0
- package/dist/esm/generate.test.mjs.map +1 -1
- package/dist/esm/generate.test.native.js +35 -0
- package/dist/esm/generate.test.native.js.map +1 -1
- package/dist/esm/helpers/createMutators.mjs +2 -1
- package/dist/esm/helpers/createMutators.mjs.map +1 -1
- package/dist/esm/helpers/createMutators.native.js +3 -1
- package/dist/esm/helpers/createMutators.native.js.map +1 -1
- package/dist/esm/helpers/recoverZeroClient.mjs +8 -2
- package/dist/esm/helpers/recoverZeroClient.mjs.map +1 -1
- package/dist/esm/helpers/recoverZeroClient.native.js +10 -2
- package/dist/esm/helpers/recoverZeroClient.native.js.map +1 -1
- package/dist/esm/helpers/recoverZeroClient.test.mjs +53 -0
- package/dist/esm/helpers/recoverZeroClient.test.mjs.map +1 -1
- package/dist/esm/helpers/recoverZeroClient.test.native.js +55 -0
- package/dist/esm/helpers/recoverZeroClient.test.native.js.map +1 -1
- package/dist/esm/index.native.js.map +1 -1
- package/dist/esm/native-typescript.mjs +164 -0
- package/dist/esm/native-typescript.mjs.map +1 -0
- package/dist/esm/native-typescript.native.js +226 -0
- package/dist/esm/native-typescript.native.js.map +1 -0
- package/package.json +4 -3
- package/src/createZeroClient.recovery.test.tsx +56 -1
- package/src/createZeroClient.tsx +54 -15
- package/src/generate-helpers.ts +79 -0
- package/src/generate-layout.ts +64 -108
- package/src/generate-lite.test.ts +49 -0
- package/src/generate-lite.ts +44 -0
- package/src/generate.test.ts +52 -0
- package/src/generate.ts +313 -366
- package/src/helpers/createMutators.ts +14 -3
- package/src/helpers/recoverZeroClient.test.ts +50 -0
- package/src/helpers/recoverZeroClient.ts +12 -2
- package/src/native-typescript.ts +176 -0
- package/types/createZeroClient.d.ts.map +1 -1
- package/types/generate-helpers.d.ts +13 -0
- package/types/generate-helpers.d.ts.map +1 -1
- package/types/generate-layout.d.ts +2 -1
- package/types/generate-layout.d.ts.map +1 -1
- package/types/generate-lite.d.ts +1 -0
- package/types/generate-lite.d.ts.map +1 -1
- package/types/generate.d.ts.map +1 -1
- package/types/helpers/createMutators.d.ts.map +1 -1
- package/types/helpers/recoverZeroClient.d.ts.map +1 -1
- package/types/native-typescript.d.ts +11 -0
- package/types/native-typescript.d.ts.map +1 -0
package/src/generate-layout.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, readdirSync, statSync } from 'node:fs'
|
|
2
2
|
import { basename, dirname, relative, resolve, sep } from 'node:path'
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import * as ts from 'typescript/unstable/ast'
|
|
5
|
+
|
|
6
|
+
import type { NativeTypeScriptProject } from './native-typescript'
|
|
5
7
|
|
|
6
8
|
export type DataNamespace = {
|
|
7
9
|
name: string
|
|
@@ -53,10 +55,7 @@ type ParsedInstanceConfig = {
|
|
|
53
55
|
supportTables: string[]
|
|
54
56
|
}
|
|
55
57
|
|
|
56
|
-
function propertyName(
|
|
57
|
-
ts: typeof import('typescript'),
|
|
58
|
-
name: ts.PropertyName
|
|
59
|
-
): string | null {
|
|
58
|
+
function propertyName(name: ts.PropertyName): string | null {
|
|
60
59
|
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
|
|
61
60
|
return name.text
|
|
62
61
|
}
|
|
@@ -64,7 +63,7 @@ function propertyName(
|
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
function readDataConfig(
|
|
67
|
-
|
|
66
|
+
project: NativeTypeScriptProject,
|
|
68
67
|
baseDir: string,
|
|
69
68
|
configPath: string | undefined
|
|
70
69
|
): { path: string; instances: ParsedInstanceConfig[] } | null {
|
|
@@ -76,13 +75,8 @@ function readDataConfig(
|
|
|
76
75
|
if (dirname(path) !== baseDir) {
|
|
77
76
|
throw new Error(`[on-zero] ${path} must be at the data root ${baseDir}`)
|
|
78
77
|
}
|
|
79
|
-
const source =
|
|
80
|
-
|
|
81
|
-
readFileSync(path, 'utf8'),
|
|
82
|
-
ts.ScriptTarget.Latest,
|
|
83
|
-
true
|
|
84
|
-
)
|
|
85
|
-
if (hasParseErrors(source)) throw new Error(`[on-zero] unable to parse ${path}`)
|
|
78
|
+
const source = project.sourceFile(path)
|
|
79
|
+
if (project.hasParseErrors(path)) throw new Error(`[on-zero] unable to parse ${path}`)
|
|
86
80
|
|
|
87
81
|
let config: ts.ObjectLiteralExpression | null = null
|
|
88
82
|
for (const statement of source.statements) {
|
|
@@ -108,7 +102,7 @@ function readDataConfig(
|
|
|
108
102
|
if (!ts.isPropertyAssignment(option)) {
|
|
109
103
|
throw new Error(`[on-zero] ${path} options must use explicit property assignments`)
|
|
110
104
|
}
|
|
111
|
-
const name = propertyName(
|
|
105
|
+
const name = propertyName(option.name)
|
|
112
106
|
if (!name) throw new Error(`[on-zero] ${path} has an unsupported option name`)
|
|
113
107
|
if (rootOptions.has(name))
|
|
114
108
|
throw new Error(`[on-zero] ${path} repeats option '${name}'`)
|
|
@@ -130,7 +124,7 @@ function readDataConfig(
|
|
|
130
124
|
`[on-zero] ${path} instances must use explicit property assignments`
|
|
131
125
|
)
|
|
132
126
|
}
|
|
133
|
-
const name = propertyName(
|
|
127
|
+
const name = propertyName(property.name)
|
|
134
128
|
if (!name) throw new Error(`[on-zero] ${path} has an unsupported instance name`)
|
|
135
129
|
if (!ts.isObjectLiteralExpression(property.initializer)) {
|
|
136
130
|
throw new Error(`[on-zero] instance '${name}' must be an object`)
|
|
@@ -146,7 +140,7 @@ function readDataConfig(
|
|
|
146
140
|
if (!ts.isPropertyAssignment(option)) {
|
|
147
141
|
throw new Error(`[on-zero] instance '${name}' options must be assignments`)
|
|
148
142
|
}
|
|
149
|
-
const optionName = propertyName(
|
|
143
|
+
const optionName = propertyName(option.name)
|
|
150
144
|
if (!optionName)
|
|
151
145
|
throw new Error(`[on-zero] instance '${name}' has an invalid option`)
|
|
152
146
|
if (seen.has(optionName)) {
|
|
@@ -215,18 +209,6 @@ function readDataConfig(
|
|
|
215
209
|
return { path, instances }
|
|
216
210
|
}
|
|
217
211
|
|
|
218
|
-
function hasParseErrors(source: ts.SourceFile): boolean {
|
|
219
|
-
// typescript keeps syntax diagnostics on SourceFile but does not expose them
|
|
220
|
-
// in its public type.
|
|
221
|
-
return Boolean(
|
|
222
|
-
(
|
|
223
|
-
source as unknown as {
|
|
224
|
-
parseDiagnostics?: readonly unknown[]
|
|
225
|
-
}
|
|
226
|
-
).parseDiagnostics?.length
|
|
227
|
-
)
|
|
228
|
-
}
|
|
229
|
-
|
|
230
212
|
/**
|
|
231
213
|
* Which kinds of data exports a single-file namespace declares.
|
|
232
214
|
*
|
|
@@ -241,18 +223,13 @@ function hasParseErrors(source: ts.SourceFile): boolean {
|
|
|
241
223
|
type NamespaceExportKinds = { model: boolean; query: boolean }
|
|
242
224
|
|
|
243
225
|
function namespaceExportKinds(
|
|
244
|
-
|
|
226
|
+
project: NativeTypeScriptProject,
|
|
245
227
|
baseDir: string,
|
|
246
228
|
path: string
|
|
247
229
|
): NamespaceExportKinds {
|
|
248
230
|
const none: NamespaceExportKinds = { model: false, query: false }
|
|
249
|
-
const source =
|
|
250
|
-
|
|
251
|
-
readFileSync(path, 'utf8'),
|
|
252
|
-
ts.ScriptTarget.Latest,
|
|
253
|
-
true
|
|
254
|
-
)
|
|
255
|
-
if (hasParseErrors(source)) {
|
|
231
|
+
const source = project.sourceFile(path)
|
|
232
|
+
if (project.hasParseErrors(path)) {
|
|
256
233
|
const displayPath = relative(dirname(baseDir), path).split(sep).join('/')
|
|
257
234
|
console.warn(`[on-zero] ignoring ${displayPath}: no recognized data exports`)
|
|
258
235
|
return none
|
|
@@ -319,7 +296,7 @@ function namespaceExportKinds(
|
|
|
319
296
|
}
|
|
320
297
|
}
|
|
321
298
|
let found = false
|
|
322
|
-
|
|
299
|
+
node.forEachChild((child) => {
|
|
323
300
|
if (!found && reachesQuery(child)) found = true
|
|
324
301
|
})
|
|
325
302
|
return found
|
|
@@ -330,16 +307,11 @@ function namespaceExportKinds(
|
|
|
330
307
|
}
|
|
331
308
|
|
|
332
309
|
function mutationTable(
|
|
333
|
-
|
|
310
|
+
project: NativeTypeScriptProject,
|
|
334
311
|
path: string,
|
|
335
312
|
namespace: string
|
|
336
313
|
): string {
|
|
337
|
-
const source =
|
|
338
|
-
path,
|
|
339
|
-
readFileSync(path, 'utf8'),
|
|
340
|
-
ts.ScriptTarget.Latest,
|
|
341
|
-
true
|
|
342
|
-
)
|
|
314
|
+
const source = project.sourceFile(path)
|
|
343
315
|
let schemaTable: string | null = null
|
|
344
316
|
|
|
345
317
|
for (const statement of source.statements) {
|
|
@@ -358,7 +330,7 @@ function mutationTable(
|
|
|
358
330
|
ts.isCallExpression(declaration.initializer)
|
|
359
331
|
) {
|
|
360
332
|
const firstArgument = declaration.initializer.arguments[0]
|
|
361
|
-
if (firstArgument && ts.
|
|
333
|
+
if (firstArgument && ts.isStringLiteral(firstArgument)) {
|
|
362
334
|
return firstArgument.text
|
|
363
335
|
}
|
|
364
336
|
}
|
|
@@ -368,12 +340,12 @@ function mutationTable(
|
|
|
368
340
|
ts.isCallExpression(node) &&
|
|
369
341
|
node.expression.getText(source) === 'table' &&
|
|
370
342
|
node.arguments[0] &&
|
|
371
|
-
ts.
|
|
343
|
+
ts.isStringLiteral(node.arguments[0])
|
|
372
344
|
) {
|
|
373
345
|
schemaTable = node.arguments[0].text
|
|
374
346
|
return
|
|
375
347
|
}
|
|
376
|
-
|
|
348
|
+
node.forEachChild(visit)
|
|
377
349
|
}
|
|
378
350
|
visit(declaration.initializer)
|
|
379
351
|
}
|
|
@@ -383,7 +355,7 @@ function mutationTable(
|
|
|
383
355
|
}
|
|
384
356
|
|
|
385
357
|
function discoverNamespaces(
|
|
386
|
-
|
|
358
|
+
project: NativeTypeScriptProject,
|
|
387
359
|
baseDir: string,
|
|
388
360
|
instance: DataInstance,
|
|
389
361
|
instanceDirs: Set<string>
|
|
@@ -395,7 +367,7 @@ function discoverNamespaces(
|
|
|
395
367
|
if (entry.isFile()) {
|
|
396
368
|
if (!isSourceFile(entry.name) || entry.name === 'on-zero.config.ts') continue
|
|
397
369
|
const path = resolve(instance.dir, entry.name)
|
|
398
|
-
const kinds = namespaceExportKinds(
|
|
370
|
+
const kinds = namespaceExportKinds(project, baseDir, path)
|
|
399
371
|
if (!kinds.model && !kinds.query) continue
|
|
400
372
|
const name = basename(entry.name, '.ts')
|
|
401
373
|
namespaces.push({
|
|
@@ -406,7 +378,7 @@ function discoverNamespaces(
|
|
|
406
378
|
// missing mutations.ts leaves modelPath null
|
|
407
379
|
modelPath: kinds.model ? path : null,
|
|
408
380
|
aggregatePath: null,
|
|
409
|
-
table: kinds.model ? mutationTable(
|
|
381
|
+
table: kinds.model ? mutationTable(project, path, name) : null,
|
|
410
382
|
sourcePaths: [path],
|
|
411
383
|
})
|
|
412
384
|
continue
|
|
@@ -439,7 +411,7 @@ function discoverNamespaces(
|
|
|
439
411
|
queryPath: hasQueries ? queryPath : null,
|
|
440
412
|
modelPath: hasMutations ? modelPath : null,
|
|
441
413
|
aggregatePath: hasAggregates ? aggregatePath : null,
|
|
442
|
-
table: hasMutations ? mutationTable(
|
|
414
|
+
table: hasMutations ? mutationTable(project, modelPath, entry.name) : null,
|
|
443
415
|
sourcePaths: [
|
|
444
416
|
hasQueries && queryPath,
|
|
445
417
|
hasMutations && modelPath,
|
|
@@ -471,23 +443,18 @@ function metadataPaths(baseDir: string): string[] {
|
|
|
471
443
|
}
|
|
472
444
|
|
|
473
445
|
function relationTargets(
|
|
474
|
-
|
|
446
|
+
project: NativeTypeScriptProject,
|
|
475
447
|
paths: string[]
|
|
476
448
|
): Map<string, Map<string, string>> {
|
|
477
449
|
const relations = new Map<string, Map<string, string>>()
|
|
478
450
|
for (const path of paths.filter((path) => basename(path) === 'relations.ts')) {
|
|
479
|
-
const source =
|
|
480
|
-
path,
|
|
481
|
-
readFileSync(path, 'utf8'),
|
|
482
|
-
ts.ScriptTarget.Latest,
|
|
483
|
-
true
|
|
484
|
-
)
|
|
451
|
+
const source = project.sourceFile(path)
|
|
485
452
|
const visit = (node: ts.Node) => {
|
|
486
453
|
if (
|
|
487
454
|
!ts.isCallExpression(node) ||
|
|
488
455
|
node.expression.getText(source) !== 'defineRelations'
|
|
489
456
|
) {
|
|
490
|
-
|
|
457
|
+
node.forEachChild(visit)
|
|
491
458
|
return
|
|
492
459
|
}
|
|
493
460
|
const factory = node.arguments[1]
|
|
@@ -527,7 +494,7 @@ function relationTargets(
|
|
|
527
494
|
}
|
|
528
495
|
|
|
529
496
|
function tableColumns(
|
|
530
|
-
|
|
497
|
+
project: NativeTypeScriptProject,
|
|
531
498
|
paths: string[],
|
|
532
499
|
namespaces: DataNamespace[]
|
|
533
500
|
): Map<string, Set<string>> {
|
|
@@ -537,26 +504,21 @@ function tableColumns(
|
|
|
537
504
|
...namespaces.flatMap((namespace) => namespace.sourcePaths),
|
|
538
505
|
])
|
|
539
506
|
for (const path of sources) {
|
|
540
|
-
const source =
|
|
541
|
-
path,
|
|
542
|
-
readFileSync(path, 'utf8'),
|
|
543
|
-
ts.ScriptTarget.Latest,
|
|
544
|
-
true
|
|
545
|
-
)
|
|
507
|
+
const source = project.sourceFile(path)
|
|
546
508
|
const visit = (node: ts.Node) => {
|
|
547
509
|
if (
|
|
548
510
|
!ts.isVariableDeclaration(node) ||
|
|
549
511
|
!ts.isIdentifier(node.name) ||
|
|
550
512
|
!node.initializer
|
|
551
513
|
) {
|
|
552
|
-
|
|
514
|
+
node.forEachChild(visit)
|
|
553
515
|
return
|
|
554
516
|
}
|
|
555
517
|
const tableNames = new Set([node.name.text])
|
|
556
518
|
let foundColumns: ts.ObjectLiteralExpression | null = null
|
|
557
519
|
const inspect = (candidate: ts.Node) => {
|
|
558
520
|
if (!ts.isCallExpression(candidate)) {
|
|
559
|
-
|
|
521
|
+
candidate.forEachChild(inspect)
|
|
560
522
|
return
|
|
561
523
|
}
|
|
562
524
|
for (const argument of candidate.arguments) {
|
|
@@ -574,12 +536,16 @@ function tableColumns(
|
|
|
574
536
|
if (foundColumns) {
|
|
575
537
|
const names = new Set(
|
|
576
538
|
(foundColumns as ts.ObjectLiteralExpression).properties
|
|
577
|
-
.map((property) =>
|
|
539
|
+
.map((property) =>
|
|
540
|
+
ts.isSpreadAssignment(property)
|
|
541
|
+
? null
|
|
542
|
+
: property.name?.getText(source).replace(/^['"]|['"]$/g, '')
|
|
543
|
+
)
|
|
578
544
|
.filter((name): name is string => Boolean(name))
|
|
579
545
|
)
|
|
580
546
|
for (const tableName of tableNames) columns.set(tableName, names)
|
|
581
547
|
}
|
|
582
|
-
|
|
548
|
+
node.forEachChild(visit)
|
|
583
549
|
}
|
|
584
550
|
visit(source)
|
|
585
551
|
}
|
|
@@ -587,17 +553,12 @@ function tableColumns(
|
|
|
587
553
|
}
|
|
588
554
|
|
|
589
555
|
function queriedTables(
|
|
590
|
-
|
|
556
|
+
project: NativeTypeScriptProject,
|
|
591
557
|
namespace: DataNamespace,
|
|
592
558
|
relations: Map<string, Map<string, string>>
|
|
593
559
|
): Array<{ table: string; query: string; root: boolean }> {
|
|
594
560
|
if (!namespace.queryPath) return []
|
|
595
|
-
const source =
|
|
596
|
-
namespace.queryPath,
|
|
597
|
-
readFileSync(namespace.queryPath, 'utf8'),
|
|
598
|
-
ts.ScriptTarget.Latest,
|
|
599
|
-
true
|
|
600
|
-
)
|
|
561
|
+
const source = project.sourceFile(namespace.queryPath)
|
|
601
562
|
const reached: Array<{ table: string; query: string; root: boolean }> = []
|
|
602
563
|
const functions = new Map<string, ts.ConciseBody>()
|
|
603
564
|
const exported = new Set<string>()
|
|
@@ -679,7 +640,7 @@ function queriedTables(
|
|
|
679
640
|
visiting.delete(key)
|
|
680
641
|
}
|
|
681
642
|
}
|
|
682
|
-
|
|
643
|
+
node.forEachChild((child) => visit(child, currentTable, query))
|
|
683
644
|
}
|
|
684
645
|
|
|
685
646
|
for (const name of exported) {
|
|
@@ -693,16 +654,11 @@ function queriedTables(
|
|
|
693
654
|
}
|
|
694
655
|
|
|
695
656
|
function aggregateTables(
|
|
696
|
-
|
|
657
|
+
project: NativeTypeScriptProject,
|
|
697
658
|
namespace: DataNamespace
|
|
698
659
|
): string[] {
|
|
699
660
|
if (!namespace.aggregatePath) return []
|
|
700
|
-
const source =
|
|
701
|
-
namespace.aggregatePath,
|
|
702
|
-
readFileSync(namespace.aggregatePath, 'utf8'),
|
|
703
|
-
ts.ScriptTarget.Latest,
|
|
704
|
-
true
|
|
705
|
-
)
|
|
661
|
+
const source = project.sourceFile(namespace.aggregatePath)
|
|
706
662
|
const unwrap = (node: ts.Expression): ts.Expression => {
|
|
707
663
|
let current = node
|
|
708
664
|
while (
|
|
@@ -754,14 +710,14 @@ function aggregateTables(
|
|
|
754
710
|
const field = definition.properties.find(
|
|
755
711
|
(candidate): candidate is ts.PropertyAssignment =>
|
|
756
712
|
ts.isPropertyAssignment(candidate) &&
|
|
757
|
-
propertyName(
|
|
713
|
+
propertyName(candidate.name) === fieldName
|
|
758
714
|
)
|
|
759
|
-
if (!field || !ts.
|
|
715
|
+
if (!field || !ts.isStringLiteral(unwrap(field.initializer))) {
|
|
760
716
|
throw new Error(
|
|
761
717
|
`[on-zero] ${namespace.aggregatePath} aggregate ${property.name.getText(source)} ${fieldName} must be a string literal`
|
|
762
718
|
)
|
|
763
719
|
}
|
|
764
|
-
tables.add((unwrap(field.initializer) as ts.
|
|
720
|
+
tables.add((unwrap(field.initializer) as ts.StringLiteral).text)
|
|
765
721
|
}
|
|
766
722
|
}
|
|
767
723
|
}
|
|
@@ -770,7 +726,7 @@ function aggregateTables(
|
|
|
770
726
|
}
|
|
771
727
|
|
|
772
728
|
function mutationSupportTables(
|
|
773
|
-
|
|
729
|
+
project: NativeTypeScriptProject,
|
|
774
730
|
baseDir: string,
|
|
775
731
|
sourceRoots: string[],
|
|
776
732
|
namespace: DataNamespace
|
|
@@ -782,12 +738,7 @@ function mutationSupportTables(
|
|
|
782
738
|
const scan = (path: string) => {
|
|
783
739
|
if (visited.has(path)) return
|
|
784
740
|
visited.add(path)
|
|
785
|
-
const source =
|
|
786
|
-
path,
|
|
787
|
-
readFileSync(path, 'utf8'),
|
|
788
|
-
ts.ScriptTarget.Latest,
|
|
789
|
-
true
|
|
790
|
-
)
|
|
741
|
+
const source = project.sourceFile(path)
|
|
791
742
|
|
|
792
743
|
const visit = (node: ts.Node) => {
|
|
793
744
|
if (
|
|
@@ -831,7 +782,7 @@ function mutationSupportTables(
|
|
|
831
782
|
}
|
|
832
783
|
}
|
|
833
784
|
|
|
834
|
-
|
|
785
|
+
node.forEachChild(visit)
|
|
835
786
|
}
|
|
836
787
|
visit(source)
|
|
837
788
|
}
|
|
@@ -861,7 +812,7 @@ function assertNoInstanceFiles(roots: string[]) {
|
|
|
861
812
|
}
|
|
862
813
|
|
|
863
814
|
function assertNoUnclaimedNamespaces(
|
|
864
|
-
|
|
815
|
+
project: NativeTypeScriptProject,
|
|
865
816
|
baseDir: string,
|
|
866
817
|
configPath: string,
|
|
867
818
|
instanceDirs: string[]
|
|
@@ -883,7 +834,7 @@ function assertNoUnclaimedNamespaces(
|
|
|
883
834
|
) {
|
|
884
835
|
continue
|
|
885
836
|
}
|
|
886
|
-
const kinds = namespaceExportKinds(
|
|
837
|
+
const kinds = namespaceExportKinds(project, baseDir, path)
|
|
887
838
|
if (kinds.model || kinds.query) {
|
|
888
839
|
throw new Error(
|
|
889
840
|
`[on-zero] data namespace ${path} is outside every instance directory declared in ${configPath}`
|
|
@@ -895,11 +846,11 @@ function assertNoUnclaimedNamespaces(
|
|
|
895
846
|
}
|
|
896
847
|
|
|
897
848
|
export function discoverDataLayout(
|
|
898
|
-
|
|
849
|
+
project: NativeTypeScriptProject,
|
|
899
850
|
baseDir: string,
|
|
900
851
|
configPath?: string
|
|
901
852
|
): DataLayout {
|
|
902
|
-
const config = readDataConfig(
|
|
853
|
+
const config = readDataConfig(project, baseDir, configPath)
|
|
903
854
|
const configured =
|
|
904
855
|
config?.instances ??
|
|
905
856
|
([
|
|
@@ -911,7 +862,7 @@ export function discoverDataLayout(
|
|
|
911
862
|
assertNoInstanceFiles(sourceRoots)
|
|
912
863
|
if (config) {
|
|
913
864
|
assertNoUnclaimedNamespaces(
|
|
914
|
-
|
|
865
|
+
project,
|
|
915
866
|
baseDir,
|
|
916
867
|
config.path,
|
|
917
868
|
configured.map((instance) => instance.dir)
|
|
@@ -930,7 +881,7 @@ export function discoverDataLayout(
|
|
|
930
881
|
const instanceDirs = new Set(instances.map((instance) => instance.dir))
|
|
931
882
|
|
|
932
883
|
for (const instance of instances) {
|
|
933
|
-
instance.namespaces = discoverNamespaces(
|
|
884
|
+
instance.namespaces = discoverNamespaces(project, baseDir, instance, instanceDirs)
|
|
934
885
|
}
|
|
935
886
|
const namespaces = instances.flatMap((instance) => instance.namespaces)
|
|
936
887
|
const namespaceOwners = new Map<string, string>()
|
|
@@ -956,8 +907,8 @@ export function discoverDataLayout(
|
|
|
956
907
|
}
|
|
957
908
|
|
|
958
909
|
const metadata = metadataPaths(baseDir)
|
|
959
|
-
const relations = relationTargets(
|
|
960
|
-
const columns = tableColumns(
|
|
910
|
+
const relations = relationTargets(project, metadata)
|
|
911
|
+
const columns = tableColumns(project, metadata, namespaces)
|
|
961
912
|
const relatedOwners = new Map<string, string>()
|
|
962
913
|
for (const instance of instances) {
|
|
963
914
|
const tables = new Set(
|
|
@@ -967,7 +918,7 @@ export function discoverDataLayout(
|
|
|
967
918
|
)
|
|
968
919
|
const syncTables = new Set(tables)
|
|
969
920
|
for (const namespace of instance.namespaces) {
|
|
970
|
-
for (const table of aggregateTables(
|
|
921
|
+
for (const table of aggregateTables(project, namespace)) {
|
|
971
922
|
const owner = tableOwners.get(table) ?? relatedOwners.get(table)
|
|
972
923
|
if (owner && owner !== instance.name) {
|
|
973
924
|
throw new Error(
|
|
@@ -979,7 +930,7 @@ export function discoverDataLayout(
|
|
|
979
930
|
tables.add(table)
|
|
980
931
|
syncTables.add(table)
|
|
981
932
|
}
|
|
982
|
-
for (const reached of queriedTables(
|
|
933
|
+
for (const reached of queriedTables(project, namespace, relations)) {
|
|
983
934
|
const owner = tableOwners.get(reached.table) ?? relatedOwners.get(reached.table)
|
|
984
935
|
if (owner && owner !== instance.name) {
|
|
985
936
|
throw new Error(
|
|
@@ -1012,7 +963,12 @@ export function discoverDataLayout(
|
|
|
1012
963
|
// instance's change log or every later pull throws on it.
|
|
1013
964
|
const supportTables = new Set<string>(instance.declaredSupportTables)
|
|
1014
965
|
for (const namespace of instance.namespaces) {
|
|
1015
|
-
for (const table of mutationSupportTables(
|
|
966
|
+
for (const table of mutationSupportTables(
|
|
967
|
+
project,
|
|
968
|
+
baseDir,
|
|
969
|
+
sourceRoots,
|
|
970
|
+
namespace
|
|
971
|
+
)) {
|
|
1016
972
|
if (tableOwners.has(table) || relatedOwners.has(table)) continue
|
|
1017
973
|
if (!instance.syncTables.includes(table)) {
|
|
1018
974
|
supportTables.add(table)
|
|
@@ -510,4 +510,53 @@ describe('generateLite', () => {
|
|
|
510
510
|
// an explicit `{ crud: true }` is the same as omitting the options object
|
|
511
511
|
expect(run(true).files['syncedMutations.ts']).toBe(validators)
|
|
512
512
|
})
|
|
513
|
+
|
|
514
|
+
test('writes drizzle-zero sqlite schema from database/schema.ts', () => {
|
|
515
|
+
const files = {
|
|
516
|
+
[`${DIR}/post.ts`]: `export const posts = () => zql.post`,
|
|
517
|
+
['/proj/src/database/schema.ts']: `export const post = sqliteTable('post', { id: text('id') })`,
|
|
518
|
+
['/proj/src/database/relations.ts']: `export const relations = defineRelations(schema, (r) => ({ post: { comments: r.many.comment({}) } }))`,
|
|
519
|
+
}
|
|
520
|
+
const empty = { mutations: [], queries: [] }
|
|
521
|
+
const result = generateLite({
|
|
522
|
+
files,
|
|
523
|
+
dir: DIR,
|
|
524
|
+
parse: makeParse({
|
|
525
|
+
[`${DIR}/post.ts`]: {
|
|
526
|
+
mutations: [],
|
|
527
|
+
queries: [
|
|
528
|
+
{
|
|
529
|
+
name: 'posts',
|
|
530
|
+
rootTable: 'post',
|
|
531
|
+
paramTypeText: null,
|
|
532
|
+
relatedPaths: [],
|
|
533
|
+
},
|
|
534
|
+
],
|
|
535
|
+
},
|
|
536
|
+
['/proj/src/database/schema.ts']: {
|
|
537
|
+
...empty,
|
|
538
|
+
tables: [{ name: 'post', columns: ['id'] }],
|
|
539
|
+
},
|
|
540
|
+
['/proj/src/database/relations.ts']: {
|
|
541
|
+
...empty,
|
|
542
|
+
relations: [
|
|
543
|
+
{
|
|
544
|
+
sourceTable: 'post',
|
|
545
|
+
name: 'comments',
|
|
546
|
+
targetTable: 'comment',
|
|
547
|
+
sourceText: 'comments: r.many.comment({})',
|
|
548
|
+
},
|
|
549
|
+
],
|
|
550
|
+
},
|
|
551
|
+
}),
|
|
552
|
+
})
|
|
553
|
+
expect(result.files['drizzleSchema.ts']).toContain(
|
|
554
|
+
'export { post } from "../../database/schema"'
|
|
555
|
+
)
|
|
556
|
+
expect(result.files['schema.ts']).toContain(
|
|
557
|
+
'import * as drizzleSchema from "./drizzleSchema"'
|
|
558
|
+
)
|
|
559
|
+
expect(result.files['schema.ts']).toContain('export type Post =')
|
|
560
|
+
expect(result.files['drizzleSchema.ts']).not.toContain('comments:')
|
|
561
|
+
})
|
|
513
562
|
})
|
package/src/generate-lite.ts
CHANGED
|
@@ -27,6 +27,8 @@ import {
|
|
|
27
27
|
generateTypesFile,
|
|
28
28
|
parseColumnType,
|
|
29
29
|
parseTypeString,
|
|
30
|
+
renderDrizzleSchemaInputModule,
|
|
31
|
+
renderDrizzleZeroSqliteSchemaModule,
|
|
30
32
|
} from './generate-helpers'
|
|
31
33
|
|
|
32
34
|
import type { ModelMutations, SchemaColumn } from './generate-helpers'
|
|
@@ -91,6 +93,8 @@ export type LiteRelationInfo = {
|
|
|
91
93
|
sourceTable: string
|
|
92
94
|
name: string
|
|
93
95
|
targetTable: string
|
|
96
|
+
// original property text from defineRelations, used to emit drizzleSchema.ts
|
|
97
|
+
sourceText?: string
|
|
94
98
|
}
|
|
95
99
|
|
|
96
100
|
export type LiteTableInfo = {
|
|
@@ -468,6 +472,7 @@ export function generateLite(opts: LiteGenerateOptions): LiteGenerateResult {
|
|
|
468
472
|
aggregateTables.set(namespace.name, [...new Set(parsed.aggregateTables ?? [])])
|
|
469
473
|
}
|
|
470
474
|
const relations = new Map<string, Map<string, string>>()
|
|
475
|
+
const drizzleRelations: LiteRelationInfo[] = []
|
|
471
476
|
const tableColumns = new Map<string, Set<string>>()
|
|
472
477
|
for (const path of Object.keys(files).filter(
|
|
473
478
|
(path) =>
|
|
@@ -479,6 +484,7 @@ export function generateLite(opts: LiteGenerateOptions): LiteGenerateResult {
|
|
|
479
484
|
const tableRelations = relations.get(relation.sourceTable) ?? new Map()
|
|
480
485
|
tableRelations.set(relation.name, relation.targetTable)
|
|
481
486
|
relations.set(relation.sourceTable, tableRelations)
|
|
487
|
+
drizzleRelations.push(relation)
|
|
482
488
|
}
|
|
483
489
|
for (const table of parsed.tables ?? []) {
|
|
484
490
|
tableColumns.set(table.name, new Set(table.columns))
|
|
@@ -828,6 +834,44 @@ export function generateLite(opts: LiteGenerateOptions): LiteGenerateResult {
|
|
|
828
834
|
)
|
|
829
835
|
}
|
|
830
836
|
|
|
837
|
+
const allTables = [
|
|
838
|
+
...new Set(
|
|
839
|
+
instances.flatMap((instance) => [
|
|
840
|
+
...(syncTables.get(instance.name) ?? []),
|
|
841
|
+
...(supportTables.get(instance.name) ?? []),
|
|
842
|
+
])
|
|
843
|
+
),
|
|
844
|
+
].sort()
|
|
845
|
+
const hasDatabaseSchema = Object.keys(files).some((path) =>
|
|
846
|
+
/\/database\/schema\.ts$/.test(path)
|
|
847
|
+
)
|
|
848
|
+
if (hasDatabaseSchema && allTables.length > 0) {
|
|
849
|
+
const included = new Set(allTables)
|
|
850
|
+
const relationTables = new Map<string, string[]>()
|
|
851
|
+
for (const relation of drizzleRelations) {
|
|
852
|
+
if (!included.has(relation.sourceTable) || !included.has(relation.targetTable)) {
|
|
853
|
+
continue
|
|
854
|
+
}
|
|
855
|
+
const properties = relationTables.get(relation.sourceTable) ?? []
|
|
856
|
+
properties.push(
|
|
857
|
+
relation.sourceText ?? `${relation.name}: r.many.${relation.targetTable}({})`
|
|
858
|
+
)
|
|
859
|
+
relationTables.set(relation.sourceTable, properties)
|
|
860
|
+
}
|
|
861
|
+
out['drizzleSchema.ts'] = renderDrizzleSchemaInputModule({
|
|
862
|
+
schemaImportPath: '../../database/schema',
|
|
863
|
+
tableNames: allTables,
|
|
864
|
+
relationTables: [...relationTables.entries()].map(([key, properties]) => ({
|
|
865
|
+
key,
|
|
866
|
+
properties,
|
|
867
|
+
})),
|
|
868
|
+
})
|
|
869
|
+
out['schema.ts'] = renderDrizzleZeroSqliteSchemaModule({
|
|
870
|
+
importPath: './drizzleSchema',
|
|
871
|
+
tableNames: allTables,
|
|
872
|
+
})
|
|
873
|
+
}
|
|
874
|
+
|
|
831
875
|
// count mutations the same way `generate()` does: one per generated crud slot
|
|
832
876
|
// plus non-crud custom mutations (the crud-ops set is excluded when hasCRUD).
|
|
833
877
|
let mutationCount = 0
|
package/src/generate.test.ts
CHANGED
|
@@ -87,6 +87,58 @@ describe('generate', () => {
|
|
|
87
87
|
expect(existsSync(syncedQueriesPath)).toBe(true)
|
|
88
88
|
})
|
|
89
89
|
|
|
90
|
+
test('rewrites a generated file whose contents changed underneath it', async () => {
|
|
91
|
+
writeFileSync(
|
|
92
|
+
join(testDir, 'post/mutations.ts'),
|
|
93
|
+
`export const schema = table('post', { id: string() })`
|
|
94
|
+
)
|
|
95
|
+
await generate({ dir: testDir, silent: true })
|
|
96
|
+
|
|
97
|
+
// a checkout, merge or revert replaces generated output without telling the
|
|
98
|
+
// cache, which still records the hash this generator last wrote there. the
|
|
99
|
+
// file exists and the recomputed content is unchanged, so every signal
|
|
100
|
+
// except the bytes themselves says there is nothing to do.
|
|
101
|
+
const modelsPath = join(testDir, 'generated/models.ts')
|
|
102
|
+
const generated = readFileSync(modelsPath, 'utf-8')
|
|
103
|
+
writeFileSync(modelsPath, '// replaced by a checkout\n')
|
|
104
|
+
|
|
105
|
+
await generate({ dir: testDir, silent: true })
|
|
106
|
+
|
|
107
|
+
expect(readFileSync(modelsPath, 'utf-8')).toBe(generated)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('writes drizzle-zero sqlite schema from database/schema.ts', async () => {
|
|
111
|
+
const dataDir = join(testDir, 'src/data')
|
|
112
|
+
writeFileSync(join(dataDir, 'post.ts'), `export const posts = () => zql.post`)
|
|
113
|
+
writeFileSync(
|
|
114
|
+
join(testDir, 'src/database/schema.ts'),
|
|
115
|
+
`
|
|
116
|
+
import { sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
|
117
|
+
export const post = sqliteTable('post', { id: text('id').primaryKey() })
|
|
118
|
+
`
|
|
119
|
+
)
|
|
120
|
+
writeFileSync(
|
|
121
|
+
join(testDir, 'src/database/relations.ts'),
|
|
122
|
+
`
|
|
123
|
+
export const relations = defineRelations(schema, (r) => ({
|
|
124
|
+
post: { comments: r.many.comment({}) },
|
|
125
|
+
}))
|
|
126
|
+
`
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
await generate({ dir: dataDir, silent: true })
|
|
130
|
+
|
|
131
|
+
expect(readFileSync(join(dataDir, 'generated/drizzleSchema.ts'), 'utf8')).toContain(
|
|
132
|
+
'export { post } from "../../database/schema"'
|
|
133
|
+
)
|
|
134
|
+
expect(readFileSync(join(dataDir, 'generated/schema.ts'), 'utf8')).toContain(
|
|
135
|
+
'import * as drizzleSchema from "./drizzleSchema"'
|
|
136
|
+
)
|
|
137
|
+
expect(readFileSync(join(dataDir, 'generated/schema.ts'), 'utf8')).toContain(
|
|
138
|
+
'export type Post ='
|
|
139
|
+
)
|
|
140
|
+
})
|
|
141
|
+
|
|
90
142
|
test('generates every crud slot for a default table registration', async () => {
|
|
91
143
|
writeFileSync(
|
|
92
144
|
join(testDir, 'post/mutations.ts'),
|