jazz-tools 0.7.0-alpha.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }