jazz-tools 0.17.3 → 0.17.5
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/.turbo/turbo-build.log +41 -41
- package/CHANGELOG.md +20 -0
- package/dist/{chunk-2SH44VLX.js → chunk-EEKKLGF2.js} +101 -13
- package/dist/chunk-EEKKLGF2.js.map +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/testing.js +1 -1
- package/dist/tools/coValues/coList.d.ts +43 -1
- package/dist/tools/coValues/coList.d.ts.map +1 -1
- package/dist/tools/exports.d.ts +1 -1
- package/dist/tools/exports.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoListSchema.d.ts +15 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoListSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoRecordSchema.d.ts +11 -0
- package/dist/tools/implementation/zodSchema/schemaTypes/CoRecordSchema.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/tools/coValues/coList.ts +124 -4
- package/src/tools/exports.ts +1 -0
- package/src/tools/implementation/zodSchema/schemaTypes/CoListSchema.ts +44 -1
- package/src/tools/implementation/zodSchema/schemaTypes/CoRecordSchema.ts +25 -0
- package/src/tools/tests/coList.test.ts +167 -0
- package/src/tools/tests/coMap.record.test.ts +104 -0
- package/dist/chunk-2SH44VLX.js.map +0 -1
@@ -1,5 +1,5 @@
|
|
1
|
-
import type { JsonValue, RawCoList } from "cojson";
|
2
|
-
import { ControlledAccount, RawAccount } from "cojson";
|
1
|
+
import type { JsonValue, RawCoList, CoValueUniqueness, RawCoID } from "cojson";
|
2
|
+
import { ControlledAccount, RawAccount, cojsonInternals } from "cojson";
|
3
3
|
import { calcPatch } from "fast-myers-diff";
|
4
4
|
import type {
|
5
5
|
Account,
|
@@ -24,6 +24,7 @@ import {
|
|
24
24
|
RegisteredSchemas,
|
25
25
|
SchemaInit,
|
26
26
|
accessChildByKey,
|
27
|
+
activeAccountContext,
|
27
28
|
coField,
|
28
29
|
coValueClassFromCoValueClassOrSchema,
|
29
30
|
coValuesCache,
|
@@ -236,12 +237,21 @@ export class CoList<out Item = any> extends Array<Item> implements CoValue {
|
|
236
237
|
static create<L extends CoList>(
|
237
238
|
this: CoValueClass<L>,
|
238
239
|
items: L[number][],
|
239
|
-
options?:
|
240
|
+
options?:
|
241
|
+
| {
|
242
|
+
owner: Account | Group;
|
243
|
+
unique?: CoValueUniqueness["uniqueness"];
|
244
|
+
}
|
245
|
+
| Account
|
246
|
+
| Group,
|
240
247
|
) {
|
241
|
-
const { owner } = parseCoValueCreateOptions(options);
|
248
|
+
const { owner, uniqueness } = parseCoValueCreateOptions(options);
|
242
249
|
const instance = new this({ init: items, owner });
|
243
250
|
const raw = owner._raw.createList(
|
244
251
|
toRawItems(items, instance._schema[ItemsSym], owner),
|
252
|
+
null,
|
253
|
+
"private",
|
254
|
+
uniqueness,
|
245
255
|
);
|
246
256
|
|
247
257
|
Object.defineProperties(instance, {
|
@@ -546,6 +556,116 @@ export class CoList<out Item = any> extends Array<Item> implements CoValue {
|
|
546
556
|
return cl.fromRaw(this._raw) as InstanceType<Cl>;
|
547
557
|
}
|
548
558
|
|
559
|
+
/** @deprecated Use `CoList.upsertUnique` and `CoList.loadUnique` instead. */
|
560
|
+
static findUnique<L extends CoList>(
|
561
|
+
this: CoValueClass<L>,
|
562
|
+
unique: CoValueUniqueness["uniqueness"],
|
563
|
+
ownerID: ID<Account> | ID<Group>,
|
564
|
+
as?: Account | Group | AnonymousJazzAgent,
|
565
|
+
) {
|
566
|
+
return CoList._findUnique(unique, ownerID, as);
|
567
|
+
}
|
568
|
+
|
569
|
+
/** @internal */
|
570
|
+
static _findUnique<L extends CoList>(
|
571
|
+
this: CoValueClass<L>,
|
572
|
+
unique: CoValueUniqueness["uniqueness"],
|
573
|
+
ownerID: ID<Account> | ID<Group>,
|
574
|
+
as?: Account | Group | AnonymousJazzAgent,
|
575
|
+
) {
|
576
|
+
as ||= activeAccountContext.get();
|
577
|
+
|
578
|
+
const header = {
|
579
|
+
type: "colist" as const,
|
580
|
+
ruleset: {
|
581
|
+
type: "ownedByGroup" as const,
|
582
|
+
group: ownerID as RawCoID,
|
583
|
+
},
|
584
|
+
meta: null,
|
585
|
+
uniqueness: unique,
|
586
|
+
};
|
587
|
+
const crypto =
|
588
|
+
as._type === "Anonymous" ? as.node.crypto : as._raw.core.node.crypto;
|
589
|
+
return cojsonInternals.idforHeader(header, crypto) as ID<L>;
|
590
|
+
}
|
591
|
+
|
592
|
+
/**
|
593
|
+
* Given some data, updates an existing CoList or initialises a new one if none exists.
|
594
|
+
*
|
595
|
+
* Note: This method respects resolve options, and thus can return `null` if the references cannot be resolved.
|
596
|
+
*
|
597
|
+
* @example
|
598
|
+
* ```ts
|
599
|
+
* const activeItems = await ItemList.upsertUnique(
|
600
|
+
* {
|
601
|
+
* value: [item1, item2, item3],
|
602
|
+
* unique: sourceData.identifier,
|
603
|
+
* owner: workspace,
|
604
|
+
* }
|
605
|
+
* );
|
606
|
+
* ```
|
607
|
+
*
|
608
|
+
* @param options The options for creating or loading the CoList. This includes the intended state of the CoList, its unique identifier, its owner, and the references to resolve.
|
609
|
+
* @returns Either an existing & modified CoList, or a new initialised CoList if none exists.
|
610
|
+
* @category Subscription & Loading
|
611
|
+
*/
|
612
|
+
static async upsertUnique<
|
613
|
+
L extends CoList,
|
614
|
+
const R extends RefsToResolve<L> = true,
|
615
|
+
>(
|
616
|
+
this: CoValueClass<L>,
|
617
|
+
options: {
|
618
|
+
value: L[number][];
|
619
|
+
unique: CoValueUniqueness["uniqueness"];
|
620
|
+
owner: Account | Group;
|
621
|
+
resolve?: RefsToResolveStrict<L, R>;
|
622
|
+
},
|
623
|
+
): Promise<Resolved<L, R> | null> {
|
624
|
+
let listId = CoList._findUnique(options.unique, options.owner.id);
|
625
|
+
let list: Resolved<L, R> | null = await loadCoValueWithoutMe(this, listId, {
|
626
|
+
...options,
|
627
|
+
loadAs: options.owner._loadedAs,
|
628
|
+
skipRetry: true,
|
629
|
+
});
|
630
|
+
if (!list) {
|
631
|
+
list = (this as any).create(options.value, {
|
632
|
+
owner: options.owner,
|
633
|
+
unique: options.unique,
|
634
|
+
}) as Resolved<L, R>;
|
635
|
+
} else {
|
636
|
+
(list as L).applyDiff(options.value);
|
637
|
+
}
|
638
|
+
|
639
|
+
return await loadCoValueWithoutMe(this, listId, {
|
640
|
+
...options,
|
641
|
+
loadAs: options.owner._loadedAs,
|
642
|
+
skipRetry: true,
|
643
|
+
});
|
644
|
+
}
|
645
|
+
|
646
|
+
/**
|
647
|
+
* Loads a CoList by its unique identifier and owner's ID.
|
648
|
+
* @param unique The unique identifier of the CoList to load.
|
649
|
+
* @param ownerID The ID of the owner of the CoList.
|
650
|
+
* @param options Additional options for loading the CoList.
|
651
|
+
* @returns The loaded CoList, or null if unavailable.
|
652
|
+
*/
|
653
|
+
static loadUnique<L extends CoList, const R extends RefsToResolve<L> = true>(
|
654
|
+
this: CoValueClass<L>,
|
655
|
+
unique: CoValueUniqueness["uniqueness"],
|
656
|
+
ownerID: ID<Account> | ID<Group>,
|
657
|
+
options?: {
|
658
|
+
resolve?: RefsToResolveStrict<L, R>;
|
659
|
+
loadAs?: Account | AnonymousJazzAgent;
|
660
|
+
},
|
661
|
+
): Promise<Resolved<L, R> | null> {
|
662
|
+
return loadCoValueWithoutMe(
|
663
|
+
this,
|
664
|
+
CoList._findUnique(unique, ownerID, options?.loadAs),
|
665
|
+
{ ...options, skipRetry: true },
|
666
|
+
);
|
667
|
+
}
|
668
|
+
|
549
669
|
/**
|
550
670
|
* Wait for the `CoList` to be uploaded to the other peers.
|
551
671
|
*
|
package/src/tools/exports.ts
CHANGED
@@ -2,12 +2,14 @@ import {
|
|
2
2
|
Account,
|
3
3
|
CoList,
|
4
4
|
Group,
|
5
|
+
ID,
|
5
6
|
RefsToResolve,
|
6
7
|
RefsToResolveStrict,
|
7
8
|
Resolved,
|
8
9
|
SubscribeListenerOptions,
|
9
10
|
coOptionalDefiner,
|
10
11
|
} from "../../../internal.js";
|
12
|
+
import { CoValueUniqueness } from "cojson";
|
11
13
|
import { AnonymousJazzAgent } from "../../anonymousJazzAgent.js";
|
12
14
|
import { CoListInit } from "../typeConverters/CoFieldInit.js";
|
13
15
|
import { InstanceOrPrimitiveOfSchema } from "../typeConverters/InstanceOrPrimitiveOfSchema.js";
|
@@ -29,7 +31,13 @@ export class CoListSchema<T extends AnyZodOrCoValueSchema>
|
|
29
31
|
|
30
32
|
create(
|
31
33
|
items: CoListInit<T>,
|
32
|
-
options?:
|
34
|
+
options?:
|
35
|
+
| {
|
36
|
+
owner: Account | Group;
|
37
|
+
unique?: CoValueUniqueness["uniqueness"];
|
38
|
+
}
|
39
|
+
| Account
|
40
|
+
| Group,
|
33
41
|
): CoListInstance<T> {
|
34
42
|
return this.coValueClass.create(items as any, options) as CoListInstance<T>;
|
35
43
|
}
|
@@ -62,6 +70,41 @@ export class CoListSchema<T extends AnyZodOrCoValueSchema>
|
|
62
70
|
return this.coValueClass;
|
63
71
|
}
|
64
72
|
|
73
|
+
/** @deprecated Use `CoList.upsertUnique` and `CoList.loadUnique` instead. */
|
74
|
+
findUnique(
|
75
|
+
unique: CoValueUniqueness["uniqueness"],
|
76
|
+
ownerID: ID<Account> | ID<Group>,
|
77
|
+
as?: Account | Group | AnonymousJazzAgent,
|
78
|
+
): ID<CoListInstanceCoValuesNullable<T>> {
|
79
|
+
return this.coValueClass.findUnique(unique, ownerID, as);
|
80
|
+
}
|
81
|
+
|
82
|
+
upsertUnique<
|
83
|
+
const R extends RefsToResolve<CoListInstanceCoValuesNullable<T>> = true,
|
84
|
+
>(options: {
|
85
|
+
value: CoListInit<T>;
|
86
|
+
unique: CoValueUniqueness["uniqueness"];
|
87
|
+
owner: Account | Group;
|
88
|
+
resolve?: RefsToResolveStrict<CoListInstanceCoValuesNullable<T>, R>;
|
89
|
+
}): Promise<Resolved<CoListInstanceCoValuesNullable<T>, R> | null> {
|
90
|
+
// @ts-expect-error
|
91
|
+
return this.coValueClass.upsertUnique(options);
|
92
|
+
}
|
93
|
+
|
94
|
+
loadUnique<
|
95
|
+
const R extends RefsToResolve<CoListInstanceCoValuesNullable<T>> = true,
|
96
|
+
>(
|
97
|
+
unique: CoValueUniqueness["uniqueness"],
|
98
|
+
ownerID: ID<Account> | ID<Group>,
|
99
|
+
options?: {
|
100
|
+
resolve?: RefsToResolveStrict<CoListInstanceCoValuesNullable<T>, R>;
|
101
|
+
loadAs?: Account | AnonymousJazzAgent;
|
102
|
+
},
|
103
|
+
): Promise<Resolved<CoListInstanceCoValuesNullable<T>, R> | null> {
|
104
|
+
// @ts-expect-error
|
105
|
+
return this.coValueClass.loadUnique(unique, ownerID, options);
|
106
|
+
}
|
107
|
+
|
65
108
|
optional(): CoOptionalSchema<this> {
|
66
109
|
return coOptionalDefiner(this);
|
67
110
|
}
|
@@ -72,12 +72,37 @@ export interface CoRecordSchema<
|
|
72
72
|
) => void,
|
73
73
|
): () => void;
|
74
74
|
|
75
|
+
/** @deprecated Use `CoMap.upsertUnique` and `CoMap.loadUnique` instead. */
|
75
76
|
findUnique(
|
76
77
|
unique: CoValueUniqueness["uniqueness"],
|
77
78
|
ownerID: ID<Account> | ID<Group>,
|
78
79
|
as?: Account | Group | AnonymousJazzAgent,
|
79
80
|
): ID<CoRecordInstanceCoValuesNullable<K, V>>;
|
80
81
|
|
82
|
+
upsertUnique<
|
83
|
+
const R extends RefsToResolve<
|
84
|
+
CoRecordInstanceCoValuesNullable<K, V>
|
85
|
+
> = true,
|
86
|
+
>(options: {
|
87
|
+
value: Simplify<CoRecordInit<K, V>>;
|
88
|
+
unique: CoValueUniqueness["uniqueness"];
|
89
|
+
owner: Account | Group;
|
90
|
+
resolve?: RefsToResolveStrict<CoRecordInstanceCoValuesNullable<K, V>, R>;
|
91
|
+
}): Promise<Resolved<CoRecordInstanceCoValuesNullable<K, V>, R> | null>;
|
92
|
+
|
93
|
+
loadUnique<
|
94
|
+
const R extends RefsToResolve<
|
95
|
+
CoRecordInstanceCoValuesNullable<K, V>
|
96
|
+
> = true,
|
97
|
+
>(
|
98
|
+
unique: CoValueUniqueness["uniqueness"],
|
99
|
+
ownerID: ID<Account> | ID<Group>,
|
100
|
+
options?: {
|
101
|
+
resolve?: RefsToResolveStrict<CoRecordInstanceCoValuesNullable<K, V>, R>;
|
102
|
+
loadAs?: Account | AnonymousJazzAgent;
|
103
|
+
},
|
104
|
+
): Promise<Resolved<CoRecordInstanceCoValuesNullable<K, V>, R> | null>;
|
105
|
+
|
81
106
|
getCoValueClass: () => typeof CoMap;
|
82
107
|
|
83
108
|
optional(): CoOptionalSchema<this>;
|
@@ -863,6 +863,173 @@ describe("CoList subscription", async () => {
|
|
863
863
|
});
|
864
864
|
});
|
865
865
|
|
866
|
+
describe("CoList unique methods", () => {
|
867
|
+
test("loadUnique returns existing list", async () => {
|
868
|
+
const ItemList = co.list(z.string());
|
869
|
+
const group = Group.create();
|
870
|
+
|
871
|
+
const originalList = ItemList.create(["item1", "item2", "item3"], {
|
872
|
+
owner: group,
|
873
|
+
unique: "test-list",
|
874
|
+
});
|
875
|
+
|
876
|
+
const foundList = await ItemList.loadUnique("test-list", group.id);
|
877
|
+
expect(foundList).toEqual(originalList);
|
878
|
+
expect(foundList?.length).toBe(3);
|
879
|
+
expect(foundList?.[0]).toBe("item1");
|
880
|
+
});
|
881
|
+
|
882
|
+
test("loadUnique returns null for non-existent list", async () => {
|
883
|
+
const ItemList = co.list(z.string());
|
884
|
+
const group = Group.create();
|
885
|
+
|
886
|
+
const foundList = await ItemList.loadUnique("non-existent", group.id);
|
887
|
+
expect(foundList).toBeNull();
|
888
|
+
});
|
889
|
+
|
890
|
+
test("upsertUnique creates new list when none exists", async () => {
|
891
|
+
const ItemList = co.list(z.string());
|
892
|
+
const group = Group.create();
|
893
|
+
|
894
|
+
const sourceData = ["item1", "item2", "item3"];
|
895
|
+
|
896
|
+
const result = await ItemList.upsertUnique({
|
897
|
+
value: sourceData,
|
898
|
+
unique: "new-list",
|
899
|
+
owner: group,
|
900
|
+
});
|
901
|
+
|
902
|
+
expect(result).not.toBeNull();
|
903
|
+
expect(result?.length).toBe(3);
|
904
|
+
expect(result?.[0]).toBe("item1");
|
905
|
+
expect(result?.[1]).toBe("item2");
|
906
|
+
expect(result?.[2]).toBe("item3");
|
907
|
+
});
|
908
|
+
|
909
|
+
test("upsertUnique updates existing list", async () => {
|
910
|
+
const ItemList = co.list(z.string());
|
911
|
+
const group = Group.create();
|
912
|
+
|
913
|
+
// Create initial list
|
914
|
+
const originalList = ItemList.create(["original1", "original2"], {
|
915
|
+
owner: group,
|
916
|
+
unique: "update-list",
|
917
|
+
});
|
918
|
+
|
919
|
+
// Upsert with new data
|
920
|
+
const updatedList = await ItemList.upsertUnique({
|
921
|
+
value: ["updated1", "updated2", "updated3"],
|
922
|
+
unique: "update-list",
|
923
|
+
owner: group,
|
924
|
+
});
|
925
|
+
|
926
|
+
expect(updatedList).toEqual(originalList); // Should be the same instance
|
927
|
+
expect(updatedList?.length).toBe(3);
|
928
|
+
expect(updatedList?.[0]).toBe("updated1");
|
929
|
+
expect(updatedList?.[1]).toBe("updated2");
|
930
|
+
expect(updatedList?.[2]).toBe("updated3");
|
931
|
+
});
|
932
|
+
|
933
|
+
test("upsertUnique with CoValue items", async () => {
|
934
|
+
const Item = co.map({
|
935
|
+
name: z.string(),
|
936
|
+
value: z.number(),
|
937
|
+
});
|
938
|
+
const ItemList = co.list(Item);
|
939
|
+
const group = Group.create();
|
940
|
+
|
941
|
+
const items = [
|
942
|
+
Item.create({ name: "First", value: 1 }, group),
|
943
|
+
Item.create({ name: "Second", value: 2 }, group),
|
944
|
+
];
|
945
|
+
|
946
|
+
const result = await ItemList.upsertUnique({
|
947
|
+
value: items,
|
948
|
+
unique: "item-list",
|
949
|
+
owner: group,
|
950
|
+
resolve: { $each: true },
|
951
|
+
});
|
952
|
+
|
953
|
+
expect(result).not.toBeNull();
|
954
|
+
expect(result?.length).toBe(2);
|
955
|
+
expect(result?.[0]?.name).toBe("First");
|
956
|
+
expect(result?.[1]?.name).toBe("Second");
|
957
|
+
});
|
958
|
+
|
959
|
+
test("upsertUnique updates list with CoValue items", async () => {
|
960
|
+
const Item = co.map({
|
961
|
+
name: z.string(),
|
962
|
+
value: z.number(),
|
963
|
+
});
|
964
|
+
const ItemList = co.list(Item);
|
965
|
+
const group = Group.create();
|
966
|
+
|
967
|
+
// Create initial list
|
968
|
+
const initialItems = [Item.create({ name: "Initial", value: 0 }, group)];
|
969
|
+
const originalList = ItemList.create(initialItems, {
|
970
|
+
owner: group,
|
971
|
+
unique: "updateable-item-list",
|
972
|
+
});
|
973
|
+
|
974
|
+
// Upsert with new items
|
975
|
+
const newItems = [
|
976
|
+
Item.create({ name: "Updated", value: 1 }, group),
|
977
|
+
Item.create({ name: "Added", value: 2 }, group),
|
978
|
+
];
|
979
|
+
|
980
|
+
const updatedList = await ItemList.upsertUnique({
|
981
|
+
value: newItems,
|
982
|
+
unique: "updateable-item-list",
|
983
|
+
owner: group,
|
984
|
+
resolve: { $each: true },
|
985
|
+
});
|
986
|
+
|
987
|
+
expect(updatedList).toEqual(originalList); // Should be the same instance
|
988
|
+
expect(updatedList?.length).toBe(2);
|
989
|
+
expect(updatedList?.[0]?.name).toBe("Updated");
|
990
|
+
expect(updatedList?.[1]?.name).toBe("Added");
|
991
|
+
});
|
992
|
+
|
993
|
+
test("findUnique returns correct ID", async () => {
|
994
|
+
const ItemList = co.list(z.string());
|
995
|
+
const group = Group.create();
|
996
|
+
|
997
|
+
const originalList = ItemList.create(["test"], {
|
998
|
+
owner: group,
|
999
|
+
unique: "find-test",
|
1000
|
+
});
|
1001
|
+
|
1002
|
+
const foundId = ItemList.findUnique("find-test", group.id);
|
1003
|
+
expect(foundId).toBe(originalList.id);
|
1004
|
+
});
|
1005
|
+
|
1006
|
+
test("upsertUnique with resolve options", async () => {
|
1007
|
+
const Category = co.map({ title: z.string() });
|
1008
|
+
const Item = co.map({
|
1009
|
+
name: z.string(),
|
1010
|
+
category: Category,
|
1011
|
+
});
|
1012
|
+
const ItemList = co.list(Item);
|
1013
|
+
const group = Group.create();
|
1014
|
+
|
1015
|
+
const category = Category.create({ title: "Category 1" }, group);
|
1016
|
+
|
1017
|
+
const items = [Item.create({ name: "Item 1", category }, group)];
|
1018
|
+
|
1019
|
+
const result = await ItemList.upsertUnique({
|
1020
|
+
value: items,
|
1021
|
+
unique: "resolved-list",
|
1022
|
+
owner: group,
|
1023
|
+
resolve: { $each: { category: true } },
|
1024
|
+
});
|
1025
|
+
|
1026
|
+
expect(result).not.toBeNull();
|
1027
|
+
expect(result?.length).toBe(1);
|
1028
|
+
expect(result?.[0]?.name).toBe("Item 1");
|
1029
|
+
expect(result?.[0]?.category?.title).toBe("Category 1");
|
1030
|
+
});
|
1031
|
+
});
|
1032
|
+
|
866
1033
|
describe("co.list schema", () => {
|
867
1034
|
test("can access the inner schema of a co.list", () => {
|
868
1035
|
const Keywords = co.list(co.plainText());
|
@@ -460,3 +460,107 @@ describe("CoMap.Record", async () => {
|
|
460
460
|
}
|
461
461
|
});
|
462
462
|
});
|
463
|
+
|
464
|
+
describe("CoRecord unique methods", () => {
|
465
|
+
test("loadUnique returns existing record", async () => {
|
466
|
+
const ItemRecord = co.record(z.string(), z.number());
|
467
|
+
const group = Group.create();
|
468
|
+
|
469
|
+
const originalRecord = ItemRecord.create(
|
470
|
+
{ item1: 1, item2: 2, item3: 3 },
|
471
|
+
{ owner: group, unique: "test-record" },
|
472
|
+
);
|
473
|
+
|
474
|
+
const foundRecord = await ItemRecord.loadUnique("test-record", group.id);
|
475
|
+
expect(foundRecord).toEqual(originalRecord);
|
476
|
+
expect(foundRecord?.item1).toBe(1);
|
477
|
+
expect(foundRecord?.item2).toBe(2);
|
478
|
+
});
|
479
|
+
|
480
|
+
test("loadUnique returns null for non-existent record", async () => {
|
481
|
+
const ItemRecord = co.record(z.string(), z.number());
|
482
|
+
const group = Group.create();
|
483
|
+
|
484
|
+
const foundRecord = await ItemRecord.loadUnique("non-existent", group.id);
|
485
|
+
expect(foundRecord).toBeNull();
|
486
|
+
});
|
487
|
+
|
488
|
+
test("upsertUnique creates new record when none exists", async () => {
|
489
|
+
const ItemRecord = co.record(z.string(), z.number());
|
490
|
+
const group = Group.create();
|
491
|
+
|
492
|
+
const sourceData = { item1: 1, item2: 2, item3: 3 };
|
493
|
+
|
494
|
+
const result = await ItemRecord.upsertUnique({
|
495
|
+
value: sourceData,
|
496
|
+
unique: "new-record",
|
497
|
+
owner: group,
|
498
|
+
});
|
499
|
+
|
500
|
+
expect(result).not.toBeNull();
|
501
|
+
expect(result?.item1).toBe(1);
|
502
|
+
expect(result?.item2).toBe(2);
|
503
|
+
expect(result?.item3).toBe(3);
|
504
|
+
});
|
505
|
+
|
506
|
+
test("upsertUnique updates existing record", async () => {
|
507
|
+
const ItemRecord = co.record(z.string(), z.number());
|
508
|
+
const group = Group.create();
|
509
|
+
|
510
|
+
// Create initial record
|
511
|
+
const originalRecord = ItemRecord.create(
|
512
|
+
{ original1: 1, original2: 2 },
|
513
|
+
{ owner: group, unique: "update-record" },
|
514
|
+
);
|
515
|
+
|
516
|
+
// Upsert with new data
|
517
|
+
const updatedRecord = await ItemRecord.upsertUnique({
|
518
|
+
value: { updated1: 10, updated2: 20, updated3: 30 },
|
519
|
+
unique: "update-record",
|
520
|
+
owner: group,
|
521
|
+
});
|
522
|
+
|
523
|
+
expect(updatedRecord).toEqual(originalRecord); // Should be the same instance
|
524
|
+
expect(updatedRecord?.updated1).toBe(10);
|
525
|
+
expect(updatedRecord?.updated2).toBe(20);
|
526
|
+
expect(updatedRecord?.updated3).toBe(30);
|
527
|
+
});
|
528
|
+
|
529
|
+
test("upsertUnique with CoValue items", async () => {
|
530
|
+
const Item = co.map({
|
531
|
+
name: z.string(),
|
532
|
+
value: z.number(),
|
533
|
+
});
|
534
|
+
const ItemRecord = co.record(z.string(), Item);
|
535
|
+
const group = Group.create();
|
536
|
+
|
537
|
+
const items = {
|
538
|
+
first: Item.create({ name: "First", value: 1 }, group),
|
539
|
+
second: Item.create({ name: "Second", value: 2 }, group),
|
540
|
+
};
|
541
|
+
|
542
|
+
const result = await ItemRecord.upsertUnique({
|
543
|
+
value: items,
|
544
|
+
unique: "item-record",
|
545
|
+
owner: group,
|
546
|
+
resolve: { first: true, second: true },
|
547
|
+
});
|
548
|
+
|
549
|
+
expect(result).not.toBeNull();
|
550
|
+
expect(result?.first?.name).toBe("First");
|
551
|
+
expect(result?.second?.name).toBe("Second");
|
552
|
+
});
|
553
|
+
|
554
|
+
test("findUnique returns correct ID", async () => {
|
555
|
+
const ItemRecord = co.record(z.string(), z.string());
|
556
|
+
const group = Group.create();
|
557
|
+
|
558
|
+
const originalRecord = ItemRecord.create(
|
559
|
+
{ test: "value" },
|
560
|
+
{ owner: group, unique: "find-test" },
|
561
|
+
);
|
562
|
+
|
563
|
+
const foundId = ItemRecord.findUnique("find-test", group.id);
|
564
|
+
expect(foundId).toBe(originalRecord.id);
|
565
|
+
});
|
566
|
+
});
|