jazz-tools 0.16.2 → 0.16.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 (76) hide show
  1. package/.turbo/turbo-build.log +38 -38
  2. package/CHANGELOG.md +22 -0
  3. package/dist/{chunk-CL3ROOZM.js → chunk-74LZG2M3.js} +112 -36
  4. package/dist/chunk-74LZG2M3.js.map +1 -0
  5. package/dist/index.js +1 -1
  6. package/dist/react-core/hooks.d.ts +150 -2
  7. package/dist/react-core/hooks.d.ts.map +1 -1
  8. package/dist/react-core/index.js.map +1 -1
  9. package/dist/testing.js +1 -1
  10. package/dist/tools/coValues/coFeed.d.ts.map +1 -1
  11. package/dist/tools/coValues/coList.d.ts.map +1 -1
  12. package/dist/tools/coValues/coMap.d.ts.map +1 -1
  13. package/dist/tools/coValues/interfaces.d.ts.map +1 -1
  14. package/dist/tools/coValues/schemaUnion.d.ts +7 -2
  15. package/dist/tools/coValues/schemaUnion.d.ts.map +1 -1
  16. package/dist/tools/exports.d.ts +1 -1
  17. package/dist/tools/exports.d.ts.map +1 -1
  18. package/dist/tools/implementation/schema.d.ts +12 -2
  19. package/dist/tools/implementation/schema.d.ts.map +1 -1
  20. package/dist/tools/implementation/zodSchema/schemaTypes/CoFeedSchema.d.ts +2 -4
  21. package/dist/tools/implementation/zodSchema/schemaTypes/CoFeedSchema.d.ts.map +1 -1
  22. package/dist/tools/implementation/zodSchema/schemaTypes/CoListSchema.d.ts +2 -4
  23. package/dist/tools/implementation/zodSchema/schemaTypes/CoListSchema.d.ts.map +1 -1
  24. package/dist/tools/implementation/zodSchema/schemaTypes/CoMapSchema.d.ts +23 -7
  25. package/dist/tools/implementation/zodSchema/schemaTypes/CoMapSchema.d.ts.map +1 -1
  26. package/dist/tools/implementation/zodSchema/typeConverters/CoFieldInit.d.ts +20 -6
  27. package/dist/tools/implementation/zodSchema/typeConverters/CoFieldInit.d.ts.map +1 -1
  28. package/dist/tools/implementation/zodSchema/typeConverters/InstanceOfSchema.d.ts +4 -4
  29. package/dist/tools/implementation/zodSchema/typeConverters/InstanceOfSchema.d.ts.map +1 -1
  30. package/dist/tools/implementation/zodSchema/typeConverters/InstanceOfSchemaCoValuesNullable.d.ts +3 -3
  31. package/dist/tools/implementation/zodSchema/typeConverters/InstanceOfSchemaCoValuesNullable.d.ts.map +1 -1
  32. package/dist/tools/implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSchema.d.ts +3 -25
  33. package/dist/tools/implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSchema.d.ts.map +1 -1
  34. package/dist/tools/implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSchemaCoValuesNullable.d.ts +3 -25
  35. package/dist/tools/implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSchemaCoValuesNullable.d.ts.map +1 -1
  36. package/dist/tools/implementation/zodSchema/typeConverters/TypeOfZodSchema.d.ts +24 -0
  37. package/dist/tools/implementation/zodSchema/typeConverters/TypeOfZodSchema.d.ts.map +1 -0
  38. package/dist/tools/implementation/zodSchema/unionUtils.d.ts +2 -3
  39. package/dist/tools/implementation/zodSchema/unionUtils.d.ts.map +1 -1
  40. package/dist/tools/implementation/zodSchema/zodSchema.d.ts +4 -5
  41. package/dist/tools/implementation/zodSchema/zodSchema.d.ts.map +1 -1
  42. package/dist/tools/internal.d.ts +1 -0
  43. package/dist/tools/internal.d.ts.map +1 -1
  44. package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
  45. package/package.json +4 -4
  46. package/src/react-core/hooks.ts +143 -0
  47. package/src/tools/coValues/coFeed.ts +12 -4
  48. package/src/tools/coValues/coList.ts +41 -19
  49. package/src/tools/coValues/coMap.ts +12 -2
  50. package/src/tools/coValues/inbox.ts +1 -1
  51. package/src/tools/coValues/interfaces.ts +2 -3
  52. package/src/tools/coValues/schemaUnion.ts +28 -3
  53. package/src/tools/exports.ts +0 -1
  54. package/src/tools/implementation/schema.ts +28 -2
  55. package/src/tools/implementation/zodSchema/schemaTypes/CoFeedSchema.ts +2 -7
  56. package/src/tools/implementation/zodSchema/schemaTypes/CoListSchema.ts +2 -7
  57. package/src/tools/implementation/zodSchema/schemaTypes/CoMapSchema.ts +61 -12
  58. package/src/tools/implementation/zodSchema/typeConverters/CoFieldInit.ts +74 -9
  59. package/src/tools/implementation/zodSchema/typeConverters/InstanceOfSchema.ts +9 -4
  60. package/src/tools/implementation/zodSchema/typeConverters/InstanceOfSchemaCoValuesNullable.ts +3 -3
  61. package/src/tools/implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSchema.ts +3 -114
  62. package/src/tools/implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSchemaCoValuesNullable.ts +5 -129
  63. package/src/tools/implementation/zodSchema/typeConverters/TypeOfZodSchema.ts +79 -0
  64. package/src/tools/implementation/zodSchema/unionUtils.ts +6 -9
  65. package/src/tools/implementation/zodSchema/zodSchema.ts +4 -14
  66. package/src/tools/internal.ts +1 -0
  67. package/src/tools/subscribe/SubscriptionScope.ts +4 -2
  68. package/src/tools/subscribe/utils.ts +2 -2
  69. package/src/tools/tests/coFeed.test.ts +40 -0
  70. package/src/tools/tests/coList.test.ts +42 -0
  71. package/src/tools/tests/coMap.test-d.ts +184 -1
  72. package/src/tools/tests/coMap.test.ts +182 -58
  73. package/src/tools/tests/groupsAndAccounts.test.ts +54 -0
  74. package/src/tools/tests/load.test.ts +19 -0
  75. package/src/tools/tests/zod.test.ts +16 -0
  76. package/dist/chunk-CL3ROOZM.js.map +0 -1
@@ -23,11 +23,7 @@ import {
23
23
  } from "./schemaTypes/CoDiscriminatedUnionSchema.js";
24
24
  import { CoFeedSchema, CoreCoFeedSchema } from "./schemaTypes/CoFeedSchema.js";
25
25
  import { CoListSchema, CoreCoListSchema } from "./schemaTypes/CoListSchema.js";
26
- import {
27
- CoMapSchema,
28
- CoMapSchemaInit,
29
- CoreCoMapSchema,
30
- } from "./schemaTypes/CoMapSchema.js";
26
+ import { CoMapSchema, CoreCoMapSchema } from "./schemaTypes/CoMapSchema.js";
31
27
  import {
32
28
  CoOptionalSchema,
33
29
  CoreCoOptionalSchema,
@@ -84,8 +80,8 @@ export type CoValueSchemaFromCoreSchema<S extends CoreCoValueSchema> =
84
80
  export type CoValueClassFromAnySchema<S extends CoValueClassOrSchema> =
85
81
  S extends CoValueClass<any>
86
82
  ? S
87
- : CoValueClass<InstanceOfSchema<S>> &
88
- CoValueFromRaw<InstanceOfSchema<S>> &
83
+ : CoValueClass<NonNullable<InstanceOfSchema<S>>> &
84
+ CoValueFromRaw<NonNullable<InstanceOfSchema<S>>> &
89
85
  (S extends CoreAccountSchema ? AccountClassEssentials : {});
90
86
 
91
87
  type AccountClassEssentials = {
@@ -105,7 +101,7 @@ export type AnyCoreCoValueSchema =
105
101
  | CoreRichTextSchema
106
102
  | CoreFileStreamSchema;
107
103
 
108
- type AnyZodSchema = z.core.$ZodType;
104
+ export type AnyZodSchema = z.core.$ZodType;
109
105
 
110
106
  export type AnyZodOrCoValueSchema = AnyZodSchema | CoreCoValueSchema;
111
107
 
@@ -122,9 +118,3 @@ export type ResolveQueryStrict<
122
118
  T extends CoValueClassOrSchema,
123
119
  R extends ResolveQuery<T>,
124
120
  > = RefsToResolveStrict<NonNullable<InstanceOfSchemaCoValuesNullable<T>>, R>;
125
-
126
- export type InitFor<T extends CoValueClassOrSchema> = T extends CoreCoMapSchema<
127
- infer Shape
128
- >
129
- ? Simplify<CoMapSchemaInit<Shape>>
130
- : never;
@@ -45,6 +45,7 @@ export * from "./implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSc
45
45
  export * from "./implementation/zodSchema/typeConverters/InstanceOrPrimitiveOfSchemaCoValuesNullable.js";
46
46
  export * from "./implementation/zodSchema/typeConverters/InstanceOfSchema.js";
47
47
  export * from "./implementation/zodSchema/typeConverters/InstanceOfSchemaCoValuesNullable.js";
48
+ export * from "./implementation/zodSchema/typeConverters/CoFieldInit.js";
48
49
  export * from "./implementation/zodSchema/runtimeConverters/coValueSchemaTransformation.js";
49
50
  export * from "./implementation/zodSchema/runtimeConverters/schemaFieldToCoFieldDef.js";
50
51
  export * from "./coValues/extensions/imageDef.js";
@@ -7,7 +7,7 @@ import {
7
7
  type ID,
8
8
  type RefEncoded,
9
9
  type RefsToResolve,
10
- instantiateRefEncoded,
10
+ instantiateRefEncodedFromRaw,
11
11
  isRefEncoded,
12
12
  } from "../internal.js";
13
13
  import { applyCoValueMigrations } from "../lib/migration.js";
@@ -75,7 +75,9 @@ export class SubscriptionScope<D extends CoValue> {
75
75
  }
76
76
 
77
77
  this.migrating = true;
78
- applyCoValueMigrations(instantiateRefEncoded(this.schema, value));
78
+ applyCoValueMigrations(
79
+ instantiateRefEncodedFromRaw(this.schema, value),
80
+ );
79
81
  this.migrated = true;
80
82
  this.handleUpdate(lastUpdate);
81
83
  return;
@@ -4,7 +4,7 @@ import {
4
4
  CoValue,
5
5
  RefEncoded,
6
6
  coValueClassFromCoValueClassOrSchema,
7
- instantiateRefEncoded,
7
+ instantiateRefEncodedFromRaw,
8
8
  } from "../internal.js";
9
9
  import { coValuesCache } from "../lib/cache.js";
10
10
  import { SubscriptionScope } from "./SubscriptionScope.js";
@@ -26,7 +26,7 @@ export function createCoValue<D extends CoValue>(
26
26
  raw: RawCoValue,
27
27
  subscriptionScope: SubscriptionScope<D>,
28
28
  ) {
29
- const freshValueInstance = instantiateRefEncoded(ref, raw);
29
+ const freshValueInstance = instantiateRefEncodedFromRaw(ref, raw);
30
30
 
31
31
  Object.defineProperty(freshValueInstance, "_subscriptionScope", {
32
32
  value: subscriptionScope,
@@ -52,6 +52,46 @@ describe("Simple CoFeed operations", async () => {
52
52
  expect(stream.perSession[me.sessionID]?.value).toEqual("milk");
53
53
  });
54
54
 
55
+ describe("Create CoFeed with a reference", () => {
56
+ let me: Account;
57
+
58
+ beforeEach(async () => {
59
+ await setupJazzTestSync();
60
+ me = await createJazzTestAccount({
61
+ isCurrentActiveAccount: true,
62
+ creationProps: { name: "Hermes Puggington" },
63
+ });
64
+ });
65
+
66
+ test("using a CoValue", () => {
67
+ const Text = co.plainText();
68
+ const TextStream = co.feed(Text);
69
+
70
+ const stream = TextStream.create([Text.create("milk")], { owner: me });
71
+
72
+ const coValue = stream.perAccount[me.id]?.value;
73
+ expect(coValue?.toString()).toEqual("milk");
74
+ });
75
+
76
+ describe("using JSON", () => {
77
+ test("automatically creates CoValues for nested objects", () => {
78
+ const Text = co.plainText();
79
+ const TextStream = co.feed(Text);
80
+
81
+ const stream = TextStream.create(["milk"], { owner: me });
82
+
83
+ const coValue = stream.perAccount[me.id]?.value;
84
+ expect(coValue?.toString()).toEqual("milk");
85
+ });
86
+
87
+ test("can create a coPlainText from an empty string", () => {
88
+ const Schema = co.feed(co.plainText());
89
+ const feed = Schema.create([""]);
90
+ expect(feed.perAccount[me.id]?.value?.toString()).toBe("");
91
+ });
92
+ });
93
+ });
94
+
55
95
  test("Construction with nullable values", () => {
56
96
  const NullableTestStream = co.feed(z.string().nullable());
57
97
  const stream = NullableTestStream.create(["milk", null], { owner: me });
@@ -52,6 +52,48 @@ describe("Simple CoList operations", async () => {
52
52
  expect(list[2]).toBe("c");
53
53
  });
54
54
 
55
+ test("create CoList with reference using CoValue", () => {
56
+ const Dog = co.map({
57
+ name: z.string(),
58
+ });
59
+ const Person = co.map({
60
+ pets: co.list(Dog),
61
+ });
62
+
63
+ const person = Person.create({
64
+ pets: [Dog.create({ name: "Rex" }), Dog.create({ name: "Fido" })],
65
+ });
66
+
67
+ expect(person.pets.length).toEqual(2);
68
+ expect(person.pets[0]?.name).toEqual("Rex");
69
+ expect(person.pets[1]?.name).toEqual("Fido");
70
+ });
71
+
72
+ describe("create CoList with reference using JSON", () => {
73
+ test("automatically creates CoValues for nested objects", () => {
74
+ const Dog = co.map({
75
+ name: z.string(),
76
+ });
77
+ const Person = co.map({
78
+ pets: co.list(Dog),
79
+ });
80
+
81
+ const person = Person.create({
82
+ pets: [{ name: "Rex" }, { name: "Fido" }],
83
+ });
84
+
85
+ expect(person.pets.length).toEqual(2);
86
+ expect(person.pets[0]?.name).toEqual("Rex");
87
+ expect(person.pets[1]?.name).toEqual("Fido");
88
+ });
89
+
90
+ test("can create a coPlainText from an empty string", () => {
91
+ const Schema = co.list(co.plainText());
92
+ const list = Schema.create([""]);
93
+ expect(list[0]?.toString()).toBe("");
94
+ });
95
+ });
96
+
55
97
  test("list with nullable content", () => {
56
98
  const List = co.list(z.string().nullable());
57
99
  const list = List.create(["a", "b", "c", null]);
@@ -1,4 +1,5 @@
1
1
  import { assert, describe, expectTypeOf, test } from "vitest";
2
+ import { ZodNumber, ZodOptional, ZodString } from "zod/v4";
2
3
  import { Group, co, z } from "../exports.js";
3
4
  import { Account } from "../index.js";
4
5
  import { CoMap, Loaded } from "../internal.js";
@@ -54,7 +55,7 @@ describe("CoMap", async () => {
54
55
  expectTypeOf(john._owner).toEqualTypeOf<Account | Group>();
55
56
  });
56
57
 
57
- test("CoMap with reference", () => {
58
+ test("create CoMap with reference using CoValue", () => {
58
59
  const Dog = co.map({
59
60
  name: z.string(),
60
61
  breed: z.string(),
@@ -85,6 +86,46 @@ describe("CoMap", async () => {
85
86
  matches(person);
86
87
  });
87
88
 
89
+ test("create CoMap with reference using JSON", () => {
90
+ const Dog = co.map({
91
+ name: z.string(),
92
+ breed: z.string(),
93
+ });
94
+ const Person = co.map({
95
+ dog1: Dog,
96
+ dog2: Dog,
97
+ get friend() {
98
+ return Person.optional();
99
+ },
100
+ });
101
+
102
+ const person = Person.create({
103
+ // @ts-expect-error - breed is missing
104
+ dog1: { name: "Rex", items },
105
+ // @ts-expect-error - Object literal may only specify known properties
106
+ dog2: { name: "Fido", breed: "Labrador", extra: "extra" },
107
+ friend: {
108
+ dog1: {
109
+ name: "Rex",
110
+ breed: "Labrador",
111
+ },
112
+ dog2: { name: "Fido", breed: "Labrador" },
113
+ },
114
+ });
115
+
116
+ type ExpectedType = {
117
+ dog1: Loaded<typeof Dog>;
118
+ dog2: Loaded<typeof Dog>;
119
+ friend: Loaded<typeof Person> | undefined;
120
+ };
121
+
122
+ function matches(value: ExpectedType) {
123
+ return value;
124
+ }
125
+
126
+ matches(person);
127
+ });
128
+
88
129
  test("CoMap with optional reference", () => {
89
130
  const Dog = co.map({
90
131
  name: z.string(),
@@ -310,6 +351,148 @@ describe("CoMap", async () => {
310
351
  matchesNarrowed(mapWithEnum.child);
311
352
  }
312
353
  });
354
+
355
+ test("CoMap.pick()", () => {
356
+ const Person = co.map({
357
+ name: z.string(),
358
+ age: z.number(),
359
+ dog: co.map({
360
+ name: z.string(),
361
+ breed: z.string(),
362
+ }),
363
+ });
364
+
365
+ const PersonWithoutDog = Person.pick({
366
+ name: true,
367
+ age: true,
368
+ });
369
+
370
+ type ExpectedType = co.Map<{
371
+ name: ZodString;
372
+ age: ZodNumber;
373
+ }>;
374
+
375
+ function matches(value: ExpectedType) {
376
+ return value;
377
+ }
378
+
379
+ matches(PersonWithoutDog);
380
+ });
381
+
382
+ test("CoMap.pick() with a recursive reference", () => {
383
+ const Person = co.map({
384
+ name: z.string(),
385
+ age: z.number(),
386
+ dog: co.map({
387
+ name: z.string(),
388
+ breed: z.string(),
389
+ }),
390
+ get friend() {
391
+ return Person.pick({
392
+ name: true,
393
+ age: true,
394
+ }).optional();
395
+ },
396
+ });
397
+
398
+ type ExpectedType = co.Map<{
399
+ name: ZodString;
400
+ age: ZodNumber;
401
+ dog: co.Map<{
402
+ name: ZodString;
403
+ breed: ZodString;
404
+ }>;
405
+ friend: co.Optional<
406
+ co.Map<{
407
+ name: ZodString;
408
+ age: ZodNumber;
409
+ }>
410
+ >;
411
+ }>;
412
+
413
+ function matches(value: ExpectedType) {
414
+ return value;
415
+ }
416
+
417
+ matches(Person);
418
+ });
419
+
420
+ test("CoMap.partial()", () => {
421
+ const Person = co.map({
422
+ name: z.string(),
423
+ age: z.number(),
424
+ dog: co.map({
425
+ name: z.string(),
426
+ breed: z.string(),
427
+ }),
428
+ });
429
+
430
+ const PersonPartial = Person.partial();
431
+
432
+ type ExpectedType = co.Map<{
433
+ name: ZodOptional<ZodString>;
434
+ age: ZodOptional<ZodNumber>;
435
+ dog: co.Optional<
436
+ co.Map<{
437
+ name: ZodString;
438
+ breed: ZodString;
439
+ }>
440
+ >;
441
+ }>;
442
+
443
+ function matches(value: ExpectedType) {
444
+ return value;
445
+ }
446
+
447
+ matches(PersonPartial);
448
+ });
449
+
450
+ test("CoMap.partial() with a recursive reference", () => {
451
+ const Person = co.map({
452
+ get draft() {
453
+ return Person.partial()
454
+ .pick({
455
+ name: true,
456
+ age: true,
457
+ dog: true,
458
+ })
459
+ .optional();
460
+ },
461
+ name: z.string(),
462
+ age: z.number(),
463
+ dog: co.map({
464
+ name: z.string(),
465
+ breed: z.string(),
466
+ }),
467
+ });
468
+
469
+ type ExpectedType = co.Map<{
470
+ draft: co.Optional<
471
+ co.Map<{
472
+ name: ZodOptional<ZodString>;
473
+ age: ZodOptional<ZodNumber>;
474
+ dog: co.Optional<
475
+ co.Map<{
476
+ name: ZodString;
477
+ breed: ZodString;
478
+ }>
479
+ >;
480
+ }>
481
+ >;
482
+ name: ZodString;
483
+ age: ZodNumber;
484
+ dog: co.Map<{
485
+ name: ZodString;
486
+ breed: ZodString;
487
+ }>;
488
+ }>;
489
+
490
+ function matches(value: ExpectedType) {
491
+ return value;
492
+ }
493
+
494
+ matches(Person);
495
+ });
313
496
  });
314
497
 
315
498
  describe("CoMap resolution", async () => {
@@ -117,10 +117,9 @@ describe("CoMap", async () => {
117
117
  expect(emptyMap.color).toEqual(undefined);
118
118
  });
119
119
 
120
- test("CoMap with reference", () => {
120
+ test("create CoMap with reference using CoValue", () => {
121
121
  const Dog = co.map({
122
122
  name: z.string(),
123
- breed: z.string(),
124
123
  });
125
124
 
126
125
  const Person = co.map({
@@ -132,11 +131,89 @@ describe("CoMap", async () => {
132
131
  const person = Person.create({
133
132
  name: "John",
134
133
  age: 20,
135
- dog: Dog.create({ name: "Rex", breed: "Labrador" }),
134
+ dog: Dog.create({ name: "Rex" }),
136
135
  });
137
136
 
138
137
  expect(person.dog?.name).toEqual("Rex");
139
- expect(person.dog?.breed).toEqual("Labrador");
138
+ });
139
+
140
+ describe("create CoMap with references using JSON", () => {
141
+ const Dog = co.map({
142
+ type: z.literal("dog"),
143
+ name: z.string(),
144
+ });
145
+ const Cat = co.map({
146
+ type: z.literal("cat"),
147
+ name: z.string(),
148
+ });
149
+
150
+ const Person = co.map({
151
+ name: co.plainText(),
152
+ bio: co.richText(),
153
+ dog: Dog,
154
+ get friends() {
155
+ return co.list(Person);
156
+ },
157
+ reactions: co.feed(co.plainText()),
158
+ pet: co.discriminatedUnion("type", [Dog, Cat]),
159
+ });
160
+
161
+ let person: ReturnType<typeof Person.create>;
162
+
163
+ beforeEach(() => {
164
+ person = Person.create({
165
+ name: "John",
166
+ bio: "I am a software engineer",
167
+ dog: { type: "dog", name: "Rex" },
168
+ friends: [
169
+ {
170
+ name: "Jane",
171
+ bio: "I am a mechanical engineer",
172
+ dog: { type: "dog", name: "Fido" },
173
+ friends: [],
174
+ reactions: [],
175
+ pet: { type: "dog", name: "Fido" },
176
+ },
177
+ ],
178
+ reactions: ["👎", "👍"],
179
+ pet: { type: "cat", name: "Whiskers" },
180
+ });
181
+ });
182
+
183
+ it("automatically creates CoValues for each CoValue reference", () => {
184
+ expect(person.name.toString()).toEqual("John");
185
+ expect(person.bio.toString()).toEqual("I am a software engineer");
186
+ expect(person.dog?.name).toEqual("Rex");
187
+ expect(person.friends.length).toEqual(1);
188
+ expect(person.friends[0]?.name.toString()).toEqual("Jane");
189
+ expect(person.friends[0]?.bio.toString()).toEqual(
190
+ "I am a mechanical engineer",
191
+ );
192
+ expect(person.friends[0]?.dog.name).toEqual("Fido");
193
+ expect(person.friends[0]?.friends.length).toEqual(0);
194
+ expect(person.reactions.byMe?.value?.toString()).toEqual("👍");
195
+ expect(person.pet.name).toEqual("Whiskers");
196
+ });
197
+
198
+ it("creates a group for each new CoValue that is a child of the referencing CoValue's owner", () => {
199
+ for (const value of Object.values(person)) {
200
+ expect(
201
+ value._owner.getParentGroups().map((group: Group) => group.id),
202
+ ).toContain(person._owner.id);
203
+ }
204
+ const friend = person.friends[0]!;
205
+ for (const value of Object.values(friend)) {
206
+ expect(
207
+ value._owner.getParentGroups().map((group: Group) => group.id),
208
+ ).toContain(friend._owner.id);
209
+ }
210
+ });
211
+
212
+ it("can create a coPlainText from an empty string", () => {
213
+ const Schema = co.map({ text: co.plainText() });
214
+ const map = Schema.create({ text: "" });
215
+ expect(map.text.toString()).toBe("");
216
+ });
140
217
  });
141
218
 
142
219
  test("CoMap with self reference", () => {
@@ -209,7 +286,7 @@ describe("CoMap", async () => {
209
286
  const Person = co.map({
210
287
  name: z.string(),
211
288
  age: z.number(),
212
- get friend(): co.Optional<typeof Person> {
289
+ get friend() {
213
290
  return co.optional(Person);
214
291
  },
215
292
  });
@@ -2104,84 +2181,131 @@ describe("CoMap migration", () => {
2104
2181
  expect(loaded?.friend?.name).toEqual("Charlie");
2105
2182
  expect(loaded?.friend?.version).toEqual(2);
2106
2183
  });
2107
- describe("Time", () => {
2108
- test("empty map created time", () => {
2109
- const currentTimestampInSeconds = Math.floor(Date.now() / 1000);
2110
- const emptyMap = co.map({}).create({});
2111
- const createdAtInSeconds = Math.floor(emptyMap._createdAt / 1000);
2184
+ });
2185
+
2186
+ describe("createdAt & lastUpdatedAt", () => {
2187
+ test("empty map created time", () => {
2188
+ const emptyMap = co.map({}).create({});
2112
2189
 
2113
- expect(createdAtInSeconds).toEqual(currentTimestampInSeconds);
2114
- expect(emptyMap._lastUpdatedAt).toEqual(emptyMap._createdAt);
2190
+ expect(emptyMap._lastUpdatedAt).toEqual(emptyMap._createdAt);
2191
+ });
2192
+
2193
+ test("created time and last updated time", async () => {
2194
+ const Person = co.map({
2195
+ name: z.string(),
2115
2196
  });
2116
2197
 
2117
- test("created time and last updated time", async () => {
2118
- const Person = co.map({
2119
- name: z.string(),
2120
- });
2198
+ const person = Person.create({ name: "John" });
2121
2199
 
2122
- let currentTimestampInSeconds = Math.floor(Date.now() / 1000);
2123
- const person = Person.create({ name: "John" });
2200
+ const createdAt = person._createdAt;
2201
+ expect(person._lastUpdatedAt).toEqual(createdAt);
2124
2202
 
2125
- const createdAt = person._createdAt;
2126
- const createdAtInSeconds = Math.floor(createdAt / 1000);
2127
- expect(createdAtInSeconds).toEqual(currentTimestampInSeconds);
2128
- expect(person._lastUpdatedAt).toEqual(createdAt);
2203
+ await new Promise((r) => setTimeout(r, 10));
2204
+ person.name = "Jane";
2129
2205
 
2130
- await new Promise((r) => setTimeout(r, 1000));
2131
- currentTimestampInSeconds = Math.floor(Date.now() / 1000);
2132
- person.name = "Jane";
2206
+ expect(person._createdAt).toEqual(createdAt);
2207
+ expect(person._lastUpdatedAt).not.toEqual(createdAt);
2208
+ });
2209
+ });
2133
2210
 
2134
- const lastUpdatedAtInSeconds = Math.floor(person._lastUpdatedAt / 1000);
2135
- expect(lastUpdatedAtInSeconds).toEqual(currentTimestampInSeconds);
2136
- expect(person._createdAt).toEqual(createdAt);
2137
- expect(person._lastUpdatedAt).not.toEqual(createdAt);
2211
+ describe("co.map schema", () => {
2212
+ test("can access the inner schemas of a co.map", () => {
2213
+ const Person = co.map({
2214
+ name: co.plainText(),
2138
2215
  });
2139
2216
 
2140
- test("comap with custom uniqueness", () => {
2217
+ const person = Person.create({
2218
+ name: Person.shape["name"].create("John"),
2219
+ });
2220
+
2221
+ expect(person.name.toString()).toEqual("John");
2222
+ });
2223
+
2224
+ describe("pick()", () => {
2225
+ test("creates a new CoMap schema by picking fields of another CoMap schema", () => {
2141
2226
  const Person = co.map({
2142
2227
  name: z.string(),
2228
+ age: z.number(),
2143
2229
  });
2144
2230
 
2145
- let currentTimestampInSeconds = Math.floor(Date.now() / 1000);
2146
- const person = Person.create(
2147
- { name: "John" },
2148
- { unique: "name", owner: Account.getMe() },
2149
- );
2231
+ const PersonWithName = Person.pick({
2232
+ name: true,
2233
+ });
2234
+
2235
+ const person = PersonWithName.create({
2236
+ name: "John",
2237
+ });
2150
2238
 
2151
- const createdAt = person._createdAt;
2152
- const createdAtInSeconds = Math.floor(createdAt / 1000);
2153
- expect(createdAtInSeconds).toEqual(currentTimestampInSeconds);
2239
+ expect(person.name).toEqual("John");
2154
2240
  });
2155
2241
 
2156
- test("empty comap with custom uniqueness", () => {
2157
- const Person = co.map({
2158
- name: z.optional(z.string()),
2242
+ test("the new schema does not include catchall properties", () => {
2243
+ const Person = co
2244
+ .map({
2245
+ name: z.string(),
2246
+ age: z.number(),
2247
+ })
2248
+ .catchall(z.string());
2249
+
2250
+ const PersonWithName = Person.pick({
2251
+ name: true,
2159
2252
  });
2160
2253
 
2161
- let currentTimestampInSeconds = Math.floor(Date.now() / 1000);
2162
- const person = Person.create(
2163
- {},
2164
- { unique: "name", owner: Account.getMe() },
2165
- );
2254
+ expect(PersonWithName.catchAll).toBeUndefined();
2166
2255
 
2167
- const createdAt = person._createdAt;
2168
- const createdAtInSeconds = Math.floor(createdAt / 1000);
2169
- expect(createdAtInSeconds).toEqual(currentTimestampInSeconds);
2256
+ const person = PersonWithName.create({
2257
+ name: "John",
2258
+ });
2259
+ // @ts-expect-error - property `extraField` does not exist in person
2260
+ expect(person.extraField).toBeUndefined();
2170
2261
  });
2171
2262
  });
2172
- });
2173
2263
 
2174
- describe("co.map schema", () => {
2175
- test("can access the inner schemas of a co.map", () => {
2176
- const Person = co.map({
2177
- name: co.plainText(),
2178
- });
2264
+ describe("partial()", () => {
2265
+ test("creates a new CoMap schema by making all properties optional", () => {
2266
+ const Dog = co.map({
2267
+ name: z.string(),
2268
+ breed: z.string(),
2269
+ });
2270
+ const Person = co.map({
2271
+ name: z.string(),
2272
+ age: z.number(),
2273
+ pet: Dog,
2274
+ });
2179
2275
 
2180
- const person = Person.create({
2181
- name: Person.shape["name"].create("John"),
2276
+ const DraftPerson = Person.partial();
2277
+
2278
+ const draftPerson = DraftPerson.create({});
2279
+
2280
+ expect(draftPerson.name).toBeUndefined();
2281
+ expect(draftPerson.age).toBeUndefined();
2282
+ expect(draftPerson.pet).toBeUndefined();
2283
+
2284
+ draftPerson.name = "John";
2285
+ draftPerson.age = 20;
2286
+ const rex = Dog.create({ name: "Rex", breed: "Labrador" });
2287
+ draftPerson.pet = rex;
2288
+
2289
+ expect(draftPerson.name).toEqual("John");
2290
+ expect(draftPerson.age).toEqual(20);
2291
+ expect(draftPerson.pet).toEqual(rex);
2182
2292
  });
2183
2293
 
2184
- expect(person.name.toString()).toEqual("John");
2294
+ test("the new schema includes catchall properties", () => {
2295
+ const Person = co
2296
+ .map({
2297
+ name: z.string(),
2298
+ age: z.number(),
2299
+ })
2300
+ .catchall(z.string());
2301
+
2302
+ const DraftPerson = Person.partial();
2303
+
2304
+ const draftPerson = DraftPerson.create({});
2305
+ draftPerson.extraField = "extra";
2306
+
2307
+ expect(draftPerson.extraField).toEqual("extra");
2308
+ });
2185
2309
  });
2186
2310
  });
2187
2311