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
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { existsSync, mkdirSync,
|
|
1
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync as writeFile } from "fs";
|
|
2
2
|
import { tmpdir } from "os";
|
|
3
3
|
import { dirname, join } from "path";
|
|
4
4
|
import { runInNewContext } from "vm";
|
|
5
|
-
import * as zero from "@rocicorp/zero";
|
|
6
5
|
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
7
|
-
import { deriveDataMembership, generate,
|
|
6
|
+
import { deriveDataMembership, generate, generateDrizzleSchemaInputFile } from "./generate.native.js";
|
|
8
7
|
var testDir = join(tmpdir(), "on-zero-test-" + Date.now());
|
|
9
8
|
function writeFileSync(path, content) {
|
|
10
9
|
mkdirSync(dirname(path), {
|
|
@@ -25,105 +24,6 @@ afterEach(function () {
|
|
|
25
24
|
});
|
|
26
25
|
});
|
|
27
26
|
describe("generate", function () {
|
|
28
|
-
test("generates models.ts, types.ts, tables.ts from model files", async function () {
|
|
29
|
-
writeFileSync(join(testDir, "post/mutations.ts"), `
|
|
30
|
-
import { table, string, boolean } from 'on-zero'
|
|
31
|
-
|
|
32
|
-
export const schema = table('post', {
|
|
33
|
-
id: string(),
|
|
34
|
-
title: string(),
|
|
35
|
-
published: boolean(),
|
|
36
|
-
})
|
|
37
|
-
`);
|
|
38
|
-
writeFileSync(join(testDir, "comment/mutations.ts"), `
|
|
39
|
-
import { table, string } from 'on-zero'
|
|
40
|
-
|
|
41
|
-
export const schema = table('comment', {
|
|
42
|
-
id: string(),
|
|
43
|
-
postId: string(),
|
|
44
|
-
body: string(),
|
|
45
|
-
})
|
|
46
|
-
`);
|
|
47
|
-
writeFileSync(join(testDir, "post/post.test.ts"), `throw new Error('test files must not be generated as models')`);
|
|
48
|
-
writeFileSync(join(testDir, "comment/comment.spec.ts"), `throw new Error('spec files must not be generated as queries')`);
|
|
49
|
-
var result = await generate({
|
|
50
|
-
dir: testDir,
|
|
51
|
-
silent: true
|
|
52
|
-
});
|
|
53
|
-
expect(result.modelCount).toBe(2);
|
|
54
|
-
expect(result.schemaCount).toBe(2);
|
|
55
|
-
expect(result.filesChanged).toBeGreaterThan(0);
|
|
56
|
-
expect(existsSync(join(testDir, "generated/models.ts"))).toBe(true);
|
|
57
|
-
expect(existsSync(join(testDir, "generated/types.ts"))).toBe(true);
|
|
58
|
-
expect(existsSync(join(testDir, "generated/tables.ts"))).toBe(true);
|
|
59
|
-
var modelsContent = readFileSync(join(testDir, "generated/models.ts"), "utf-8");
|
|
60
|
-
expect(modelsContent).toContain("import * as comment from '../comment/mutations'");
|
|
61
|
-
expect(modelsContent).toContain("import * as post from '../post/mutations'");
|
|
62
|
-
expect(modelsContent).not.toContain("post.test");
|
|
63
|
-
expect(modelsContent).toContain("export const models = {");
|
|
64
|
-
var typesContent = readFileSync(join(testDir, "generated/types.ts"), "utf-8");
|
|
65
|
-
expect(typesContent).toContain("export type Post = TableInsertRow<typeof schema.post>");
|
|
66
|
-
expect(typesContent).toContain("export type Comment = TableInsertRow<typeof schema.comment>");
|
|
67
|
-
var tablesContent = readFileSync(join(testDir, "generated/tables.ts"), "utf-8");
|
|
68
|
-
expect(tablesContent).toContain("export { schema as post } from '../post/mutations'");
|
|
69
|
-
expect(tablesContent).toContain("export { schema as comment } from '../comment/mutations'");
|
|
70
|
-
});
|
|
71
|
-
test("generates query validators from query files", async function () {
|
|
72
|
-
writeFileSync(join(testDir, "post/mutations.ts"), `export const schema = table('post', { id: string() })`);
|
|
73
|
-
writeFileSync(join(testDir, "post/queries.ts"), `
|
|
74
|
-
import { zero } from '../zero'
|
|
75
|
-
|
|
76
|
-
export const allPosts = () => zero.query.post
|
|
77
|
-
|
|
78
|
-
export const postById = ({ id }: { id: string }) => zero.query.post.where('id', id)
|
|
79
|
-
|
|
80
|
-
export const postsByAuthor = ({ authorId, limit }: { authorId: string; limit?: number }) =>
|
|
81
|
-
zero.query.post.where('authorId', authorId).limit(limit ?? 10)
|
|
82
|
-
`);
|
|
83
|
-
var result = await generate({
|
|
84
|
-
dir: testDir,
|
|
85
|
-
silent: true
|
|
86
|
-
});
|
|
87
|
-
expect(result.queryCount).toBe(3);
|
|
88
|
-
expect(existsSync(join(testDir, "generated/groupedQueries.ts"))).toBe(true);
|
|
89
|
-
expect(existsSync(join(testDir, "generated/syncedQueries.ts"))).toBe(true);
|
|
90
|
-
var groupedContent = readFileSync(join(testDir, "generated/groupedQueries.ts"), "utf-8");
|
|
91
|
-
expect(groupedContent).toContain("import * as postSource from '../post/queries'");
|
|
92
|
-
expect(groupedContent).toContain("postById: postSource.postById");
|
|
93
|
-
var syncedContent = readFileSync(join(testDir, "generated/syncedQueries.ts"), "utf-8");
|
|
94
|
-
expect(syncedContent).toContain("allPosts: defineQuery");
|
|
95
|
-
expect(syncedContent).toContain("postById: defineQuery");
|
|
96
|
-
expect(syncedContent).toContain("postsByAuthor: defineQuery");
|
|
97
|
-
expect(syncedContent).toContain("v.object");
|
|
98
|
-
});
|
|
99
|
-
test("skips permission exports in queries", async function () {
|
|
100
|
-
writeFileSync(join(testDir, "post/mutations.ts"), `export const schema = table('post', { id: string() })`);
|
|
101
|
-
writeFileSync(join(testDir, "post/queries.ts"), `
|
|
102
|
-
export const permission = () => ({ canRead: true })
|
|
103
|
-
export const allPosts = () => zero.query.post
|
|
104
|
-
`);
|
|
105
|
-
var result = await generate({
|
|
106
|
-
dir: testDir,
|
|
107
|
-
silent: true
|
|
108
|
-
});
|
|
109
|
-
expect(result.queryCount).toBe(1);
|
|
110
|
-
var syncedContent = readFileSync(join(testDir, "generated/syncedQueries.ts"), "utf-8");
|
|
111
|
-
expect(syncedContent).toContain("allPosts");
|
|
112
|
-
expect(syncedContent).not.toContain("permission:");
|
|
113
|
-
});
|
|
114
|
-
test("aliases user import without changing the model key", async function () {
|
|
115
|
-
writeFileSync(join(testDir, "user/mutations.ts"), `export const schema = table('user', { id: string(), name: string() })`);
|
|
116
|
-
await generate({
|
|
117
|
-
dir: testDir,
|
|
118
|
-
silent: true
|
|
119
|
-
});
|
|
120
|
-
var modelsContent = readFileSync(join(testDir, "generated/models.ts"), "utf-8");
|
|
121
|
-
expect(modelsContent).toContain("import * as userPublic from '../user/mutations'");
|
|
122
|
-
expect(modelsContent).toContain("user: userPublic,");
|
|
123
|
-
expect(modelsContent).not.toContain("\n userPublic,");
|
|
124
|
-
var typesContent = readFileSync(join(testDir, "generated/types.ts"), "utf-8");
|
|
125
|
-
expect(typesContent).toContain("typeof schema.userPublic");
|
|
126
|
-
});
|
|
127
27
|
test("runs after command when files change", async function () {
|
|
128
28
|
writeFileSync(join(testDir, "post/mutations.ts"), `export const schema = table('post', { id: string() })`);
|
|
129
29
|
var markerFile = join(testDir, "after-ran");
|
|
@@ -169,51 +69,6 @@ describe("instance layout", function () {
|
|
|
169
69
|
var dataDir = function dataDir2() {
|
|
170
70
|
return join(testDir, "src/data");
|
|
171
71
|
};
|
|
172
|
-
test("discovers file and folder namespaces and emits related sync closure", async function () {
|
|
173
|
-
writeFileSync(join(dataDir(), "control/reaction.ts"), `export const allReactions = () => zql.reaction`);
|
|
174
|
-
writeFileSync(join(dataDir(), "on-zero.config.ts"), `export default defineConfig({ instances: { control: {}, project: { scope: 'projectId' } } })`);
|
|
175
|
-
writeFileSync(join(dataDir(), "project/message/queries.ts"), `
|
|
176
|
-
const unused = () => zql.message.related('notARealRelation')
|
|
177
|
-
const withComments = () => zql.message.related('comments', (q) => q.related('author'))
|
|
178
|
-
export const messages = () => withComments()
|
|
179
|
-
`);
|
|
180
|
-
writeFileSync(join(dataDir(), "project/message/mutations.ts"), `export const schema = table('message').columns({ id: string(), projectId: string() })`);
|
|
181
|
-
writeFileSync(join(dataDir(), "project/message/helpers.ts"), `export const privateHelper = () => 'ignored'`);
|
|
182
|
-
writeFileSync(join(testDir, "src/database/relations.ts"), `
|
|
183
|
-
export const relations = defineRelations(schema, (r) => ({
|
|
184
|
-
message: { comments: r.many.comment({}) },
|
|
185
|
-
comment: { author: r.one.userPublic({}) },
|
|
186
|
-
}))
|
|
187
|
-
`);
|
|
188
|
-
writeFileSync(join(testDir, "src/database/schema.ts"), `
|
|
189
|
-
export const comment = sqliteTable('comment', { id: text(), projectId: text() })
|
|
190
|
-
export const userPublic = sqliteTable('user_public', { id: text(), projectId: text() })
|
|
191
|
-
`);
|
|
192
|
-
var membership = await deriveDataMembership({
|
|
193
|
-
dir: dataDir()
|
|
194
|
-
});
|
|
195
|
-
expect(membership.allTables).toEqual(["comment", "message", "reaction", "userPublic"]);
|
|
196
|
-
expect(membership.instances.project).toEqual({
|
|
197
|
-
tables: ["message"],
|
|
198
|
-
syncTables: ["comment", "message", "userPublic"],
|
|
199
|
-
supportTables: [],
|
|
200
|
-
scope: "projectId"
|
|
201
|
-
});
|
|
202
|
-
await generate({
|
|
203
|
-
dir: dataDir(),
|
|
204
|
-
silent: true
|
|
205
|
-
});
|
|
206
|
-
var grouped = readFileSync(join(dataDir(), "generated/groupedQueries.ts"), "utf8");
|
|
207
|
-
expect(grouped).toContain("from '../control/reaction'");
|
|
208
|
-
expect(grouped).toContain("from '../project/message/queries'");
|
|
209
|
-
expect(grouped).not.toContain("privateHelper");
|
|
210
|
-
var manifest = readFileSync(join(dataDir(), "generated/instances.ts"), "utf8");
|
|
211
|
-
expect(manifest).toContain("control: {");
|
|
212
|
-
expect(manifest).toContain("project: {");
|
|
213
|
-
expect(manifest).toContain('tables: ["message"]');
|
|
214
|
-
expect(manifest).toContain('syncTables: ["comment","message","userPublic"]');
|
|
215
|
-
expect(manifest).toContain(`defaultVisibility: (value: string) => ({ column: "projectId", value })`);
|
|
216
|
-
});
|
|
217
72
|
test("derives query membership from the returned query root", async function () {
|
|
218
73
|
writeFileSync(join(dataDir(), "category/mutations.ts"), `export const schema = table('category').columns({ id: string() })`);
|
|
219
74
|
writeFileSync(join(dataDir(), "dashboard/queries.ts"), `export const monthSummary = () => zql.category.related('expenses')`);
|
|
@@ -432,42 +287,6 @@ export const messages = () => zql.message.related('author')
|
|
|
432
287
|
silent: true
|
|
433
288
|
})).rejects.toThrow(/namespace 'message'.*instances 'control' and 'project'/);
|
|
434
289
|
});
|
|
435
|
-
test("resolves configured instance directories relative to the config file", async function () {
|
|
436
|
-
writeFileSync(join(dataDir(), "on-zero.config.ts"), `export default defineConfig({ instances: { control: { dir: './planes/control-data', supportTables: ['audit'] }, project: { dir: '../project-data', scope: 'projectId' } } })`);
|
|
437
|
-
writeFileSync(join(dataDir(), "planes/control-data/account.ts"), `export const accounts = () => zql.account`);
|
|
438
|
-
writeFileSync(join(testDir, "src/project-data/message.ts"), `export const schema = table('message').columns({ id: string(), projectId: string() })`);
|
|
439
|
-
await expect(deriveDataMembership({
|
|
440
|
-
dir: dataDir(),
|
|
441
|
-
config: join(dataDir(), "on-zero.config.ts")
|
|
442
|
-
})).resolves.toEqual({
|
|
443
|
-
instances: {
|
|
444
|
-
control: {
|
|
445
|
-
tables: ["account"],
|
|
446
|
-
syncTables: ["account"],
|
|
447
|
-
supportTables: ["audit"],
|
|
448
|
-
scope: null
|
|
449
|
-
},
|
|
450
|
-
project: {
|
|
451
|
-
tables: ["message"],
|
|
452
|
-
syncTables: ["message"],
|
|
453
|
-
supportTables: [],
|
|
454
|
-
scope: "projectId"
|
|
455
|
-
}
|
|
456
|
-
},
|
|
457
|
-
allTables: ["account", "audit", "message"]
|
|
458
|
-
});
|
|
459
|
-
await expect(generate({
|
|
460
|
-
dir: dataDir(),
|
|
461
|
-
config: join(dataDir(), "on-zero.config.ts"),
|
|
462
|
-
silent: true
|
|
463
|
-
})).resolves.toMatchObject({
|
|
464
|
-
modelCount: 1,
|
|
465
|
-
schemaCount: 1
|
|
466
|
-
});
|
|
467
|
-
var models = readFileSync(join(dataDir(), "generated/models.ts"), "utf8");
|
|
468
|
-
expect(models).toContain("from '../../project-data/message'");
|
|
469
|
-
expect(models).not.toContain("account");
|
|
470
|
-
});
|
|
471
290
|
test("rejects missing configured instance directories", async function () {
|
|
472
291
|
writeFileSync(join(dataDir(), "on-zero.config.ts"), `export default defineConfig({ instances: { project: { dir: './missing' } } })`);
|
|
473
292
|
await expect(deriveDataMembership({
|
|
@@ -512,253 +331,6 @@ export const messages = () => zql.message.related('author')
|
|
|
512
331
|
});
|
|
513
332
|
});
|
|
514
333
|
describe("mutations", function () {
|
|
515
|
-
test("generates validators for inline mutation param types", async function () {
|
|
516
|
-
writeFileSync(join(testDir, "post/mutations.ts"), `
|
|
517
|
-
import { table, string } from 'on-zero'
|
|
518
|
-
import { mutations, serverWhere } from 'on-zero'
|
|
519
|
-
|
|
520
|
-
export const schema = table('post').columns({
|
|
521
|
-
id: string(),
|
|
522
|
-
title: string(),
|
|
523
|
-
}).primaryKey('id')
|
|
524
|
-
|
|
525
|
-
const perm = serverWhere('post', () => true)
|
|
526
|
-
|
|
527
|
-
export const mutate = mutations(schema, perm, {
|
|
528
|
-
archive: async ({ tx }, { id, reason }: { id: string; reason: string }) => {
|
|
529
|
-
await tx.mutate.post.update({ id, archived: true })
|
|
530
|
-
},
|
|
531
|
-
})
|
|
532
|
-
`);
|
|
533
|
-
var result = await generate({
|
|
534
|
-
dir: testDir,
|
|
535
|
-
silent: true
|
|
536
|
-
});
|
|
537
|
-
expect(result.mutationCount).toBeGreaterThan(0);
|
|
538
|
-
expect(existsSync(join(testDir, "generated/syncedMutations.ts"))).toBe(true);
|
|
539
|
-
var content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
|
|
540
|
-
expect(content).toContain("archive");
|
|
541
|
-
expect(content).toContain("v.object");
|
|
542
|
-
expect(content).toContain("v.string()");
|
|
543
|
-
});
|
|
544
|
-
test("generates CRUD validators from schema columns", async function () {
|
|
545
|
-
writeFileSync(join(testDir, "task/mutations.ts"), `
|
|
546
|
-
import { table, string, number, boolean } from 'on-zero'
|
|
547
|
-
import { mutations, serverWhere } from 'on-zero'
|
|
548
|
-
|
|
549
|
-
export const schema = table('task').columns({
|
|
550
|
-
id: string(),
|
|
551
|
-
title: string(),
|
|
552
|
-
priority: number(),
|
|
553
|
-
done: boolean(),
|
|
554
|
-
note: string().optional(),
|
|
555
|
-
}).primaryKey('id')
|
|
556
|
-
|
|
557
|
-
const perm = serverWhere('task', () => true)
|
|
558
|
-
|
|
559
|
-
export const mutate = mutations(schema, perm)
|
|
560
|
-
`);
|
|
561
|
-
var result = await generate({
|
|
562
|
-
dir: testDir,
|
|
563
|
-
silent: true
|
|
564
|
-
});
|
|
565
|
-
var content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
|
|
566
|
-
expect(content).toContain("insert:");
|
|
567
|
-
expect(content).toMatch(/insert:.*v\.object/);
|
|
568
|
-
expect(content).toContain("update:");
|
|
569
|
-
expect(content).toContain("delete:");
|
|
570
|
-
});
|
|
571
|
-
test("treats models without export const mutate as empty mutations", async function () {
|
|
572
|
-
writeFileSync(join(testDir, "readonly/mutations.ts"), `
|
|
573
|
-
import { table, string } from 'on-zero'
|
|
574
|
-
|
|
575
|
-
export const schema = table('readonly').columns({
|
|
576
|
-
id: string(),
|
|
577
|
-
name: string(),
|
|
578
|
-
}).primaryKey('id')
|
|
579
|
-
`);
|
|
580
|
-
var result = await generate({
|
|
581
|
-
dir: testDir,
|
|
582
|
-
silent: true
|
|
583
|
-
});
|
|
584
|
-
expect(result.mutationCount).toBe(0);
|
|
585
|
-
var content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
|
|
586
|
-
expect(content).toContain("readonly: {");
|
|
587
|
-
});
|
|
588
|
-
test("extracts custom mutations from bare mutations({})", async function () {
|
|
589
|
-
writeFileSync(join(testDir, "admin/mutations.ts"), `
|
|
590
|
-
import { mutations } from 'on-zero'
|
|
591
|
-
|
|
592
|
-
export const mutate = mutations({
|
|
593
|
-
reset: async ({ tx }, { targetId }: { targetId: string }) => {
|
|
594
|
-
await tx.mutate.admin.delete({ id: targetId })
|
|
595
|
-
},
|
|
596
|
-
})
|
|
597
|
-
`);
|
|
598
|
-
var result = await generate({
|
|
599
|
-
dir: testDir,
|
|
600
|
-
silent: true
|
|
601
|
-
});
|
|
602
|
-
var content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
|
|
603
|
-
expect(content).toContain("reset");
|
|
604
|
-
expect(content).toContain("v.string()");
|
|
605
|
-
});
|
|
606
|
-
test("generates validators for string-model multiline mutation params", async function () {
|
|
607
|
-
writeFileSync(join(testDir, "agentEvent/mutations.ts"), `
|
|
608
|
-
import { mutations, serverWhere } from 'on-zero'
|
|
609
|
-
|
|
610
|
-
const perm = serverWhere('agentEvent', () => true)
|
|
611
|
-
|
|
612
|
-
export const mutate = mutations('agentEvent', perm, {
|
|
613
|
-
claimReviewLease: async (
|
|
614
|
-
{ tx },
|
|
615
|
-
props: {
|
|
616
|
-
id: string
|
|
617
|
-
projectId: string
|
|
618
|
-
agentId: string
|
|
619
|
-
userId: string
|
|
620
|
-
taskId: string
|
|
621
|
-
reviewKey: string
|
|
622
|
-
runnerId: string
|
|
623
|
-
createdAt: number
|
|
624
|
-
},
|
|
625
|
-
) => {
|
|
626
|
-
await tx.mutate.agentEvent.insert(props)
|
|
627
|
-
},
|
|
628
|
-
})
|
|
629
|
-
`);
|
|
630
|
-
await generate({
|
|
631
|
-
dir: testDir,
|
|
632
|
-
silent: true
|
|
633
|
-
});
|
|
634
|
-
var content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
|
|
635
|
-
expect(content).toContain("claimReviewLease");
|
|
636
|
-
expect(content).toContain("projectId: v.string()");
|
|
637
|
-
expect(content).toContain("createdAt: v.number()");
|
|
638
|
-
expect(content).not.toContain("claimReviewLease: v.object({})");
|
|
639
|
-
});
|
|
640
|
-
test("generates validators for object intersections", async function () {
|
|
641
|
-
writeFileSync(join(testDir, "agent/mutations.ts"), `
|
|
642
|
-
import { mutations, serverWhere } from 'on-zero'
|
|
643
|
-
|
|
644
|
-
const perm = serverWhere('agent', () => true)
|
|
645
|
-
|
|
646
|
-
export const mutate = mutations('agent', perm, {
|
|
647
|
-
update: async (
|
|
648
|
-
{ tx },
|
|
649
|
-
props: {
|
|
650
|
-
id: string
|
|
651
|
-
} & {
|
|
652
|
-
status?: string
|
|
653
|
-
currentTaskId?: string
|
|
654
|
-
[key: string]: unknown
|
|
655
|
-
},
|
|
656
|
-
) => {
|
|
657
|
-
await tx.mutate.agent.update(props)
|
|
658
|
-
},
|
|
659
|
-
})
|
|
660
|
-
`);
|
|
661
|
-
await generate({
|
|
662
|
-
dir: testDir,
|
|
663
|
-
silent: true
|
|
664
|
-
});
|
|
665
|
-
var content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
|
|
666
|
-
expect(content).toContain("update");
|
|
667
|
-
expect(content).toContain("id: v.string()");
|
|
668
|
-
expect(content).toContain("status: v.optional(v.string())");
|
|
669
|
-
expect(content).toContain("currentTaskId: v.optional(v.string())");
|
|
670
|
-
expect(content).not.toContain("update: v.object({})");
|
|
671
|
-
expect(content).not.toContain("[key");
|
|
672
|
-
});
|
|
673
|
-
test("uses v.unknown for untyped mutation payloads", async function () {
|
|
674
|
-
writeFileSync(join(testDir, "project/mutations.ts"), `
|
|
675
|
-
import { mutations, serverWhere } from 'on-zero'
|
|
676
|
-
|
|
677
|
-
const perm = serverWhere('project', () => true)
|
|
678
|
-
|
|
679
|
-
export const mutate = mutations('project', perm, {
|
|
680
|
-
insert: async ({ tx }, project) => {
|
|
681
|
-
await tx.mutate.project.insert(project)
|
|
682
|
-
},
|
|
683
|
-
})
|
|
684
|
-
`);
|
|
685
|
-
await generate({
|
|
686
|
-
dir: testDir,
|
|
687
|
-
silent: true
|
|
688
|
-
});
|
|
689
|
-
var content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
|
|
690
|
-
expect(content).toContain("insert: v.unknown()");
|
|
691
|
-
expect(content).not.toContain("insert: v.void_()");
|
|
692
|
-
});
|
|
693
|
-
test("handles mutations with only context param (void)", async function () {
|
|
694
|
-
writeFileSync(join(testDir, "user/mutations.ts"), `
|
|
695
|
-
import { table, string } from 'on-zero'
|
|
696
|
-
import { mutations, serverWhere } from 'on-zero'
|
|
697
|
-
|
|
698
|
-
export const schema = table('user').columns({
|
|
699
|
-
id: string(),
|
|
700
|
-
name: string(),
|
|
701
|
-
}).primaryKey('id')
|
|
702
|
-
|
|
703
|
-
const perm = serverWhere('user', () => true)
|
|
704
|
-
|
|
705
|
-
export const mutate = mutations(schema, perm, {
|
|
706
|
-
finishOnboarding: async ({ tx }) => {
|
|
707
|
-
// no second param
|
|
708
|
-
},
|
|
709
|
-
})
|
|
710
|
-
`);
|
|
711
|
-
var result = await generate({
|
|
712
|
-
dir: testDir,
|
|
713
|
-
silent: true
|
|
714
|
-
});
|
|
715
|
-
var content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
|
|
716
|
-
expect(content).toContain("finishOnboarding");
|
|
717
|
-
});
|
|
718
|
-
test("handles primitive param type", async function () {
|
|
719
|
-
writeFileSync(join(testDir, "user/mutations.ts"), `
|
|
720
|
-
import { table, string } from 'on-zero'
|
|
721
|
-
import { mutations, serverWhere } from 'on-zero'
|
|
722
|
-
|
|
723
|
-
export const schema = table('user').columns({
|
|
724
|
-
id: string(),
|
|
725
|
-
name: string(),
|
|
726
|
-
}).primaryKey('id')
|
|
727
|
-
|
|
728
|
-
const perm = serverWhere('user', () => true)
|
|
729
|
-
|
|
730
|
-
export const mutate = mutations(schema, perm, {
|
|
731
|
-
completeSignup: async ({ tx }, userId: string) => {
|
|
732
|
-
await tx.mutate.user.update({ id: userId })
|
|
733
|
-
},
|
|
734
|
-
})
|
|
735
|
-
`);
|
|
736
|
-
var result = await generate({
|
|
737
|
-
dir: testDir,
|
|
738
|
-
silent: true
|
|
739
|
-
});
|
|
740
|
-
var content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
|
|
741
|
-
expect(content).toContain("completeSignup");
|
|
742
|
-
expect(content).toContain("v.string()");
|
|
743
|
-
});
|
|
744
|
-
test("handles array param type", async function () {
|
|
745
|
-
writeFileSync(join(testDir, "batch/mutations.ts"), `
|
|
746
|
-
import { mutations } from 'on-zero'
|
|
747
|
-
|
|
748
|
-
export const mutate = mutations({
|
|
749
|
-
bulkDelete: async ({ tx }, ids: Array<{ id: string }>) => {
|
|
750
|
-
for (const { id } of ids) await tx.mutate.batch.delete({ id })
|
|
751
|
-
},
|
|
752
|
-
})
|
|
753
|
-
`);
|
|
754
|
-
var result = await generate({
|
|
755
|
-
dir: testDir,
|
|
756
|
-
silent: true
|
|
757
|
-
});
|
|
758
|
-
var content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
|
|
759
|
-
expect(content).toContain("bulkDelete");
|
|
760
|
-
expect(content).toContain("v.array");
|
|
761
|
-
});
|
|
762
334
|
test("populates mutationCount and caching works", async function () {
|
|
763
335
|
writeFileSync(join(testDir, "item/mutations.ts"), `
|
|
764
336
|
import { table, string } from 'on-zero'
|
|
@@ -790,242 +362,4 @@ export const mutate = mutations(schema, perm, {
|
|
|
790
362
|
expect(second.mutationCount).toBe(first.mutationCount);
|
|
791
363
|
});
|
|
792
364
|
});
|
|
793
|
-
describe("type resolution", function () {
|
|
794
|
-
test("resolves imported type references for mutations", async function () {
|
|
795
|
-
writeFileSync(join(testDir, "post/types.ts"), `
|
|
796
|
-
export type ArchiveParams = {
|
|
797
|
-
id: string
|
|
798
|
-
reason: string
|
|
799
|
-
archived: boolean
|
|
800
|
-
}
|
|
801
|
-
`);
|
|
802
|
-
writeFileSync(join(testDir, "post/mutations.ts"), `
|
|
803
|
-
import { table, string, boolean } from 'on-zero'
|
|
804
|
-
import { mutations } from 'on-zero'
|
|
805
|
-
import type { ArchiveParams } from './types'
|
|
806
|
-
|
|
807
|
-
export const mutate = mutations({
|
|
808
|
-
archive: async ({ tx }, params: ArchiveParams) => {
|
|
809
|
-
await tx.mutate.post.update(params)
|
|
810
|
-
},
|
|
811
|
-
})
|
|
812
|
-
`);
|
|
813
|
-
var result = await generate({
|
|
814
|
-
dir: testDir,
|
|
815
|
-
silent: true
|
|
816
|
-
});
|
|
817
|
-
var content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
|
|
818
|
-
expect(content).toContain("v.string()");
|
|
819
|
-
expect(content).toContain("v.boolean()");
|
|
820
|
-
expect(content).toContain("reason");
|
|
821
|
-
expect(content).toContain("archived");
|
|
822
|
-
});
|
|
823
|
-
test("resolves utility types like Pick", async function () {
|
|
824
|
-
writeFileSync(join(testDir, "item/types.ts"), `
|
|
825
|
-
export type Item = {
|
|
826
|
-
id: string
|
|
827
|
-
name: string
|
|
828
|
-
description: string
|
|
829
|
-
count: number
|
|
830
|
-
}
|
|
831
|
-
`);
|
|
832
|
-
writeFileSync(join(testDir, "item/mutations.ts"), `
|
|
833
|
-
import { table, string, number } from 'on-zero'
|
|
834
|
-
import { mutations, serverWhere } from 'on-zero'
|
|
835
|
-
import type { Item } from './types'
|
|
836
|
-
|
|
837
|
-
export const schema = table('item').columns({
|
|
838
|
-
id: string(),
|
|
839
|
-
name: string(),
|
|
840
|
-
description: string(),
|
|
841
|
-
count: number(),
|
|
842
|
-
}).primaryKey('id')
|
|
843
|
-
|
|
844
|
-
const perm = serverWhere('item', () => true)
|
|
845
|
-
|
|
846
|
-
export const mutate = mutations(schema, perm, {
|
|
847
|
-
rename: async ({ tx }, updates: Pick<Item, 'id' | 'name'>) => {
|
|
848
|
-
await tx.mutate.item.update(updates)
|
|
849
|
-
},
|
|
850
|
-
})
|
|
851
|
-
`);
|
|
852
|
-
var result = await generate({
|
|
853
|
-
dir: testDir,
|
|
854
|
-
silent: true
|
|
855
|
-
});
|
|
856
|
-
var content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
|
|
857
|
-
expect(content).toContain("id");
|
|
858
|
-
expect(content).toContain("name");
|
|
859
|
-
expect(content).not.toMatch(/rename:[\s\S]*description/);
|
|
860
|
-
expect(content).not.toMatch(/rename:[\s\S]*count/);
|
|
861
|
-
});
|
|
862
|
-
test("resolves instantiated Zero row fields through Partial and Omit", async function () {
|
|
863
|
-
symlinkSync(join(import.meta.dirname, "../node_modules"), join(testDir, "node_modules"), "dir");
|
|
864
|
-
writeFileSync(join(testDir, "item/types.ts"), `
|
|
865
|
-
import type { Row } from '@rocicorp/zero'
|
|
866
|
-
import { drizzleZeroConfig } from 'drizzle-zero-sqlite'
|
|
867
|
-
import { sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
|
868
|
-
|
|
869
|
-
const item = sqliteTable('item', {
|
|
870
|
-
id: text().primaryKey(),
|
|
871
|
-
name: text().notNull(),
|
|
872
|
-
description: text().notNull(),
|
|
873
|
-
})
|
|
874
|
-
const schema = drizzleZeroConfig({ item })
|
|
875
|
-
|
|
876
|
-
export type Item = Row<(typeof schema)['tables']['item']>
|
|
877
|
-
`);
|
|
878
|
-
writeFileSync(join(testDir, "item/mutations.ts"), `
|
|
879
|
-
import { mutations } from 'on-zero'
|
|
880
|
-
import type { Item } from './types'
|
|
881
|
-
|
|
882
|
-
type ItemUpdate = { id: string } & Partial<Omit<Item, 'id'>>
|
|
883
|
-
|
|
884
|
-
export const mutate = mutations('item', {
|
|
885
|
-
update: async ({ tx }, props: ItemUpdate) => {
|
|
886
|
-
await tx.mutate.item.update(props)
|
|
887
|
-
},
|
|
888
|
-
})
|
|
889
|
-
`);
|
|
890
|
-
await generate({
|
|
891
|
-
dir: testDir,
|
|
892
|
-
silent: true
|
|
893
|
-
});
|
|
894
|
-
var content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
|
|
895
|
-
expect(content).toContain("name: v.optional(v.string())");
|
|
896
|
-
expect(content).toContain("description: v.optional(v.string())");
|
|
897
|
-
expect(content).not.toContain("name: v.optional(v.unknown())");
|
|
898
|
-
expect(content).not.toContain("description: v.optional(v.unknown())");
|
|
899
|
-
});
|
|
900
|
-
test("skips symbol-keyed properties when resolving imported mutation param types", async function () {
|
|
901
|
-
writeFileSync(join(testDir, "item/types.ts"), `
|
|
902
|
-
export type WeirdParams = {
|
|
903
|
-
id: string
|
|
904
|
-
[Symbol.iterator]?: () => Iterator<string>
|
|
905
|
-
}
|
|
906
|
-
`);
|
|
907
|
-
writeFileSync(join(testDir, "item/mutations.ts"), `
|
|
908
|
-
import { mutations } from 'on-zero'
|
|
909
|
-
import type { WeirdParams } from './types'
|
|
910
|
-
|
|
911
|
-
export const mutate = mutations({
|
|
912
|
-
run: async ({ tx }, params: WeirdParams) => {
|
|
913
|
-
await tx.mutate.item.delete({ id: params.id })
|
|
914
|
-
},
|
|
915
|
-
})
|
|
916
|
-
`);
|
|
917
|
-
await generate({
|
|
918
|
-
dir: testDir,
|
|
919
|
-
silent: true
|
|
920
|
-
});
|
|
921
|
-
var content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
|
|
922
|
-
expect(content).toContain("run");
|
|
923
|
-
expect(content).toContain("id: v.string()");
|
|
924
|
-
expect(content).not.toContain("__@iterator");
|
|
925
|
-
});
|
|
926
|
-
test("resolves imported types in query params", async function () {
|
|
927
|
-
writeFileSync(join(testDir, "post/mutations.ts"), `export const schema = table('post', { id: string() })`);
|
|
928
|
-
writeFileSync(join(testDir, "post/types.ts"), `
|
|
929
|
-
export type PostFilter = {
|
|
930
|
-
authorId: string
|
|
931
|
-
published: boolean
|
|
932
|
-
}
|
|
933
|
-
`);
|
|
934
|
-
writeFileSync(join(testDir, "post/queries.ts"), `
|
|
935
|
-
import type { PostFilter } from './types'
|
|
936
|
-
|
|
937
|
-
export const filteredPosts = (filter: PostFilter) => zero.query.post
|
|
938
|
-
`);
|
|
939
|
-
var result = await generate({
|
|
940
|
-
dir: testDir,
|
|
941
|
-
silent: true
|
|
942
|
-
});
|
|
943
|
-
var content = readFileSync(join(testDir, "generated/syncedQueries.ts"), "utf-8");
|
|
944
|
-
expect(content).toContain("filteredPosts");
|
|
945
|
-
expect(content).toContain("v.object");
|
|
946
|
-
expect(content).toContain("authorId");
|
|
947
|
-
expect(content).toContain("v.boolean()");
|
|
948
|
-
});
|
|
949
|
-
});
|
|
950
|
-
describe("generateDrizzleSchemaFile", function () {
|
|
951
|
-
test("preserves table server names and executable two-hop relationships", function () {
|
|
952
|
-
var schema = {
|
|
953
|
-
tables: {
|
|
954
|
-
users: {
|
|
955
|
-
name: "users",
|
|
956
|
-
serverName: "user_records",
|
|
957
|
-
primaryKey: ["id"],
|
|
958
|
-
columns: {
|
|
959
|
-
id: {
|
|
960
|
-
type: "string",
|
|
961
|
-
optional: false,
|
|
962
|
-
customType: null
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
},
|
|
966
|
-
groups: {
|
|
967
|
-
name: "groups",
|
|
968
|
-
primaryKey: ["id"],
|
|
969
|
-
columns: {
|
|
970
|
-
id: {
|
|
971
|
-
type: "string",
|
|
972
|
-
optional: false,
|
|
973
|
-
customType: null
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
},
|
|
977
|
-
memberships: {
|
|
978
|
-
name: "memberships",
|
|
979
|
-
primaryKey: ["userId", "groupId"],
|
|
980
|
-
columns: {
|
|
981
|
-
userId: {
|
|
982
|
-
type: "string",
|
|
983
|
-
optional: false,
|
|
984
|
-
customType: null
|
|
985
|
-
},
|
|
986
|
-
groupId: {
|
|
987
|
-
type: "string",
|
|
988
|
-
optional: false,
|
|
989
|
-
customType: null
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
},
|
|
994
|
-
relationships: {
|
|
995
|
-
users: {
|
|
996
|
-
groups: [{
|
|
997
|
-
sourceField: ["id"],
|
|
998
|
-
destField: ["userId"],
|
|
999
|
-
destSchema: "memberships",
|
|
1000
|
-
cardinality: "many"
|
|
1001
|
-
}, {
|
|
1002
|
-
sourceField: ["groupId"],
|
|
1003
|
-
destField: ["id"],
|
|
1004
|
-
destSchema: "groups",
|
|
1005
|
-
cardinality: "many"
|
|
1006
|
-
}]
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
};
|
|
1010
|
-
var source = generateDrizzleSchemaFile(schema);
|
|
1011
|
-
var executableSource = source.replace("import { boolean, createSchema, json, number, relationships, string, table } from '@rocicorp/zero'", "").replace("export const schema =", "globalThis.generatedSchema =");
|
|
1012
|
-
var context = {
|
|
1013
|
-
...zero
|
|
1014
|
-
};
|
|
1015
|
-
runInNewContext(executableSource, context);
|
|
1016
|
-
expect(source).toContain('const usersTable = table("users").from("user_records")');
|
|
1017
|
-
expect(context.generatedSchema).toMatchObject({
|
|
1018
|
-
tables: {
|
|
1019
|
-
users: {
|
|
1020
|
-
serverName: "user_records"
|
|
1021
|
-
}
|
|
1022
|
-
},
|
|
1023
|
-
relationships: {
|
|
1024
|
-
users: {
|
|
1025
|
-
groups: schema.relationships.users.groups
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
});
|
|
1029
|
-
});
|
|
1030
|
-
});
|
|
1031
365
|
//# sourceMappingURL=generate.test.native.js.map
|