jazz-tools 0.7.0-alpha.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 (85) hide show
  1. package/.eslintrc.cjs +24 -0
  2. package/.turbo/turbo-build.log +24 -0
  3. package/CHANGELOG.md +42 -0
  4. package/LICENSE.txt +19 -0
  5. package/README.md +3 -0
  6. package/dist/coValueInterfaces.js +8 -0
  7. package/dist/coValueInterfaces.js.map +1 -0
  8. package/dist/coValues/account/account.js +11 -0
  9. package/dist/coValues/account/account.js.map +1 -0
  10. package/dist/coValues/account/accountOf.js +150 -0
  11. package/dist/coValues/account/accountOf.js.map +1 -0
  12. package/dist/coValues/account/migration.js +4 -0
  13. package/dist/coValues/account/migration.js.map +1 -0
  14. package/dist/coValues/coList/coList.js +2 -0
  15. package/dist/coValues/coList/coList.js.map +1 -0
  16. package/dist/coValues/coList/coListOf.js +235 -0
  17. package/dist/coValues/coList/coListOf.js.map +1 -0
  18. package/dist/coValues/coList/internalDocs.js +2 -0
  19. package/dist/coValues/coList/internalDocs.js.map +1 -0
  20. package/dist/coValues/coMap/coMap.js +2 -0
  21. package/dist/coValues/coMap/coMap.js.map +1 -0
  22. package/dist/coValues/coMap/coMapOf.js +262 -0
  23. package/dist/coValues/coMap/coMapOf.js.map +1 -0
  24. package/dist/coValues/coMap/internalDocs.js +2 -0
  25. package/dist/coValues/coMap/internalDocs.js.map +1 -0
  26. package/dist/coValues/coStream/coStream.js +2 -0
  27. package/dist/coValues/coStream/coStream.js.map +1 -0
  28. package/dist/coValues/coStream/coStreamOf.js +244 -0
  29. package/dist/coValues/coStream/coStreamOf.js.map +1 -0
  30. package/dist/coValues/construction.js +34 -0
  31. package/dist/coValues/construction.js.map +1 -0
  32. package/dist/coValues/extensions/imageDef.js +36 -0
  33. package/dist/coValues/extensions/imageDef.js.map +1 -0
  34. package/dist/coValues/group/group.js +2 -0
  35. package/dist/coValues/group/group.js.map +1 -0
  36. package/dist/coValues/group/groupOf.js +109 -0
  37. package/dist/coValues/group/groupOf.js.map +1 -0
  38. package/dist/coValues/resolution.js +66 -0
  39. package/dist/coValues/resolution.js.map +1 -0
  40. package/dist/errors.js +2 -0
  41. package/dist/errors.js.map +1 -0
  42. package/dist/index.js +31 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/refs.js +95 -0
  45. package/dist/refs.js.map +1 -0
  46. package/dist/schemaHelpers.js +14 -0
  47. package/dist/schemaHelpers.js.map +1 -0
  48. package/dist/subscriptionScope.js +81 -0
  49. package/dist/subscriptionScope.js.map +1 -0
  50. package/dist/tests/coList.test.js +207 -0
  51. package/dist/tests/coList.test.js.map +1 -0
  52. package/dist/tests/coMap.test.js +238 -0
  53. package/dist/tests/coMap.test.js.map +1 -0
  54. package/dist/tests/coStream.test.js +263 -0
  55. package/dist/tests/coStream.test.js.map +1 -0
  56. package/dist/tests/types.test.js +33 -0
  57. package/dist/tests/types.test.js.map +1 -0
  58. package/package.json +23 -0
  59. package/src/coValueInterfaces.ts +105 -0
  60. package/src/coValues/account/account.ts +106 -0
  61. package/src/coValues/account/accountOf.ts +284 -0
  62. package/src/coValues/account/migration.ts +12 -0
  63. package/src/coValues/coList/coList.ts +57 -0
  64. package/src/coValues/coList/coListOf.ts +377 -0
  65. package/src/coValues/coList/internalDocs.ts +1 -0
  66. package/src/coValues/coMap/coMap.ts +110 -0
  67. package/src/coValues/coMap/coMapOf.ts +451 -0
  68. package/src/coValues/coMap/internalDocs.ts +1 -0
  69. package/src/coValues/coStream/coStream.ts +63 -0
  70. package/src/coValues/coStream/coStreamOf.ts +404 -0
  71. package/src/coValues/construction.ts +110 -0
  72. package/src/coValues/extensions/imageDef.ts +51 -0
  73. package/src/coValues/group/group.ts +27 -0
  74. package/src/coValues/group/groupOf.ts +183 -0
  75. package/src/coValues/resolution.ts +111 -0
  76. package/src/errors.ts +1 -0
  77. package/src/index.ts +68 -0
  78. package/src/refs.ts +128 -0
  79. package/src/schemaHelpers.ts +72 -0
  80. package/src/subscriptionScope.ts +118 -0
  81. package/src/tests/coList.test.ts +283 -0
  82. package/src/tests/coMap.test.ts +357 -0
  83. package/src/tests/coStream.test.ts +415 -0
  84. package/src/tests/types.test.ts +37 -0
  85. package/tsconfig.json +15 -0
@@ -0,0 +1,415 @@
1
+ import { expect, describe, test, beforeEach, Test } from "vitest";
2
+
3
+ import { webcrypto } from "node:crypto";
4
+ import { connectedPeers } from "cojson/src/streamUtils.js";
5
+ import { newRandomSessionID } from "cojson/src/coValueCore.js";
6
+ import { Effect, Queue } from "effect";
7
+ import { AnyAccount, BinaryCoStream, Co, ID, S, Account, jazzReady } from "..";
8
+ import { Simplify } from "effect/Types";
9
+
10
+ if (!("crypto" in globalThis)) {
11
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
+ (globalThis as any).crypto = webcrypto;
13
+ }
14
+
15
+ beforeEach(async () => {
16
+ await jazzReady;
17
+ });
18
+
19
+ describe("Simple CoStream operations", async () => {
20
+ const me = await Account.create({
21
+ name: "Hermes Puggington",
22
+ });
23
+
24
+ class TestStream extends Co.stream(S.string).as<TestStream>() {}
25
+
26
+ const stream = new TestStream(["milk"], { owner: me });
27
+
28
+ test("Construction", () => {
29
+ expect(stream.by[me.id]?.value).toEqual("milk");
30
+ expect(stream.in[me.sessionID]?.value).toEqual("milk");
31
+ });
32
+
33
+ describe("Mutation", () => {
34
+ test("pushing", () => {
35
+ stream.push("bread");
36
+ expect(stream.by[me.id]?.value).toEqual("bread");
37
+ expect(stream.in[me.sessionID]?.value).toEqual("bread");
38
+
39
+ stream.push("butter");
40
+ expect(stream.by[me.id]?.value).toEqual("butter");
41
+ expect(stream.in[me.sessionID]?.value).toEqual("butter");
42
+ });
43
+ });
44
+ });
45
+
46
+ describe("CoStream resolution", async () => {
47
+ class TwiceNestedStream extends Co.stream(
48
+ S.string
49
+ ).as<TwiceNestedStream>() {
50
+ fancyValueOf(account: ID<AnyAccount>) {
51
+ return "Sir " + this.by[account]?.value;
52
+ }
53
+ }
54
+
55
+ class NestedStream extends Co.stream(
56
+ TwiceNestedStream
57
+ ).as<NestedStream>() {}
58
+
59
+ class TestStream extends Co.stream(NestedStream).as<TestStream>() {}
60
+
61
+ const initNodeAndStream = async () => {
62
+ const me = await Account.create({
63
+ name: "Hermes Puggington",
64
+ });
65
+
66
+ const stream = new TestStream(
67
+ [
68
+ new NestedStream(
69
+ [new TwiceNestedStream(["milk"], { owner: me })],
70
+ { owner: me }
71
+ ),
72
+ ],
73
+ { owner: me }
74
+ );
75
+
76
+ return { me, stream };
77
+ };
78
+
79
+ test("Construction", async () => {
80
+ const { me, stream } = await initNodeAndStream();
81
+ expect(
82
+ stream.by[me.id]?.value?.by[me.id]?.value?.by[me.id]?.value
83
+ ).toEqual("milk");
84
+ });
85
+
86
+ test("Loading and availability", async () => {
87
+ const { me, stream } = await initNodeAndStream();
88
+ const [initialAsPeer, secondPeer] = connectedPeers(
89
+ "initial",
90
+ "second",
91
+ { peer1role: "server", peer2role: "client" }
92
+ );
93
+ me._raw.core.node.syncManager.addPeer(secondPeer);
94
+ const meOnSecondPeer = await Account.become({
95
+ accountID: me.id,
96
+ accountSecret: me._raw.agentSecret,
97
+ peersToLoadFrom: [initialAsPeer],
98
+ sessionID: newRandomSessionID(me.id as any),
99
+ });
100
+
101
+ const loadedStream = await TestStream.load(stream.id, {
102
+ as: meOnSecondPeer,
103
+ });
104
+
105
+ expect(loadedStream?.by[me.id]?.value).toEqual(undefined);
106
+ expect(loadedStream?.by[me.id]?.ref?.id).toEqual(
107
+ stream.by[me.id]?.value?.id
108
+ );
109
+
110
+ const loadedNestedStream = await NestedStream.load(
111
+ stream.by[me.id]!.value!.id,
112
+ { as: meOnSecondPeer }
113
+ );
114
+
115
+ expect(loadedStream?.by[me.id]?.value).toEqual(loadedNestedStream);
116
+ expect(loadedStream?.by[me.id]?.value?.by[me.id]?.value).toEqual(
117
+ undefined
118
+ );
119
+ expect(loadedStream?.by[me.id]?.ref?.value).toEqual(loadedNestedStream);
120
+ expect(loadedStream?.by[me.id]?.value?.by[me.id]?.ref?.id).toEqual(
121
+ stream.by[me.id]?.value?.by[me.id]?.value?.id
122
+ );
123
+
124
+ const loadedTwiceNestedStream = await TwiceNestedStream.load(
125
+ stream.by[me.id]!.value!.by[me.id]!.value!.id,
126
+ { as: meOnSecondPeer }
127
+ );
128
+
129
+ expect(loadedStream?.by[me.id]?.value?.by[me.id]?.value).toEqual(
130
+ loadedTwiceNestedStream
131
+ );
132
+ expect(
133
+ loadedStream?.by[me.id]?.value?.by[me.id]?.value?.fancyValueOf(me.id)
134
+ ).toEqual("Sir milk");
135
+ expect(loadedStream?.by[me.id]?.ref?.value).toEqual(loadedNestedStream);
136
+ expect(loadedStream?.by[me.id]?.value?.by[me.id]?.ref?.value).toEqual(
137
+ loadedTwiceNestedStream
138
+ );
139
+
140
+ const otherNestedStream = new NestedStream(
141
+ [new TwiceNestedStream(["butter"], { owner: meOnSecondPeer })],
142
+ { owner: meOnSecondPeer }
143
+ );
144
+ loadedStream?.push(otherNestedStream);
145
+ expect(loadedStream?.by[me.id]?.value).toEqual(otherNestedStream);
146
+ expect(loadedStream?.by[me.id]?.ref?.value).toEqual(otherNestedStream);
147
+ expect(loadedStream?.by[me.id]?.value?.by[me.id]?.value).toEqual(
148
+ otherNestedStream.by[me.id]?.value
149
+ );
150
+ expect(
151
+ loadedStream?.by[me.id]?.value?.by[me.id]?.value?.fancyValueOf(me.id)
152
+ ).toEqual("Sir butter");
153
+ });
154
+
155
+ test("Subscription & auto-resolution", async () => {
156
+ const { me, stream } = await initNodeAndStream();
157
+
158
+ const [initialAsPeer, secondAsPeer] = connectedPeers(
159
+ "initial",
160
+ "second",
161
+ { peer1role: "server", peer2role: "client" }
162
+ );
163
+
164
+ me._raw.core.node.syncManager.addPeer(secondAsPeer);
165
+
166
+ const meOnSecondPeer = await Account.become({
167
+ accountID: me.id,
168
+ accountSecret: me._raw.agentSecret,
169
+ peersToLoadFrom: [initialAsPeer],
170
+ sessionID: newRandomSessionID(me.id as any),
171
+ });
172
+
173
+ await Effect.runPromise(
174
+ Effect.gen(function* ($) {
175
+ const queue = yield* $(Queue.unbounded<TestStream>());
176
+
177
+ TestStream.subscribe(
178
+ stream.id,
179
+ { as: meOnSecondPeer },
180
+ (subscribedStream) => {
181
+ console.log(
182
+ "subscribedStream.by[me.id]",
183
+ subscribedStream.by[me.id]
184
+ );
185
+ console.log(
186
+ "subscribedStream.by[me.id]?.value?.by[me.id]?.value",
187
+ subscribedStream.by[me.id]?.value?.by[me.id]?.value
188
+ );
189
+ console.log(
190
+ "subscribedStream.by[me.id]?.value?.by[me.id]?.value?.by[me.id]?.value",
191
+ subscribedStream.by[me.id]?.value?.by[me.id]?.value
192
+ ?.by[me.id]?.value
193
+ );
194
+ Effect.runPromise(Queue.offer(queue, subscribedStream));
195
+ }
196
+ );
197
+
198
+ type T = Simplify<TestStream>;
199
+ const te: T = stream;
200
+
201
+ const update1 = yield* $(Queue.take(queue));
202
+ expect(update1.by[me.id]?.value).toEqual(undefined);
203
+
204
+ const update2 = yield* $(Queue.take(queue));
205
+ expect(update2.by[me.id]?.value).toBeDefined();
206
+ expect(
207
+ update2.by[me.id]?.value?.by[me.id]?.value
208
+ ).toBeUndefined();
209
+
210
+ const update3 = yield* $(Queue.take(queue));
211
+ expect(update3.by[me.id]?.value?.by[me.id]?.value).toBeDefined();
212
+ expect(
213
+ update3.by[me.id]?.value?.by[me.id]?.value?.by[me.id]?.value
214
+ ).toBe("milk");
215
+
216
+ update3.by[me.id]!.value!.by[me.id]!.value!.push("bread");
217
+
218
+ const update4 = yield* $(Queue.take(queue));
219
+ expect(
220
+ update4.by[me.id]?.value?.by[me.id]?.value?.by[me.id]?.value
221
+ ).toBe("bread");
222
+
223
+ // When assigning a new nested stream, we get an update
224
+ const newTwiceNested = new TwiceNestedStream(["butter"], {
225
+ owner: meOnSecondPeer,
226
+ });
227
+
228
+ const newNested = new NestedStream([newTwiceNested], {
229
+ owner: meOnSecondPeer,
230
+ });
231
+
232
+ update4.push(newNested);
233
+
234
+ const update5 = yield* $(Queue.take(queue));
235
+ expect(
236
+ update5.by[me.id]?.value?.by[me.id]?.value?.by[me.id]?.value
237
+ ).toBe("butter");
238
+
239
+ // we get updates when the new nested stream changes
240
+ newTwiceNested.push("jam");
241
+ const update6 = yield* $(Queue.take(queue));
242
+ expect(
243
+ update6.by[me.id]?.value?.by[me.id]?.value?.by[me.id]?.value
244
+ ).toBe("jam");
245
+ })
246
+ );
247
+ });
248
+ });
249
+
250
+ describe("Simple BinaryCoStream operations", async () => {
251
+ const me = await Account.create({
252
+ name: "Hermes Puggington",
253
+ });
254
+
255
+ const stream = new Co.binaryStream(undefined, { owner: me });
256
+
257
+ test("Construction", () => {
258
+ expect(stream.getChunks()).toBeUndefined();
259
+ });
260
+
261
+ test("Mutation", () => {
262
+ stream.start({ mimeType: "text/plain" });
263
+ stream.push(new Uint8Array([1, 2, 3]));
264
+ stream.push(new Uint8Array([4, 5, 6]));
265
+ stream.end();
266
+
267
+ const chunks = stream.getChunks();
268
+ expect(chunks?.mimeType).toBe("text/plain");
269
+ expect(chunks?.chunks).toEqual([
270
+ new Uint8Array([1, 2, 3]),
271
+ new Uint8Array([4, 5, 6]),
272
+ ]);
273
+ expect(chunks?.finished).toBe(true);
274
+ });
275
+ });
276
+
277
+ describe("BinaryCoStream loading & Subscription", async () => {
278
+ const initNodeAndStream = async () => {
279
+ const me = await Account.create({
280
+ name: "Hermes Puggington",
281
+ });
282
+
283
+ const stream = new Co.binaryStream(undefined, { owner: me });
284
+
285
+ stream.start({ mimeType: "text/plain" });
286
+ stream.push(new Uint8Array([1, 2, 3]));
287
+ stream.push(new Uint8Array([4, 5, 6]));
288
+ stream.end();
289
+
290
+ return { me, stream };
291
+ };
292
+
293
+ test("Construction", async () => {
294
+ const { me, stream } = await initNodeAndStream();
295
+ expect(stream.getChunks()).toEqual({
296
+ mimeType: "text/plain",
297
+ chunks: [new Uint8Array([1, 2, 3]), new Uint8Array([4, 5, 6])],
298
+ finished: true,
299
+ });
300
+ });
301
+
302
+ test("Loading and availability", async () => {
303
+ const { me, stream } = await initNodeAndStream();
304
+ const [initialAsPeer, secondAsPeer] = connectedPeers(
305
+ "initial",
306
+ "second",
307
+ { peer1role: "server", peer2role: "client" }
308
+ );
309
+ me._raw.core.node.syncManager.addPeer(secondAsPeer);
310
+ const meOnSecondPeer = await Account.become({
311
+ accountID: me.id,
312
+ accountSecret: me._raw.agentSecret,
313
+ peersToLoadFrom: [initialAsPeer],
314
+ sessionID: newRandomSessionID(me.id as any),
315
+ });
316
+
317
+ const loadedStream = await Co.binaryStream.load(stream.id, {
318
+ as: meOnSecondPeer,
319
+ });
320
+
321
+ expect(loadedStream?.getChunks()).toEqual({
322
+ mimeType: "text/plain",
323
+ chunks: [new Uint8Array([1, 2, 3]), new Uint8Array([4, 5, 6])],
324
+ finished: true,
325
+ });
326
+ });
327
+
328
+ test("Subscription", async () => {
329
+ const { me } = await initNodeAndStream();
330
+
331
+ const stream = new Co.binaryStream(undefined, { owner: me });
332
+
333
+ const [initialAsPeer, secondAsPeer] = connectedPeers(
334
+ "initial",
335
+ "second",
336
+ { peer1role: "server", peer2role: "client" }
337
+ );
338
+
339
+ me._raw.core.node.syncManager.addPeer(secondAsPeer);
340
+
341
+ const meOnSecondPeer = await Account.become({
342
+ accountID: me.id,
343
+ accountSecret: me._raw.agentSecret,
344
+ peersToLoadFrom: [initialAsPeer],
345
+ sessionID: newRandomSessionID(me.id as any),
346
+ });
347
+
348
+ await Effect.runPromise(
349
+ Effect.gen(function* ($) {
350
+ const queue = yield* $(Queue.unbounded<BinaryCoStream>());
351
+
352
+ Co.binaryStream.subscribe(
353
+ stream.id,
354
+ { as: meOnSecondPeer },
355
+ (subscribedStream) => {
356
+ Effect.runPromise(Queue.offer(queue, subscribedStream));
357
+ }
358
+ );
359
+
360
+ const update1 = yield* $(Queue.take(queue));
361
+ expect(update1.getChunks()).toBeUndefined();
362
+
363
+ stream.start({ mimeType: "text/plain" });
364
+
365
+ const update2 = yield* $(Queue.take(queue));
366
+ expect(update2.getChunks({ allowUnfinished: true })).toEqual({
367
+ mimeType: "text/plain",
368
+ fileName: undefined,
369
+ chunks: [],
370
+ totalSizeBytes: undefined,
371
+ finished: false,
372
+ });
373
+
374
+ stream.push(new Uint8Array([1, 2, 3]));
375
+
376
+ const update3 = yield* $(Queue.take(queue));
377
+ expect(update3.getChunks({ allowUnfinished: true })).toEqual({
378
+ mimeType: "text/plain",
379
+ fileName: undefined,
380
+ chunks: [new Uint8Array([1, 2, 3])],
381
+ totalSizeBytes: undefined,
382
+ finished: false,
383
+ });
384
+
385
+ stream.push(new Uint8Array([4, 5, 6]));
386
+
387
+ const update4 = yield* $(Queue.take(queue));
388
+ expect(update4.getChunks({ allowUnfinished: true })).toEqual({
389
+ mimeType: "text/plain",
390
+ fileName: undefined,
391
+ chunks: [
392
+ new Uint8Array([1, 2, 3]),
393
+ new Uint8Array([4, 5, 6]),
394
+ ],
395
+ totalSizeBytes: undefined,
396
+ finished: false,
397
+ });
398
+
399
+ stream.end();
400
+
401
+ const update5 = yield* $(Queue.take(queue));
402
+ expect(update5.getChunks()).toEqual({
403
+ mimeType: "text/plain",
404
+ fileName: undefined,
405
+ chunks: [
406
+ new Uint8Array([1, 2, 3]),
407
+ new Uint8Array([4, 5, 6]),
408
+ ],
409
+ totalSizeBytes: undefined,
410
+ finished: true,
411
+ });
412
+ })
413
+ );
414
+ });
415
+ });
@@ -0,0 +1,37 @@
1
+ import { Account, Co, ImageDefinition, S, jazzReady } from "..";
2
+ import { describe, test, beforeEach, expectTypeOf } from "vitest";
3
+ import { ValueRef } from "../refs";
4
+ import { webcrypto } from "node:crypto";
5
+
6
+ if (!("crypto" in globalThis)) {
7
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
+ (globalThis as any).crypto = webcrypto;
9
+ }
10
+
11
+ beforeEach(async () => {
12
+ await jazzReady;
13
+ });
14
+
15
+ class TestMap extends Co.map({
16
+ name: S.string,
17
+ image: Co.media.imageDef,
18
+ maybeImage: S.optional(Co.media.imageDef),
19
+ }).as<TestMap>() {}
20
+
21
+ describe("CoMap type tests", () => {
22
+ test("Optional field refs work", async () => {
23
+ const me = await Account.create({
24
+ name: "Hermes Puggington",
25
+ });
26
+
27
+ const map = new TestMap({
28
+ name: "Hermes",
29
+ image: new ImageDefinition({
30
+ originalSize: [100, 100],
31
+ placeholderDataURL: "data:image/png;base64,",
32
+ }, { owner: me }),
33
+ }, {owner: me});
34
+ expectTypeOf(map._refs.image).toMatchTypeOf<ValueRef<ImageDefinition>>();
35
+ expectTypeOf(map._refs.maybeImage).toMatchTypeOf<ValueRef<ImageDefinition>>();
36
+ })
37
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["ESNext"],
4
+ "module": "esnext",
5
+ "target": "ES2020",
6
+ "moduleResolution": "bundler",
7
+ "moduleDetection": "force",
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "noUncheckedIndexedAccess": true,
12
+ "esModuleInterop": true
13
+ },
14
+ "include": ["./src/**/*"],
15
+ }