on-zero 0.9.7 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/cjs/generate-helpers.test.cjs +0 -38
  2. package/dist/cjs/generate-helpers.test.native.js +0 -38
  3. package/dist/cjs/generate-helpers.test.native.js.map +1 -1
  4. package/dist/cjs/generate-lite.test.cjs +0 -522
  5. package/dist/cjs/generate-lite.test.native.js +0 -525
  6. package/dist/cjs/generate-lite.test.native.js.map +1 -1
  7. package/dist/cjs/generate.test.cjs +0 -691
  8. package/dist/cjs/generate.test.native.js +0 -691
  9. package/dist/cjs/generate.test.native.js.map +1 -1
  10. package/dist/cjs/mutations.test.cjs +0 -11
  11. package/dist/cjs/mutations.test.native.js +0 -18
  12. package/dist/cjs/mutations.test.native.js.map +1 -1
  13. package/dist/cjs/server.test.cjs +0 -93
  14. package/dist/cjs/server.test.native.js +0 -101
  15. package/dist/cjs/server.test.native.js.map +1 -1
  16. package/dist/cjs/serverWhere.test.cjs +0 -45
  17. package/dist/cjs/serverWhere.test.native.js +0 -51
  18. package/dist/cjs/serverWhere.test.native.js.map +1 -1
  19. package/dist/esm/generate-helpers.test.mjs +1 -39
  20. package/dist/esm/generate-helpers.test.mjs.map +1 -1
  21. package/dist/esm/generate-helpers.test.native.js +1 -39
  22. package/dist/esm/generate-helpers.test.native.js.map +1 -1
  23. package/dist/esm/generate-lite.test.mjs +0 -522
  24. package/dist/esm/generate-lite.test.mjs.map +1 -1
  25. package/dist/esm/generate-lite.test.native.js +0 -525
  26. package/dist/esm/generate-lite.test.native.js.map +1 -1
  27. package/dist/esm/generate.test.mjs +2 -668
  28. package/dist/esm/generate.test.mjs.map +1 -1
  29. package/dist/esm/generate.test.native.js +2 -668
  30. package/dist/esm/generate.test.native.js.map +1 -1
  31. package/dist/esm/mutations.test.mjs +0 -11
  32. package/dist/esm/mutations.test.mjs.map +1 -1
  33. package/dist/esm/mutations.test.native.js +0 -18
  34. package/dist/esm/mutations.test.native.js.map +1 -1
  35. package/dist/esm/server.test.mjs +0 -93
  36. package/dist/esm/server.test.mjs.map +1 -1
  37. package/dist/esm/server.test.native.js +0 -101
  38. package/dist/esm/server.test.native.js.map +1 -1
  39. package/dist/esm/serverWhere.test.mjs +0 -45
  40. package/dist/esm/serverWhere.test.mjs.map +1 -1
  41. package/dist/esm/serverWhere.test.native.js +0 -51
  42. package/dist/esm/serverWhere.test.native.js.map +1 -1
  43. package/package.json +1 -1
  44. package/src/generate-helpers.test.ts +0 -40
  45. package/src/generate-lite.test.ts +0 -586
  46. package/src/generate.test.ts +0 -844
  47. package/src/mutations.test.ts +0 -21
  48. package/src/server.test.ts +0 -75
  49. package/src/serverWhere.test.ts +0 -42
@@ -1,10 +1,9 @@
1
- import { existsSync, mkdirSync, readFileSync, rmSync, symlinkSync, writeFileSync as writeFile } from "node:fs";
1
+ import { existsSync, mkdirSync, rmSync, writeFileSync as writeFile } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { dirname, join } from "node:path";
4
4
  import { runInNewContext } from "node:vm";
5
- import * as zero from "@rocicorp/zero";
6
5
  import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
7
- import { deriveDataMembership, generate, generateDrizzleSchemaFile, generateDrizzleSchemaInputFile } from "./generate.mjs";
6
+ import { deriveDataMembership, generate, generateDrizzleSchemaInputFile } from "./generate.mjs";
8
7
  const testDir = join(tmpdir(), "on-zero-test-" + Date.now());
9
8
  function writeFileSync(path, content) {
10
9
  mkdirSync(dirname(path), {
@@ -25,105 +24,6 @@ afterEach(() => {
25
24
  });
26
25
  });
27
26
  describe("generate", () => {
28
- test("generates models.ts, types.ts, tables.ts from model files", async () => {
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
- const 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
- const 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
- const 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
- const 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 () => {
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
- const 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
- const 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
- const 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 () => {
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
- const result = await generate({
106
- dir: testDir,
107
- silent: true
108
- });
109
- expect(result.queryCount).toBe(1);
110
- const 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 () => {
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
- const 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
- const 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 () => {
128
28
  writeFileSync(join(testDir, "post/mutations.ts"), `export const schema = table('post', { id: string() })`);
129
29
  const markerFile = join(testDir, "after-ran");
@@ -167,51 +67,6 @@ export const allPosts = () => zero.query.post
167
67
  });
168
68
  describe("instance layout", () => {
169
69
  const dataDir = () => join(testDir, "src/data");
170
- test("discovers file and folder namespaces and emits related sync closure", async () => {
171
- writeFileSync(join(dataDir(), "control/reaction.ts"), `export const allReactions = () => zql.reaction`);
172
- writeFileSync(join(dataDir(), "on-zero.config.ts"), `export default defineConfig({ instances: { control: {}, project: { scope: 'projectId' } } })`);
173
- writeFileSync(join(dataDir(), "project/message/queries.ts"), `
174
- const unused = () => zql.message.related('notARealRelation')
175
- const withComments = () => zql.message.related('comments', (q) => q.related('author'))
176
- export const messages = () => withComments()
177
- `);
178
- writeFileSync(join(dataDir(), "project/message/mutations.ts"), `export const schema = table('message').columns({ id: string(), projectId: string() })`);
179
- writeFileSync(join(dataDir(), "project/message/helpers.ts"), `export const privateHelper = () => 'ignored'`);
180
- writeFileSync(join(testDir, "src/database/relations.ts"), `
181
- export const relations = defineRelations(schema, (r) => ({
182
- message: { comments: r.many.comment({}) },
183
- comment: { author: r.one.userPublic({}) },
184
- }))
185
- `);
186
- writeFileSync(join(testDir, "src/database/schema.ts"), `
187
- export const comment = sqliteTable('comment', { id: text(), projectId: text() })
188
- export const userPublic = sqliteTable('user_public', { id: text(), projectId: text() })
189
- `);
190
- const membership = await deriveDataMembership({
191
- dir: dataDir()
192
- });
193
- expect(membership.allTables).toEqual(["comment", "message", "reaction", "userPublic"]);
194
- expect(membership.instances.project).toEqual({
195
- tables: ["message"],
196
- syncTables: ["comment", "message", "userPublic"],
197
- supportTables: [],
198
- scope: "projectId"
199
- });
200
- await generate({
201
- dir: dataDir(),
202
- silent: true
203
- });
204
- const grouped = readFileSync(join(dataDir(), "generated/groupedQueries.ts"), "utf8");
205
- expect(grouped).toContain("from '../control/reaction'");
206
- expect(grouped).toContain("from '../project/message/queries'");
207
- expect(grouped).not.toContain("privateHelper");
208
- const manifest = readFileSync(join(dataDir(), "generated/instances.ts"), "utf8");
209
- expect(manifest).toContain("control: {");
210
- expect(manifest).toContain("project: {");
211
- expect(manifest).toContain('tables: ["message"]');
212
- expect(manifest).toContain('syncTables: ["comment","message","userPublic"]');
213
- expect(manifest).toContain(`defaultVisibility: (value: string) => ({ column: "projectId", value })`);
214
- });
215
70
  test("derives query membership from the returned query root", async () => {
216
71
  writeFileSync(join(dataDir(), "category/mutations.ts"), `export const schema = table('category').columns({ id: string() })`);
217
72
  writeFileSync(join(dataDir(), "dashboard/queries.ts"), `export const monthSummary = () => zql.category.related('expenses')`);
@@ -420,42 +275,6 @@ export const messages = () => zql.message.related('author')
420
275
  silent: true
421
276
  })).rejects.toThrow(/namespace 'message'.*instances 'control' and 'project'/);
422
277
  });
423
- test("resolves configured instance directories relative to the config file", async () => {
424
- writeFileSync(join(dataDir(), "on-zero.config.ts"), `export default defineConfig({ instances: { control: { dir: './planes/control-data', supportTables: ['audit'] }, project: { dir: '../project-data', scope: 'projectId' } } })`);
425
- writeFileSync(join(dataDir(), "planes/control-data/account.ts"), `export const accounts = () => zql.account`);
426
- writeFileSync(join(testDir, "src/project-data/message.ts"), `export const schema = table('message').columns({ id: string(), projectId: string() })`);
427
- await expect(deriveDataMembership({
428
- dir: dataDir(),
429
- config: join(dataDir(), "on-zero.config.ts")
430
- })).resolves.toEqual({
431
- instances: {
432
- control: {
433
- tables: ["account"],
434
- syncTables: ["account"],
435
- supportTables: ["audit"],
436
- scope: null
437
- },
438
- project: {
439
- tables: ["message"],
440
- syncTables: ["message"],
441
- supportTables: [],
442
- scope: "projectId"
443
- }
444
- },
445
- allTables: ["account", "audit", "message"]
446
- });
447
- await expect(generate({
448
- dir: dataDir(),
449
- config: join(dataDir(), "on-zero.config.ts"),
450
- silent: true
451
- })).resolves.toMatchObject({
452
- modelCount: 1,
453
- schemaCount: 1
454
- });
455
- const models = readFileSync(join(dataDir(), "generated/models.ts"), "utf8");
456
- expect(models).toContain("from '../../project-data/message'");
457
- expect(models).not.toContain("account");
458
- });
459
278
  test("rejects missing configured instance directories", async () => {
460
279
  writeFileSync(join(dataDir(), "on-zero.config.ts"), `export default defineConfig({ instances: { project: { dir: './missing' } } })`);
461
280
  await expect(deriveDataMembership({
@@ -500,253 +319,6 @@ export const messages = () => zql.message.related('author')
500
319
  });
501
320
  });
502
321
  describe("mutations", () => {
503
- test("generates validators for inline mutation param types", async () => {
504
- writeFileSync(join(testDir, "post/mutations.ts"), `
505
- import { table, string } from 'on-zero'
506
- import { mutations, serverWhere } from 'on-zero'
507
-
508
- export const schema = table('post').columns({
509
- id: string(),
510
- title: string(),
511
- }).primaryKey('id')
512
-
513
- const perm = serverWhere('post', () => true)
514
-
515
- export const mutate = mutations(schema, perm, {
516
- archive: async ({ tx }, { id, reason }: { id: string; reason: string }) => {
517
- await tx.mutate.post.update({ id, archived: true })
518
- },
519
- })
520
- `);
521
- const result = await generate({
522
- dir: testDir,
523
- silent: true
524
- });
525
- expect(result.mutationCount).toBeGreaterThan(0);
526
- expect(existsSync(join(testDir, "generated/syncedMutations.ts"))).toBe(true);
527
- const content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
528
- expect(content).toContain("archive");
529
- expect(content).toContain("v.object");
530
- expect(content).toContain("v.string()");
531
- });
532
- test("generates CRUD validators from schema columns", async () => {
533
- writeFileSync(join(testDir, "task/mutations.ts"), `
534
- import { table, string, number, boolean } from 'on-zero'
535
- import { mutations, serverWhere } from 'on-zero'
536
-
537
- export const schema = table('task').columns({
538
- id: string(),
539
- title: string(),
540
- priority: number(),
541
- done: boolean(),
542
- note: string().optional(),
543
- }).primaryKey('id')
544
-
545
- const perm = serverWhere('task', () => true)
546
-
547
- export const mutate = mutations(schema, perm)
548
- `);
549
- const result = await generate({
550
- dir: testDir,
551
- silent: true
552
- });
553
- const content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
554
- expect(content).toContain("insert:");
555
- expect(content).toMatch(/insert:.*v\.object/);
556
- expect(content).toContain("update:");
557
- expect(content).toContain("delete:");
558
- });
559
- test("treats models without export const mutate as empty mutations", async () => {
560
- writeFileSync(join(testDir, "readonly/mutations.ts"), `
561
- import { table, string } from 'on-zero'
562
-
563
- export const schema = table('readonly').columns({
564
- id: string(),
565
- name: string(),
566
- }).primaryKey('id')
567
- `);
568
- const result = await generate({
569
- dir: testDir,
570
- silent: true
571
- });
572
- expect(result.mutationCount).toBe(0);
573
- const content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
574
- expect(content).toContain("readonly: {");
575
- });
576
- test("extracts custom mutations from bare mutations({})", async () => {
577
- writeFileSync(join(testDir, "admin/mutations.ts"), `
578
- import { mutations } from 'on-zero'
579
-
580
- export const mutate = mutations({
581
- reset: async ({ tx }, { targetId }: { targetId: string }) => {
582
- await tx.mutate.admin.delete({ id: targetId })
583
- },
584
- })
585
- `);
586
- const result = await generate({
587
- dir: testDir,
588
- silent: true
589
- });
590
- const content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
591
- expect(content).toContain("reset");
592
- expect(content).toContain("v.string()");
593
- });
594
- test("generates validators for string-model multiline mutation params", async () => {
595
- writeFileSync(join(testDir, "agentEvent/mutations.ts"), `
596
- import { mutations, serverWhere } from 'on-zero'
597
-
598
- const perm = serverWhere('agentEvent', () => true)
599
-
600
- export const mutate = mutations('agentEvent', perm, {
601
- claimReviewLease: async (
602
- { tx },
603
- props: {
604
- id: string
605
- projectId: string
606
- agentId: string
607
- userId: string
608
- taskId: string
609
- reviewKey: string
610
- runnerId: string
611
- createdAt: number
612
- },
613
- ) => {
614
- await tx.mutate.agentEvent.insert(props)
615
- },
616
- })
617
- `);
618
- await generate({
619
- dir: testDir,
620
- silent: true
621
- });
622
- const content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
623
- expect(content).toContain("claimReviewLease");
624
- expect(content).toContain("projectId: v.string()");
625
- expect(content).toContain("createdAt: v.number()");
626
- expect(content).not.toContain("claimReviewLease: v.object({})");
627
- });
628
- test("generates validators for object intersections", async () => {
629
- writeFileSync(join(testDir, "agent/mutations.ts"), `
630
- import { mutations, serverWhere } from 'on-zero'
631
-
632
- const perm = serverWhere('agent', () => true)
633
-
634
- export const mutate = mutations('agent', perm, {
635
- update: async (
636
- { tx },
637
- props: {
638
- id: string
639
- } & {
640
- status?: string
641
- currentTaskId?: string
642
- [key: string]: unknown
643
- },
644
- ) => {
645
- await tx.mutate.agent.update(props)
646
- },
647
- })
648
- `);
649
- await generate({
650
- dir: testDir,
651
- silent: true
652
- });
653
- const content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
654
- expect(content).toContain("update");
655
- expect(content).toContain("id: v.string()");
656
- expect(content).toContain("status: v.optional(v.string())");
657
- expect(content).toContain("currentTaskId: v.optional(v.string())");
658
- expect(content).not.toContain("update: v.object({})");
659
- expect(content).not.toContain("[key");
660
- });
661
- test("uses v.unknown for untyped mutation payloads", async () => {
662
- writeFileSync(join(testDir, "project/mutations.ts"), `
663
- import { mutations, serverWhere } from 'on-zero'
664
-
665
- const perm = serverWhere('project', () => true)
666
-
667
- export const mutate = mutations('project', perm, {
668
- insert: async ({ tx }, project) => {
669
- await tx.mutate.project.insert(project)
670
- },
671
- })
672
- `);
673
- await generate({
674
- dir: testDir,
675
- silent: true
676
- });
677
- const content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
678
- expect(content).toContain("insert: v.unknown()");
679
- expect(content).not.toContain("insert: v.void_()");
680
- });
681
- test("handles mutations with only context param (void)", async () => {
682
- writeFileSync(join(testDir, "user/mutations.ts"), `
683
- import { table, string } from 'on-zero'
684
- import { mutations, serverWhere } from 'on-zero'
685
-
686
- export const schema = table('user').columns({
687
- id: string(),
688
- name: string(),
689
- }).primaryKey('id')
690
-
691
- const perm = serverWhere('user', () => true)
692
-
693
- export const mutate = mutations(schema, perm, {
694
- finishOnboarding: async ({ tx }) => {
695
- // no second param
696
- },
697
- })
698
- `);
699
- const result = await generate({
700
- dir: testDir,
701
- silent: true
702
- });
703
- const content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
704
- expect(content).toContain("finishOnboarding");
705
- });
706
- test("handles primitive param type", async () => {
707
- writeFileSync(join(testDir, "user/mutations.ts"), `
708
- import { table, string } from 'on-zero'
709
- import { mutations, serverWhere } from 'on-zero'
710
-
711
- export const schema = table('user').columns({
712
- id: string(),
713
- name: string(),
714
- }).primaryKey('id')
715
-
716
- const perm = serverWhere('user', () => true)
717
-
718
- export const mutate = mutations(schema, perm, {
719
- completeSignup: async ({ tx }, userId: string) => {
720
- await tx.mutate.user.update({ id: userId })
721
- },
722
- })
723
- `);
724
- const result = await generate({
725
- dir: testDir,
726
- silent: true
727
- });
728
- const content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
729
- expect(content).toContain("completeSignup");
730
- expect(content).toContain("v.string()");
731
- });
732
- test("handles array param type", async () => {
733
- writeFileSync(join(testDir, "batch/mutations.ts"), `
734
- import { mutations } from 'on-zero'
735
-
736
- export const mutate = mutations({
737
- bulkDelete: async ({ tx }, ids: Array<{ id: string }>) => {
738
- for (const { id } of ids) await tx.mutate.batch.delete({ id })
739
- },
740
- })
741
- `);
742
- const result = await generate({
743
- dir: testDir,
744
- silent: true
745
- });
746
- const content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
747
- expect(content).toContain("bulkDelete");
748
- expect(content).toContain("v.array");
749
- });
750
322
  test("populates mutationCount and caching works", async () => {
751
323
  writeFileSync(join(testDir, "item/mutations.ts"), `
752
324
  import { table, string } from 'on-zero'
@@ -778,242 +350,4 @@ export const mutate = mutations(schema, perm, {
778
350
  expect(second.mutationCount).toBe(first.mutationCount);
779
351
  });
780
352
  });
781
- describe("type resolution", () => {
782
- test("resolves imported type references for mutations", async () => {
783
- writeFileSync(join(testDir, "post/types.ts"), `
784
- export type ArchiveParams = {
785
- id: string
786
- reason: string
787
- archived: boolean
788
- }
789
- `);
790
- writeFileSync(join(testDir, "post/mutations.ts"), `
791
- import { table, string, boolean } from 'on-zero'
792
- import { mutations } from 'on-zero'
793
- import type { ArchiveParams } from './types'
794
-
795
- export const mutate = mutations({
796
- archive: async ({ tx }, params: ArchiveParams) => {
797
- await tx.mutate.post.update(params)
798
- },
799
- })
800
- `);
801
- const result = await generate({
802
- dir: testDir,
803
- silent: true
804
- });
805
- const content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
806
- expect(content).toContain("v.string()");
807
- expect(content).toContain("v.boolean()");
808
- expect(content).toContain("reason");
809
- expect(content).toContain("archived");
810
- });
811
- test("resolves utility types like Pick", async () => {
812
- writeFileSync(join(testDir, "item/types.ts"), `
813
- export type Item = {
814
- id: string
815
- name: string
816
- description: string
817
- count: number
818
- }
819
- `);
820
- writeFileSync(join(testDir, "item/mutations.ts"), `
821
- import { table, string, number } from 'on-zero'
822
- import { mutations, serverWhere } from 'on-zero'
823
- import type { Item } from './types'
824
-
825
- export const schema = table('item').columns({
826
- id: string(),
827
- name: string(),
828
- description: string(),
829
- count: number(),
830
- }).primaryKey('id')
831
-
832
- const perm = serverWhere('item', () => true)
833
-
834
- export const mutate = mutations(schema, perm, {
835
- rename: async ({ tx }, updates: Pick<Item, 'id' | 'name'>) => {
836
- await tx.mutate.item.update(updates)
837
- },
838
- })
839
- `);
840
- const result = await generate({
841
- dir: testDir,
842
- silent: true
843
- });
844
- const content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
845
- expect(content).toContain("id");
846
- expect(content).toContain("name");
847
- expect(content).not.toMatch(/rename:[\s\S]*description/);
848
- expect(content).not.toMatch(/rename:[\s\S]*count/);
849
- });
850
- test("resolves instantiated Zero row fields through Partial and Omit", async () => {
851
- symlinkSync(join(import.meta.dirname, "../node_modules"), join(testDir, "node_modules"), "dir");
852
- writeFileSync(join(testDir, "item/types.ts"), `
853
- import type { Row } from '@rocicorp/zero'
854
- import { drizzleZeroConfig } from 'drizzle-zero-sqlite'
855
- import { sqliteTable, text } from 'drizzle-orm/sqlite-core'
856
-
857
- const item = sqliteTable('item', {
858
- id: text().primaryKey(),
859
- name: text().notNull(),
860
- description: text().notNull(),
861
- })
862
- const schema = drizzleZeroConfig({ item })
863
-
864
- export type Item = Row<(typeof schema)['tables']['item']>
865
- `);
866
- writeFileSync(join(testDir, "item/mutations.ts"), `
867
- import { mutations } from 'on-zero'
868
- import type { Item } from './types'
869
-
870
- type ItemUpdate = { id: string } & Partial<Omit<Item, 'id'>>
871
-
872
- export const mutate = mutations('item', {
873
- update: async ({ tx }, props: ItemUpdate) => {
874
- await tx.mutate.item.update(props)
875
- },
876
- })
877
- `);
878
- await generate({
879
- dir: testDir,
880
- silent: true
881
- });
882
- const content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
883
- expect(content).toContain("name: v.optional(v.string())");
884
- expect(content).toContain("description: v.optional(v.string())");
885
- expect(content).not.toContain("name: v.optional(v.unknown())");
886
- expect(content).not.toContain("description: v.optional(v.unknown())");
887
- });
888
- test("skips symbol-keyed properties when resolving imported mutation param types", async () => {
889
- writeFileSync(join(testDir, "item/types.ts"), `
890
- export type WeirdParams = {
891
- id: string
892
- [Symbol.iterator]?: () => Iterator<string>
893
- }
894
- `);
895
- writeFileSync(join(testDir, "item/mutations.ts"), `
896
- import { mutations } from 'on-zero'
897
- import type { WeirdParams } from './types'
898
-
899
- export const mutate = mutations({
900
- run: async ({ tx }, params: WeirdParams) => {
901
- await tx.mutate.item.delete({ id: params.id })
902
- },
903
- })
904
- `);
905
- await generate({
906
- dir: testDir,
907
- silent: true
908
- });
909
- const content = readFileSync(join(testDir, "generated/syncedMutations.ts"), "utf-8");
910
- expect(content).toContain("run");
911
- expect(content).toContain("id: v.string()");
912
- expect(content).not.toContain("__@iterator");
913
- });
914
- test("resolves imported types in query params", async () => {
915
- writeFileSync(join(testDir, "post/mutations.ts"), `export const schema = table('post', { id: string() })`);
916
- writeFileSync(join(testDir, "post/types.ts"), `
917
- export type PostFilter = {
918
- authorId: string
919
- published: boolean
920
- }
921
- `);
922
- writeFileSync(join(testDir, "post/queries.ts"), `
923
- import type { PostFilter } from './types'
924
-
925
- export const filteredPosts = (filter: PostFilter) => zero.query.post
926
- `);
927
- const result = await generate({
928
- dir: testDir,
929
- silent: true
930
- });
931
- const content = readFileSync(join(testDir, "generated/syncedQueries.ts"), "utf-8");
932
- expect(content).toContain("filteredPosts");
933
- expect(content).toContain("v.object");
934
- expect(content).toContain("authorId");
935
- expect(content).toContain("v.boolean()");
936
- });
937
- });
938
- describe("generateDrizzleSchemaFile", () => {
939
- test("preserves table server names and executable two-hop relationships", () => {
940
- const schema = {
941
- tables: {
942
- users: {
943
- name: "users",
944
- serverName: "user_records",
945
- primaryKey: ["id"],
946
- columns: {
947
- id: {
948
- type: "string",
949
- optional: false,
950
- customType: null
951
- }
952
- }
953
- },
954
- groups: {
955
- name: "groups",
956
- primaryKey: ["id"],
957
- columns: {
958
- id: {
959
- type: "string",
960
- optional: false,
961
- customType: null
962
- }
963
- }
964
- },
965
- memberships: {
966
- name: "memberships",
967
- primaryKey: ["userId", "groupId"],
968
- columns: {
969
- userId: {
970
- type: "string",
971
- optional: false,
972
- customType: null
973
- },
974
- groupId: {
975
- type: "string",
976
- optional: false,
977
- customType: null
978
- }
979
- }
980
- }
981
- },
982
- relationships: {
983
- users: {
984
- groups: [{
985
- sourceField: ["id"],
986
- destField: ["userId"],
987
- destSchema: "memberships",
988
- cardinality: "many"
989
- }, {
990
- sourceField: ["groupId"],
991
- destField: ["id"],
992
- destSchema: "groups",
993
- cardinality: "many"
994
- }]
995
- }
996
- }
997
- };
998
- const source = generateDrizzleSchemaFile(schema);
999
- const executableSource = source.replace("import { boolean, createSchema, json, number, relationships, string, table } from '@rocicorp/zero'", "").replace("export const schema =", "globalThis.generatedSchema =");
1000
- const context = {
1001
- ...zero
1002
- };
1003
- runInNewContext(executableSource, context);
1004
- expect(source).toContain('const usersTable = table("users").from("user_records")');
1005
- expect(context.generatedSchema).toMatchObject({
1006
- tables: {
1007
- users: {
1008
- serverName: "user_records"
1009
- }
1010
- },
1011
- relationships: {
1012
- users: {
1013
- groups: schema.relationships.users.groups
1014
- }
1015
- }
1016
- });
1017
- });
1018
- });
1019
353
  //# sourceMappingURL=generate.test.mjs.map