jazz-tools 0.14.1 → 0.14.4

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 (32) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/CHANGELOG.md +13 -0
  3. package/dist/{chunk-WLOZKDOH.js → chunk-6QMAFVZO.js} +3 -1
  4. package/dist/{chunk-WLOZKDOH.js.map → chunk-6QMAFVZO.js.map} +1 -1
  5. package/dist/implementation/zodSchema/runtimeConverters/zodFieldToCoFieldDef.d.ts.map +1 -1
  6. package/dist/implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSchema.d.ts +1 -1
  7. package/dist/implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSchema.d.ts.map +1 -1
  8. package/dist/implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSchemaCoValuesNullable.d.ts +1 -1
  9. package/dist/implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSchemaCoValuesNullable.d.ts.map +1 -1
  10. package/dist/index.js +1 -1
  11. package/dist/testing.d.ts +4 -4
  12. package/dist/testing.d.ts.map +1 -1
  13. package/dist/testing.js +5 -6
  14. package/dist/testing.js.map +1 -1
  15. package/dist/tests/coMap.record.test-d.d.ts +2 -0
  16. package/dist/tests/coMap.record.test-d.d.ts.map +1 -0
  17. package/dist/tests/coMap.test-d.d.ts +2 -0
  18. package/dist/tests/coMap.test-d.d.ts.map +1 -0
  19. package/dist/tests/patterns/notifications.test.d.ts +2 -0
  20. package/dist/tests/patterns/notifications.test.d.ts.map +1 -0
  21. package/package.json +1 -1
  22. package/src/implementation/zodSchema/runtimeConverters/zodFieldToCoFieldDef.ts +3 -0
  23. package/src/implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSchema.ts +3 -1
  24. package/src/implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSchemaCoValuesNullable.ts +3 -1
  25. package/src/testing.ts +17 -10
  26. package/src/tests/coList.test.ts +9 -0
  27. package/src/tests/coMap.record.test-d.ts +149 -0
  28. package/src/tests/coMap.record.test.ts +11 -0
  29. package/src/tests/coMap.test-d.ts +348 -0
  30. package/src/tests/coMap.test.ts +8 -0
  31. package/src/tests/patterns/notifications.test.ts +89 -0
  32. package/src/tests/patterns/requestToJoin.test.ts +8 -5
@@ -0,0 +1,348 @@
1
+ import { assert, describe, expectTypeOf, test } from "vitest";
2
+ import { Group, co, z } from "../exports.js";
3
+ import { Account } from "../index.js";
4
+ import { CoListSchema, Loaded } from "../internal.js";
5
+
6
+ describe("CoMap", async () => {
7
+ describe("init", () => {
8
+ test("create a CoMap with basic property access", () => {
9
+ const Person = co.map({
10
+ color: z.string(),
11
+ _height: z.number(),
12
+ birthday: z.date(),
13
+ name: z.string(),
14
+ enum: z.enum(["a", "b", "c"]),
15
+ enumMap: z.enum({ a: 1, b: 2, c: 3 }),
16
+ optionalDate: z.date().optional(),
17
+ });
18
+
19
+ const birthday = new Date("1989-11-27");
20
+
21
+ const john = Person.create({
22
+ color: "red",
23
+ _height: 10,
24
+ birthday,
25
+ enum: "a",
26
+ enumMap: 1,
27
+ name: "John",
28
+ });
29
+
30
+ type ExpectedType = {
31
+ color: string;
32
+ _height: number;
33
+ birthday: Date;
34
+ name: string;
35
+ enum: "a" | "b" | "c";
36
+ enumMap: 1 | 2 | 3;
37
+ optionalDate: Date | undefined;
38
+ };
39
+
40
+ function matches(value: ExpectedType) {
41
+ return value;
42
+ }
43
+
44
+ matches(john);
45
+ });
46
+
47
+ test("has the _owner property", () => {
48
+ const Person = co.map({
49
+ name: z.string(),
50
+ });
51
+
52
+ const john = Person.create({ name: "John" }, Account.getMe());
53
+
54
+ expectTypeOf(john._owner).toEqualTypeOf<Account | Group>();
55
+ });
56
+
57
+ test("CoMap with reference", () => {
58
+ const Dog = co.map({
59
+ name: z.string(),
60
+ breed: z.string(),
61
+ });
62
+
63
+ const Person = co.map({
64
+ name: z.string(),
65
+ age: z.number(),
66
+ dog: Dog,
67
+ });
68
+
69
+ const person = Person.create({
70
+ name: "John",
71
+ age: 20,
72
+ dog: Dog.create({ name: "Rex", breed: "Labrador" }),
73
+ });
74
+
75
+ type ExpectedType = {
76
+ name: string;
77
+ age: number;
78
+ dog: Loaded<typeof Dog>;
79
+ };
80
+
81
+ function matches(value: ExpectedType) {
82
+ return value;
83
+ }
84
+
85
+ matches(person);
86
+ });
87
+
88
+ test("CoMap with optional reference", () => {
89
+ const Dog = co.map({
90
+ name: z.string(),
91
+ breed: z.string(),
92
+ });
93
+
94
+ const Person = co.map({
95
+ name: z.string(),
96
+ age: z.number(),
97
+ dog: Dog.optional(),
98
+ });
99
+
100
+ const person = Person.create({
101
+ name: "John",
102
+ age: 20,
103
+ dog: Dog.create({ name: "Rex", breed: "Labrador" }),
104
+ });
105
+
106
+ type ExpectedType = {
107
+ name: string;
108
+ age: number;
109
+ dog: Loaded<typeof Dog> | undefined;
110
+ };
111
+
112
+ function matches(value: ExpectedType) {
113
+ return value;
114
+ }
115
+
116
+ matches(person);
117
+ });
118
+
119
+ test("CoMap with self reference", () => {
120
+ const Person = co.map({
121
+ name: z.string(),
122
+ age: z.number(),
123
+ // TODO: would be nice if this didn't need a type annotation
124
+ get friend(): z.ZodOptional<typeof Person> {
125
+ return z.optional(Person);
126
+ },
127
+ });
128
+
129
+ const person = Person.create({
130
+ name: "John",
131
+ age: 20,
132
+ friend: Person.create({ name: "Jane", age: 21 }),
133
+ });
134
+
135
+ type ExpectedType = {
136
+ name: string;
137
+ age: number;
138
+ friend: Loaded<typeof Person> | undefined;
139
+ };
140
+
141
+ function matches(value: ExpectedType) {
142
+ return value;
143
+ }
144
+
145
+ matches(person);
146
+ });
147
+
148
+ test("should disallow extra properties", () => {
149
+ const Person = co.map({
150
+ name: z.string(),
151
+ age: z.number(),
152
+ });
153
+
154
+ // @ts-expect-error - x is not a valid property
155
+ Person.create({ name: "John", age: 30, xtra: 1 });
156
+ });
157
+ });
158
+
159
+ describe("Mutation", () => {
160
+ test("update a reference", () => {
161
+ const Dog = co.map({
162
+ name: z.string(),
163
+ });
164
+
165
+ const Person = co.map({
166
+ name: z.string(),
167
+ age: z.number(),
168
+ dog: Dog,
169
+ });
170
+
171
+ const john = Person.create({
172
+ name: "John",
173
+ age: 20,
174
+ dog: Dog.create({ name: "Rex" }),
175
+ });
176
+
177
+ john.dog = Dog.create({ name: "Fido" });
178
+ });
179
+
180
+ test("update a reference on a loaded value", () => {
181
+ const Dog = co.map({
182
+ name: z.string(),
183
+ get siblings(): CoListSchema<typeof Dog> {
184
+ return co.list(Dog);
185
+ },
186
+ });
187
+
188
+ const Person = co.map({
189
+ name: z.string(),
190
+ age: z.number(),
191
+ dog: Dog,
192
+ });
193
+
194
+ const john = Person.create({
195
+ name: "John",
196
+ age: 20,
197
+ dog: Dog.create({ name: "Rex", siblings: co.list(Dog).create([]) }),
198
+ }) as Loaded<typeof Person, { dog: { siblings: true } }>;
199
+
200
+ john.dog = Dog.create({
201
+ name: "Fido",
202
+ siblings: co.list(Dog).create([]),
203
+ });
204
+ });
205
+ });
206
+
207
+ test("Enum of maps", () => {
208
+ const ChildA = co.map({
209
+ type: z.literal("a"),
210
+ value: z.number(),
211
+ });
212
+
213
+ const ChildB = co.map({
214
+ type: z.literal("b"),
215
+ value: z.string(),
216
+ });
217
+
218
+ const MapWithEnumOfMaps = co.map({
219
+ name: z.string(),
220
+ child: z.discriminatedUnion([ChildA, ChildB]),
221
+ });
222
+
223
+ const mapWithEnum = MapWithEnumOfMaps.create({
224
+ name: "enum",
225
+ child: ChildA.create({
226
+ type: "a",
227
+ value: 5,
228
+ }),
229
+ });
230
+
231
+ type ExpectedType = {
232
+ name: string;
233
+ child: Loaded<typeof ChildA> | Loaded<typeof ChildB>;
234
+ };
235
+
236
+ function matches(value: ExpectedType) {
237
+ return value;
238
+ }
239
+
240
+ matches(mapWithEnum);
241
+
242
+ function matchesNarrowed(value: Loaded<typeof ChildA>) {
243
+ return value;
244
+ }
245
+
246
+ if (mapWithEnum.child.type === "a") {
247
+ matchesNarrowed(mapWithEnum.child);
248
+ }
249
+ });
250
+ });
251
+
252
+ describe("CoMap resolution", async () => {
253
+ test("partial loading a map with deep resolve", async () => {
254
+ const Dog = co.map({
255
+ name: z.string(),
256
+ breed: z.string(),
257
+ });
258
+
259
+ const Person = co.map({
260
+ name: z.string(),
261
+ age: z.number(),
262
+ dog1: Dog,
263
+ dog2: Dog,
264
+ });
265
+
266
+ const person = Person.create({
267
+ name: "John",
268
+ age: 20,
269
+ dog1: Dog.create({ name: "Rex", breed: "Labrador" }),
270
+ dog2: Dog.create({ name: "Fido", breed: "Poodle" }),
271
+ });
272
+
273
+ const loadedPerson = await Person.load(person.id, {
274
+ resolve: {
275
+ dog1: true,
276
+ },
277
+ });
278
+
279
+ type ExpectedType = {
280
+ name: string;
281
+ age: number;
282
+ dog1: Loaded<typeof Dog>;
283
+ dog2: Loaded<typeof Dog> | null;
284
+ } | null;
285
+
286
+ function matches(value: ExpectedType) {
287
+ return value;
288
+ }
289
+
290
+ matches(loadedPerson);
291
+
292
+ assert(loadedPerson);
293
+ expectTypeOf<typeof loadedPerson.dog1.name>().toEqualTypeOf<string>();
294
+ expectTypeOf<typeof loadedPerson.dog2>().toEqualTypeOf<Loaded<
295
+ typeof Dog
296
+ > | null>();
297
+ });
298
+
299
+ test("loading a map with deep resolve and $onError", async () => {
300
+ const Dog = co.map({
301
+ name: z.string(),
302
+ breed: z.string(),
303
+ });
304
+
305
+ const Person = co.map({
306
+ name: z.string(),
307
+ age: z.number(),
308
+ dog1: Dog,
309
+ dog2: Dog,
310
+ });
311
+
312
+ const person = Person.create({
313
+ name: "John",
314
+ age: 20,
315
+ dog1: Dog.create({ name: "Rex", breed: "Labrador" }),
316
+ dog2: Dog.create({ name: "Fido", breed: "Poodle" }),
317
+ });
318
+
319
+ const loadedPerson = await Person.load(person.id, {
320
+ resolve: {
321
+ dog1: true,
322
+ dog2: { $onError: null },
323
+ },
324
+ });
325
+
326
+ type ExpectedType = {
327
+ name: string;
328
+ age: number;
329
+ dog1: Loaded<typeof Dog>;
330
+ dog2: Loaded<typeof Dog> | null;
331
+ } | null;
332
+
333
+ function matches(value: ExpectedType) {
334
+ return value;
335
+ }
336
+
337
+ matches(loadedPerson);
338
+
339
+ assert(loadedPerson);
340
+ expectTypeOf<typeof loadedPerson.dog1.name>().toEqualTypeOf<string>();
341
+ expectTypeOf<typeof loadedPerson.dog2>().toEqualTypeOf<
342
+ | (Loaded<typeof Dog> & {
343
+ $onError: never; // TODO: Clean the $onError from the type
344
+ })
345
+ | null
346
+ >();
347
+ });
348
+ });
@@ -34,6 +34,8 @@ describe("CoMap", async () => {
34
34
  _height: z.number(),
35
35
  birthday: z.date(),
36
36
  name: z.string(),
37
+ enum: z.enum(["a", "b", "c"]),
38
+ enumMap: z.enum({ a: 1, b: 2, c: 3 }),
37
39
  // nullable: z.optional.encoded<string | undefined>({
38
40
  // encode: (value: string | undefined) => value || null,
39
41
  // decode: (value: unknown) => (value as string) || undefined,
@@ -48,6 +50,8 @@ describe("CoMap", async () => {
48
50
  _height: 10,
49
51
  birthday,
50
52
  name: "John",
53
+ enum: "a",
54
+ enumMap: 1,
51
55
  });
52
56
 
53
57
  expect(john.color).toEqual("red");
@@ -59,7 +63,11 @@ describe("CoMap", async () => {
59
63
  "_height",
60
64
  "birthday",
61
65
  "name",
66
+ "enum",
67
+ "enumMap",
62
68
  ]);
69
+ expect(john.enum).toEqual("a");
70
+ expect(john.enumMap).toEqual(1);
63
71
  });
64
72
 
65
73
  test("property existence", () => {
@@ -0,0 +1,89 @@
1
+ import { describe, expect, test, vi } from "vitest";
2
+ import { InstanceOfSchema, Loaded, co, z } from "../../exports";
3
+ import { createJazzTestAccount } from "../../testing";
4
+
5
+ const QueuedNotification = co.map({
6
+ content: z.string(),
7
+ sent: z.boolean(),
8
+ });
9
+
10
+ const WorkerRoot = co.map({
11
+ notificationQueue: co.list(QueuedNotification),
12
+ });
13
+
14
+ const WorkerAccount = co
15
+ .account({
16
+ root: WorkerRoot,
17
+ profile: co.profile(),
18
+ })
19
+ .withMigration((account) => {
20
+ if (account.root === undefined) {
21
+ account.root = WorkerRoot.create({
22
+ notificationQueue: co.list(QueuedNotification).create([]),
23
+ });
24
+ }
25
+ });
26
+
27
+ async function pushNotification(
28
+ worker: InstanceOfSchema<typeof WorkerAccount>,
29
+ content: string,
30
+ ) {
31
+ const workerAccount = await worker.ensureLoaded({
32
+ resolve: {
33
+ root: {
34
+ notificationQueue: {
35
+ $each: true,
36
+ },
37
+ },
38
+ },
39
+ });
40
+
41
+ const notification = QueuedNotification.create({
42
+ content,
43
+ sent: false,
44
+ });
45
+
46
+ workerAccount.root.notificationQueue.push(notification);
47
+
48
+ return notification;
49
+ }
50
+
51
+ async function sendAllThePendingNotifications(
52
+ worker: InstanceOfSchema<typeof WorkerAccount>,
53
+ sender: (notification: Loaded<typeof QueuedNotification>) => Promise<void>,
54
+ ) {
55
+ const workerAccount = await worker.ensureLoaded({
56
+ resolve: {
57
+ root: {
58
+ notificationQueue: {
59
+ $each: true,
60
+ },
61
+ },
62
+ },
63
+ });
64
+
65
+ for (const notification of workerAccount.root.notificationQueue) {
66
+ if (!notification.sent) {
67
+ notification.sent = true;
68
+ await sender(notification);
69
+ }
70
+ }
71
+ }
72
+
73
+ describe("Notifications", () => {
74
+ test("when pushing a notification, it should be added to the queue", async () => {
75
+ const worker = await createJazzTestAccount({
76
+ isCurrentActiveAccount: true,
77
+ AccountSchema: WorkerAccount,
78
+ });
79
+
80
+ const notification = await pushNotification(worker, "Hello");
81
+
82
+ const spy = vi.fn();
83
+
84
+ await sendAllThePendingNotifications(worker, spy);
85
+
86
+ expect(notification.sent).toBe(true);
87
+ expect(spy).toHaveBeenCalledTimes(1);
88
+ });
89
+ });
@@ -20,6 +20,7 @@ const Organization = co.map({
20
20
  statuses: RequestsStatus,
21
21
  projects: co.list(z.string()),
22
22
  mainGroup: Group,
23
+ adminsGroup: Group,
23
24
  });
24
25
 
25
26
  async function setup() {
@@ -65,6 +66,7 @@ async function setup() {
65
66
  // but this is the source of truth for admins
66
67
  statuses: RequestsStatus.create({}, adminsGroup),
67
68
  mainGroup: organizationGroup,
69
+ adminsGroup,
68
70
  },
69
71
  publicGroup,
70
72
  );
@@ -84,7 +86,7 @@ async function setup() {
84
86
 
85
87
  async function sendRequestToJoin(organizationId: string, account: Account) {
86
88
  const organization = await Organization.load(organizationId, {
87
- resolve: { requests: true },
89
+ resolve: { requests: true, adminsGroup: true },
88
90
  loadAs: account,
89
91
  });
90
92
 
@@ -92,12 +94,15 @@ async function sendRequestToJoin(organizationId: string, account: Account) {
92
94
  throw new Error("RequestsMap not found");
93
95
  }
94
96
 
97
+ const group = Group.create(account);
98
+ group.extend(organization.adminsGroup);
99
+
95
100
  const request = RequestToJoin.create(
96
101
  {
97
102
  account,
98
103
  status: "pending",
99
104
  },
100
- organization.requests._owner,
105
+ group,
101
106
  );
102
107
 
103
108
  organization.requests[account.id] = request;
@@ -264,8 +269,6 @@ describe("Request to join", () => {
264
269
 
265
270
  // With the writeOnly permission, the user can download the request
266
271
  // but not it's content
267
- assert(requestOnUser2);
268
- expect(requestOnUser2.status).toBe(undefined);
269
- expect(requestOnUser2.account).toBe(undefined);
272
+ expect(requestOnUser2).toBeNull();
270
273
  });
271
274
  });