cojson 0.1.8 → 0.1.10

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/dist/account.d.ts +6 -3
  2. package/dist/account.js +4 -2
  3. package/dist/account.js.map +1 -1
  4. package/dist/coValue.d.ts +44 -80
  5. package/dist/coValue.js +4 -348
  6. package/dist/coValue.js.map +1 -1
  7. package/dist/coValueCore.d.ts +84 -0
  8. package/dist/coValueCore.js +356 -0
  9. package/dist/coValueCore.js.map +1 -0
  10. package/dist/coValues/coList.d.ts +114 -0
  11. package/dist/{contentTypes → coValues}/coList.js +59 -19
  12. package/dist/coValues/coList.js.map +1 -0
  13. package/dist/{contentTypes → coValues}/coMap.d.ts +25 -7
  14. package/dist/{contentTypes → coValues}/coMap.js +34 -15
  15. package/dist/coValues/coMap.js.map +1 -0
  16. package/dist/coValues/coStream.d.ts +69 -0
  17. package/dist/coValues/coStream.js +131 -0
  18. package/dist/coValues/coStream.js.map +1 -0
  19. package/dist/coValues/static.d.ts +14 -0
  20. package/dist/coValues/static.js +20 -0
  21. package/dist/coValues/static.js.map +1 -0
  22. package/dist/group.d.ts +57 -9
  23. package/dist/group.js +94 -28
  24. package/dist/group.js.map +1 -1
  25. package/dist/index.d.ts +19 -10
  26. package/dist/index.js +7 -5
  27. package/dist/index.js.map +1 -1
  28. package/dist/node.d.ts +59 -5
  29. package/dist/node.js +36 -15
  30. package/dist/node.js.map +1 -1
  31. package/dist/permissions.d.ts +2 -2
  32. package/dist/permissions.js +1 -1
  33. package/dist/permissions.js.map +1 -1
  34. package/dist/sync.d.ts +3 -3
  35. package/dist/sync.js +2 -2
  36. package/dist/sync.js.map +1 -1
  37. package/dist/testUtils.d.ts +2 -2
  38. package/dist/testUtils.js +1 -1
  39. package/dist/testUtils.js.map +1 -1
  40. package/package.json +2 -2
  41. package/src/account.test.ts +1 -1
  42. package/src/account.ts +8 -5
  43. package/src/coValue.test.ts +335 -129
  44. package/src/coValue.ts +52 -576
  45. package/src/coValueCore.test.ts +180 -0
  46. package/src/coValueCore.ts +592 -0
  47. package/src/{contentTypes → coValues}/coList.ts +91 -42
  48. package/src/{contentTypes → coValues}/coMap.ts +40 -20
  49. package/src/coValues/coStream.ts +249 -0
  50. package/src/coValues/static.ts +31 -0
  51. package/src/group.test.ts +47 -0
  52. package/src/group.ts +120 -50
  53. package/src/index.ts +43 -28
  54. package/src/node.ts +48 -27
  55. package/src/permissions.test.ts +32 -32
  56. package/src/permissions.ts +5 -5
  57. package/src/sync.test.ts +77 -77
  58. package/src/sync.ts +5 -5
  59. package/src/testUtils.ts +1 -1
  60. package/tsconfig.json +1 -2
  61. package/dist/contentType.d.ts +0 -15
  62. package/dist/contentType.js +0 -7
  63. package/dist/contentType.js.map +0 -1
  64. package/dist/contentTypes/coList.d.ts +0 -77
  65. package/dist/contentTypes/coList.js.map +0 -1
  66. package/dist/contentTypes/coMap.js.map +0 -1
  67. package/dist/contentTypes/coStream.d.ts +0 -11
  68. package/dist/contentTypes/coStream.js +0 -16
  69. package/dist/contentTypes/coStream.js.map +0 -1
  70. package/dist/contentTypes/static.d.ts +0 -11
  71. package/dist/contentTypes/static.js +0 -14
  72. package/dist/contentTypes/static.js.map +0 -1
  73. package/src/contentType.test.ts +0 -284
  74. package/src/contentType.ts +0 -26
  75. package/src/contentTypes/coStream.ts +0 -24
  76. package/src/contentTypes/static.ts +0 -22
@@ -1,181 +1,387 @@
1
- import { Transaction } from "./coValue.js";
1
+ import { accountOrAgentIDfromSessionID } from "./coValueCore.js";
2
+ import { BinaryCoStream } from "./coValues/coStream.js";
3
+ import { createdNowUnique } from "./crypto.js";
2
4
  import { LocalNode } from "./node.js";
3
- import { createdNowUnique, getAgentSignerSecret, newRandomAgentSecret, sign } from "./crypto.js";
4
5
  import { randomAnonymousAccountAndSessionID } from "./testUtils.js";
5
- import { CoMap, MapOpPayload } from "./contentTypes/coMap.js";
6
- import { AccountID } from "./index.js";
7
- import { Role } from "./permissions.js";
8
6
 
9
- test("Can create coValue with new agent credentials and add transaction to it", () => {
10
- const [account, sessionID] = randomAnonymousAccountAndSessionID();
11
- const node = new LocalNode(account, sessionID);
7
+ test("Empty CoMap works", () => {
8
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID());
12
9
 
13
10
  const coValue = node.createCoValue({
14
- type: "costream",
11
+ type: "comap",
15
12
  ruleset: { type: "unsafeAllowAll" },
16
13
  meta: null,
17
14
  ...createdNowUnique(),
18
15
  });
19
16
 
20
- const transaction: Transaction = {
21
- privacy: "trusting",
22
- madeAt: Date.now(),
23
- changes: [
24
- {
25
- hello: "world",
26
- },
27
- ],
28
- };
29
-
30
- const { expectedNewHash } = coValue.expectedNewHashAfter(
31
- node.currentSessionID,
32
- [transaction]
33
- );
34
-
35
- expect(
36
- coValue.tryAddTransactions(
37
- node.currentSessionID,
38
- [transaction],
39
- expectedNewHash,
40
- sign(account.currentSignerSecret(), expectedNewHash)
41
- )
42
- ).toBe(true);
17
+ const content = coValue.getCurrentContent();
18
+
19
+ if (content.type !== "comap") {
20
+ throw new Error("Expected map");
21
+ }
22
+
23
+ expect(content.type).toEqual("comap");
24
+ expect([...content.keys()]).toEqual([]);
25
+ expect(content.toJSON()).toEqual({});
43
26
  });
44
27
 
45
- test("transactions with wrong signature are rejected", () => {
46
- const wrongAgent = newRandomAgentSecret();
47
- const [agentSecret, sessionID] = randomAnonymousAccountAndSessionID();
48
- const node = new LocalNode(agentSecret, sessionID);
28
+ test("Can insert and delete CoMap entries in edit()", () => {
29
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID());
49
30
 
50
31
  const coValue = node.createCoValue({
51
- type: "costream",
32
+ type: "comap",
52
33
  ruleset: { type: "unsafeAllowAll" },
53
34
  meta: null,
54
35
  ...createdNowUnique(),
55
36
  });
56
37
 
57
- const transaction: Transaction = {
58
- privacy: "trusting",
59
- madeAt: Date.now(),
60
- changes: [
61
- {
62
- hello: "world",
63
- },
64
- ],
65
- };
66
-
67
- const { expectedNewHash } = coValue.expectedNewHashAfter(
68
- node.currentSessionID,
69
- [transaction]
70
- );
71
-
72
- expect(
73
- coValue.tryAddTransactions(
74
- node.currentSessionID,
75
- [transaction],
76
- expectedNewHash,
77
- sign(getAgentSignerSecret(wrongAgent), expectedNewHash)
78
- )
79
- ).toBe(false);
38
+ const content = coValue.getCurrentContent();
39
+
40
+ if (content.type !== "comap") {
41
+ throw new Error("Expected map");
42
+ }
43
+
44
+ expect(content.type).toEqual("comap");
45
+
46
+ content.edit((editable) => {
47
+ editable.set("hello", "world", "trusting");
48
+ expect(editable.get("hello")).toEqual("world");
49
+ editable.set("foo", "bar", "trusting");
50
+ expect(editable.get("foo")).toEqual("bar");
51
+ expect([...editable.keys()]).toEqual(["hello", "foo"]);
52
+ editable.delete("foo", "trusting");
53
+ expect(editable.get("foo")).toEqual(undefined);
54
+ });
80
55
  });
81
56
 
82
- test("transactions with correctly signed, but wrong hash are rejected", () => {
83
- const [account, sessionID] = randomAnonymousAccountAndSessionID();
84
- const node = new LocalNode(account, sessionID);
57
+ test("Can get CoMap entry values at different points in time", () => {
58
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID());
85
59
 
86
60
  const coValue = node.createCoValue({
87
- type: "costream",
61
+ type: "comap",
62
+ ruleset: { type: "unsafeAllowAll" },
63
+ meta: null,
64
+ ...createdNowUnique(),
65
+ });
66
+
67
+ const content = coValue.getCurrentContent();
68
+
69
+ if (content.type !== "comap") {
70
+ throw new Error("Expected map");
71
+ }
72
+
73
+ expect(content.type).toEqual("comap");
74
+
75
+ content.edit((editable) => {
76
+ const beforeA = Date.now();
77
+ while (Date.now() < beforeA + 10) {}
78
+ editable.set("hello", "A", "trusting");
79
+ const beforeB = Date.now();
80
+ while (Date.now() < beforeB + 10) {}
81
+ editable.set("hello", "B", "trusting");
82
+ const beforeC = Date.now();
83
+ while (Date.now() < beforeC + 10) {}
84
+ editable.set("hello", "C", "trusting");
85
+ expect(editable.get("hello")).toEqual("C");
86
+ expect(editable.getAtTime("hello", Date.now())).toEqual("C");
87
+ expect(editable.getAtTime("hello", beforeA)).toEqual(undefined);
88
+ expect(editable.getAtTime("hello", beforeB)).toEqual("A");
89
+ expect(editable.getAtTime("hello", beforeC)).toEqual("B");
90
+ });
91
+ });
92
+
93
+ test("Can get all historic values of key in CoMap", () => {
94
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID());
95
+
96
+ const coValue = node.createCoValue({
97
+ type: "comap",
88
98
  ruleset: { type: "unsafeAllowAll" },
89
99
  meta: null,
90
100
  ...createdNowUnique(),
91
101
  });
92
102
 
93
- const transaction: Transaction = {
94
- privacy: "trusting",
95
- madeAt: Date.now(),
96
- changes: [
103
+ const content = coValue.getCurrentContent();
104
+
105
+ if (content.type !== "comap") {
106
+ throw new Error("Expected map");
107
+ }
108
+
109
+ expect(content.type).toEqual("comap");
110
+
111
+ content.edit((editable) => {
112
+ editable.set("hello", "A", "trusting");
113
+ const txA = editable.getLastTxID("hello");
114
+ editable.set("hello", "B", "trusting");
115
+ const txB = editable.getLastTxID("hello");
116
+ editable.delete("hello", "trusting");
117
+ const txDel = editable.getLastTxID("hello");
118
+ editable.set("hello", "C", "trusting");
119
+ const txC = editable.getLastTxID("hello");
120
+ expect(editable.getHistory("hello")).toEqual([
97
121
  {
98
- hello: "world",
122
+ txID: txA,
123
+ value: "A",
124
+ at: txA && coValue.getTx(txA)?.madeAt,
99
125
  },
100
- ],
101
- };
102
-
103
- const { expectedNewHash } = coValue.expectedNewHashAfter(
104
- node.currentSessionID,
105
- [
106
126
  {
107
- privacy: "trusting",
108
- madeAt: Date.now(),
109
- changes: [
110
- {
111
- hello: "wrong",
112
- },
113
- ],
127
+ txID: txB,
128
+ value: "B",
129
+ at: txB && coValue.getTx(txB)?.madeAt,
114
130
  },
115
- ]
116
- );
117
-
118
- expect(
119
- coValue.tryAddTransactions(
120
- node.currentSessionID,
121
- [transaction],
122
- expectedNewHash,
123
- sign(account.currentSignerSecret(), expectedNewHash)
124
- )
125
- ).toBe(false);
131
+ {
132
+ txID: txDel,
133
+ value: undefined,
134
+ at: txDel && coValue.getTx(txDel)?.madeAt,
135
+ },
136
+ {
137
+ txID: txC,
138
+ value: "C",
139
+ at: txC && coValue.getTx(txC)?.madeAt,
140
+ },
141
+ ]);
142
+ });
126
143
  });
127
144
 
128
- test("New transactions in a group correctly update owned values, including subscriptions", async () => {
129
- const [account, sessionID] = randomAnonymousAccountAndSessionID();
130
- const node = new LocalNode(account, sessionID);
145
+ test("Can get last tx ID for a key in CoMap", () => {
146
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID());
131
147
 
132
- const group = node.createGroup();
148
+ const coValue = node.createCoValue({
149
+ type: "comap",
150
+ ruleset: { type: "unsafeAllowAll" },
151
+ meta: null,
152
+ ...createdNowUnique(),
153
+ });
133
154
 
134
- const timeBeforeEdit = Date.now();
155
+ const content = coValue.getCurrentContent();
156
+
157
+ if (content.type !== "comap") {
158
+ throw new Error("Expected map");
159
+ }
160
+
161
+ expect(content.type).toEqual("comap");
162
+
163
+ content.edit((editable) => {
164
+ expect(editable.getLastTxID("hello")).toEqual(undefined);
165
+ editable.set("hello", "A", "trusting");
166
+ const sessionID = editable.getLastTxID("hello")?.sessionID;
167
+ expect(sessionID && accountOrAgentIDfromSessionID(sessionID)).toEqual(
168
+ node.account.id
169
+ );
170
+ expect(editable.getLastTxID("hello")?.txIndex).toEqual(0);
171
+ editable.set("hello", "B", "trusting");
172
+ expect(editable.getLastTxID("hello")?.txIndex).toEqual(1);
173
+ editable.set("hello", "C", "trusting");
174
+ expect(editable.getLastTxID("hello")?.txIndex).toEqual(2);
175
+ });
176
+ });
177
+
178
+ test("Empty CoList works", () => {
179
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID());
135
180
 
136
- await new Promise((resolve) => setTimeout(resolve, 10));
181
+ const coValue = node.createCoValue({
182
+ type: "colist",
183
+ ruleset: { type: "unsafeAllowAll" },
184
+ meta: null,
185
+ ...createdNowUnique(),
186
+ });
137
187
 
138
- let map = group.createMap();
188
+ const content = coValue.getCurrentContent();
139
189
 
140
- let mapAfterEdit = map.edit((map) => {
141
- map.set("hello", "world");
190
+ if (content.type !== "colist") {
191
+ throw new Error("Expected list");
192
+ }
193
+
194
+ expect(content.type).toEqual("colist");
195
+ expect(content.toJSON()).toEqual([]);
196
+ });
197
+
198
+ test("Can append, prepend and delete items to CoList", () => {
199
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID());
200
+
201
+ const coValue = node.createCoValue({
202
+ type: "colist",
203
+ ruleset: { type: "unsafeAllowAll" },
204
+ meta: null,
205
+ ...createdNowUnique(),
142
206
  });
143
207
 
144
- const listener = jest.fn().mockImplementation();
208
+ const content = coValue.getCurrentContent();
209
+
210
+ if (content.type !== "colist") {
211
+ throw new Error("Expected list");
212
+ }
213
+
214
+ expect(content.type).toEqual("colist");
215
+
216
+ content.edit((editable) => {
217
+ editable.append(0, "hello", "trusting");
218
+ expect(editable.toJSON()).toEqual(["hello"]);
219
+ editable.append(0, "world", "trusting");
220
+ expect(editable.toJSON()).toEqual(["hello", "world"]);
221
+ editable.prepend(1, "beautiful", "trusting");
222
+ expect(editable.toJSON()).toEqual(["hello", "beautiful", "world"]);
223
+ editable.prepend(3, "hooray", "trusting");
224
+ expect(editable.toJSON()).toEqual([
225
+ "hello",
226
+ "beautiful",
227
+ "world",
228
+ "hooray",
229
+ ]);
230
+ editable.delete(2, "trusting");
231
+ expect(editable.toJSON()).toEqual(["hello", "beautiful", "hooray"]);
232
+ });
233
+ });
145
234
 
146
- map.subscribe(listener);
235
+ test("Push is equivalent to append after last item", () => {
236
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID());
147
237
 
148
- expect(listener.mock.calls[0][0].get("hello")).toBe("world");
238
+ const coValue = node.createCoValue({
239
+ type: "colist",
240
+ ruleset: { type: "unsafeAllowAll" },
241
+ meta: null,
242
+ ...createdNowUnique(),
243
+ });
149
244
 
150
- const resignationThatWeJustLearnedAbout = {
151
- privacy: "trusting",
152
- madeAt: timeBeforeEdit,
153
- changes: [
154
- {
155
- op: "set",
156
- key: account.id,
157
- value: "revoked"
158
- } satisfies MapOpPayload<typeof account.id, Role>
159
- ]
160
- } satisfies Transaction;
245
+ const content = coValue.getCurrentContent();
246
+
247
+ if (content.type !== "colist") {
248
+ throw new Error("Expected list");
249
+ }
250
+
251
+ expect(content.type).toEqual("colist");
252
+
253
+ content.edit((editable) => {
254
+ editable.append(0, "hello", "trusting");
255
+ expect(editable.toJSON()).toEqual(["hello"]);
256
+ editable.push("world", "trusting");
257
+ expect(editable.toJSON()).toEqual(["hello", "world"]);
258
+ editable.push("hooray", "trusting");
259
+ expect(editable.toJSON()).toEqual(["hello", "world", "hooray"]);
260
+ });
261
+ });
262
+
263
+ test("Can push into empty list", () => {
264
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID());
265
+
266
+ const coValue = node.createCoValue({
267
+ type: "colist",
268
+ ruleset: { type: "unsafeAllowAll" },
269
+ meta: null,
270
+ ...createdNowUnique(),
271
+ });
272
+
273
+ const content = coValue.getCurrentContent();
274
+
275
+ if (content.type !== "colist") {
276
+ throw new Error("Expected list");
277
+ }
278
+
279
+ expect(content.type).toEqual("colist");
280
+
281
+ content.edit((editable) => {
282
+ editable.push("hello", "trusting");
283
+ expect(editable.toJSON()).toEqual(["hello"]);
284
+ });
285
+ });
286
+
287
+ test("Empty CoStream works", () => {
288
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID());
289
+
290
+ const coValue = node.createCoValue({
291
+ type: "costream",
292
+ ruleset: { type: "unsafeAllowAll" },
293
+ meta: null,
294
+ ...createdNowUnique(),
295
+ });
296
+
297
+ const content = coValue.getCurrentContent();
161
298
 
162
- const { expectedNewHash } = group.groupMap.coValue.expectedNewHashAfter(sessionID, [
163
- resignationThatWeJustLearnedAbout,
164
- ]);
299
+ if (content.type !== "costream") {
300
+ throw new Error("Expected stream");
301
+ }
165
302
 
166
- const signature = sign(
167
- node.account.currentSignerSecret(),
168
- expectedNewHash
169
- );
303
+ expect(content.type).toEqual("costream");
304
+ expect(content.toJSON()).toEqual({});
305
+ expect(content.getSingleStream()).toEqual(undefined);
306
+ });
170
307
 
171
- expect(map.coValue.getValidSortedTransactions().length).toBe(1);
308
+ test("Can push into CoStream", () => {
309
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID());
172
310
 
173
- const manuallyAdddedTxSuccess = group.groupMap.coValue.tryAddTransactions(node.currentSessionID, [resignationThatWeJustLearnedAbout], expectedNewHash, signature);
311
+ const coValue = node.createCoValue({
312
+ type: "costream",
313
+ ruleset: { type: "unsafeAllowAll" },
314
+ meta: null,
315
+ ...createdNowUnique(),
316
+ });
174
317
 
175
- expect(manuallyAdddedTxSuccess).toBe(true);
318
+ const content = coValue.getCurrentContent();
319
+
320
+ if (content.type !== "costream") {
321
+ throw new Error("Expected stream");
322
+ }
323
+
324
+ content.edit((editable) => {
325
+ editable.push({ hello: "world" }, "trusting");
326
+ expect(editable.toJSON()).toEqual({
327
+ [node.currentSessionID]: [{ hello: "world" }],
328
+ });
329
+ editable.push({ foo: "bar" }, "trusting");
330
+ expect(editable.toJSON()).toEqual({
331
+ [node.currentSessionID]: [{ hello: "world" }, { foo: "bar" }],
332
+ });
333
+ expect(editable.getSingleStream()).toEqual([
334
+ { hello: "world" },
335
+ { foo: "bar" },
336
+ ]);
337
+ });
338
+ });
339
+
340
+ test("Empty BinaryCoStream works", () => {
341
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID());
176
342
 
177
- expect(listener.mock.calls.length).toBe(2);
178
- expect(listener.mock.calls[1][0].get("hello")).toBe(undefined);
343
+ const coValue = node.createCoValue({
344
+ type: "costream",
345
+ ruleset: { type: "unsafeAllowAll" },
346
+ meta: { type: "binary" },
347
+ ...createdNowUnique(),
348
+ });
179
349
 
180
- expect(map.coValue.getValidSortedTransactions().length).toBe(0);
350
+ const content = coValue.getCurrentContent();
351
+
352
+ if (content.type !== "costream" || content.meta?.type !== "binary" || !(content instanceof BinaryCoStream)) {
353
+ throw new Error("Expected binary stream");
354
+ }
355
+
356
+ expect(content.type).toEqual("costream");
357
+ expect(content.meta.type).toEqual("binary");
358
+ expect(content.toJSON()).toEqual({});
359
+ expect(content.getBinaryChunks()).toEqual(undefined);
360
+ });
361
+
362
+ test("Can push into BinaryCoStream", () => {
363
+ const node = new LocalNode(...randomAnonymousAccountAndSessionID());
364
+
365
+ const coValue = node.createCoValue({
366
+ type: "costream",
367
+ ruleset: { type: "unsafeAllowAll" },
368
+ meta: { type: "binary" },
369
+ ...createdNowUnique(),
370
+ });
371
+
372
+ const content = coValue.getCurrentContent();
373
+
374
+ if (content.type !== "costream" || content.meta?.type !== "binary" || !(content instanceof BinaryCoStream)) {
375
+ throw new Error("Expected binary stream");
376
+ }
377
+
378
+ content.edit((editable) => {
379
+ editable.startBinaryStream({mimeType: "text/plain", fileName: "test.txt"}, "trusting");
380
+ expect(editable.getBinaryChunks()).toEqual({
381
+ mimeType: "text/plain",
382
+ fileName: "test.txt",
383
+ chunks: [],
384
+ finished: false,
385
+ });
386
+ });
181
387
  });