jazz-tools 0.7.0-alpha.9 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. package/.eslintrc.cjs +3 -10
  2. package/.prettierrc.js +9 -0
  3. package/.turbo/turbo-build.log +14 -15
  4. package/.turbo/turbo-lint.log +4 -0
  5. package/.turbo/turbo-test.log +140 -0
  6. package/CHANGELOG.md +331 -27
  7. package/LICENSE.txt +1 -1
  8. package/README.md +10 -2
  9. package/dist/coValues/account.js +86 -41
  10. package/dist/coValues/account.js.map +1 -1
  11. package/dist/coValues/coList.js +75 -48
  12. package/dist/coValues/coList.js.map +1 -1
  13. package/dist/coValues/coMap.js +167 -44
  14. package/dist/coValues/coMap.js.map +1 -1
  15. package/dist/coValues/coStream.js +192 -35
  16. package/dist/coValues/coStream.js.map +1 -1
  17. package/dist/coValues/deepLoading.js +60 -0
  18. package/dist/coValues/deepLoading.js.map +1 -0
  19. package/dist/coValues/extensions/imageDef.js +10 -7
  20. package/dist/coValues/extensions/imageDef.js.map +1 -1
  21. package/dist/coValues/group.js +73 -13
  22. package/dist/coValues/group.js.map +1 -1
  23. package/dist/coValues/interfaces.js +58 -35
  24. package/dist/coValues/interfaces.js.map +1 -1
  25. package/dist/implementation/devtoolsFormatters.js +114 -0
  26. package/dist/implementation/devtoolsFormatters.js.map +1 -0
  27. package/dist/implementation/refs.js +58 -18
  28. package/dist/implementation/refs.js.map +1 -1
  29. package/dist/implementation/schema.js +58 -0
  30. package/dist/implementation/schema.js.map +1 -0
  31. package/dist/implementation/subscriptionScope.js +19 -1
  32. package/dist/implementation/subscriptionScope.js.map +1 -1
  33. package/dist/implementation/symbols.js +5 -0
  34. package/dist/implementation/symbols.js.map +1 -0
  35. package/dist/index.js +4 -5
  36. package/dist/index.js.map +1 -1
  37. package/dist/internal.js +5 -2
  38. package/dist/internal.js.map +1 -1
  39. package/dist/tests/coList.test.js +51 -48
  40. package/dist/tests/coList.test.js.map +1 -1
  41. package/dist/tests/coMap.test.js +131 -74
  42. package/dist/tests/coMap.test.js.map +1 -1
  43. package/dist/tests/coStream.test.js +56 -41
  44. package/dist/tests/coStream.test.js.map +1 -1
  45. package/dist/tests/deepLoading.test.js +188 -0
  46. package/dist/tests/deepLoading.test.js.map +1 -0
  47. package/dist/tests/groupsAndAccounts.test.js +83 -0
  48. package/dist/tests/groupsAndAccounts.test.js.map +1 -0
  49. package/package.json +17 -9
  50. package/src/coValues/account.ts +186 -128
  51. package/src/coValues/coList.ts +156 -107
  52. package/src/coValues/coMap.ts +272 -148
  53. package/src/coValues/coStream.ts +388 -87
  54. package/src/coValues/deepLoading.ts +229 -0
  55. package/src/coValues/extensions/imageDef.ts +17 -13
  56. package/src/coValues/group.ts +166 -59
  57. package/src/coValues/interfaces.ts +189 -160
  58. package/src/implementation/devtoolsFormatters.ts +110 -0
  59. package/src/implementation/inspect.ts +1 -1
  60. package/src/implementation/refs.ts +80 -28
  61. package/src/implementation/schema.ts +141 -0
  62. package/src/implementation/subscriptionScope.ts +48 -12
  63. package/src/implementation/symbols.ts +11 -0
  64. package/src/index.ts +19 -8
  65. package/src/internal.ts +7 -3
  66. package/src/tests/coList.test.ts +77 -62
  67. package/src/tests/coMap.test.ts +201 -114
  68. package/src/tests/coStream.test.ts +113 -84
  69. package/src/tests/deepLoading.test.ts +304 -0
  70. package/src/tests/groupsAndAccounts.test.ts +91 -0
  71. package/dist/implementation/encoding.js +0 -26
  72. package/dist/implementation/encoding.js.map +0 -1
  73. package/src/implementation/encoding.ts +0 -105
@@ -8,16 +8,20 @@ import type {
8
8
  RawCoStream,
9
9
  SessionID,
10
10
  } from "cojson";
11
- import { cojsonInternals } from "cojson";
11
+ import { MAX_RECOMMENDED_TX_SIZE, cojsonInternals } from "cojson";
12
12
  import type {
13
13
  CoValue,
14
- ValidItem,
15
- Encoding,
16
- EncodingFor,
14
+ Schema,
15
+ SchemaFor,
17
16
  Group,
18
17
  ID,
19
- Me,
20
- IsVal,
18
+ IfCo,
19
+ UnCo,
20
+ AccountCtx,
21
+ CoValueClass,
22
+ DeeplyLoaded,
23
+ DepthsIn,
24
+ UnavailableError,
21
25
  } from "../internal.js";
22
26
  import {
23
27
  ItemsSym,
@@ -25,46 +29,56 @@ import {
25
29
  CoValueBase,
26
30
  Ref,
27
31
  inspect,
28
- val,
32
+ co,
29
33
  InitValues,
30
34
  SchemaInit,
35
+ isRefEncoded,
36
+ loadCoValue,
37
+ loadCoValueEf,
38
+ subscribeToCoValue,
39
+ subscribeToCoValueEf,
40
+ ensureCoValueLoaded,
41
+ subscribeToExistingCoValue,
31
42
  } from "../internal.js";
32
- import { Schema } from "@effect/schema";
43
+ import { encodeSync, decodeSync } from "@effect/schema/Schema";
44
+ import { Effect, Stream } from "effect";
33
45
 
34
- export type CoStreamEntry<Item> = {
46
+ export type CoStreamEntry<Item> = SingleCoStreamEntry<Item> & {
47
+ all: IterableIterator<SingleCoStreamEntry<Item>>;
48
+ };
49
+
50
+ export type SingleCoStreamEntry<Item> = {
35
51
  value: NonNullable<Item> extends CoValue ? NonNullable<Item> | null : Item;
36
- ref?: NonNullable<Item> extends CoValue ? Ref<NonNullable<Item>> : never;
37
- by?: Account;
52
+ ref: NonNullable<Item> extends CoValue ? Ref<NonNullable<Item>> : never;
53
+ by?: Account | null;
38
54
  madeAt: Date;
39
55
  tx: CojsonInternalTypes.TransactionID;
40
56
  };
41
57
 
42
- export class CoStream<Item extends ValidItem<Item, "CoStream"> = any>
43
- extends CoValueBase
44
- implements CoValue<"CoStream", RawCoStream>
45
- {
46
- static Of<Item extends ValidItem<Item, "CoStream"> = any>(
47
- item: IsVal<Item, Item>
48
- ): typeof CoStream<Item> {
58
+ /** @category CoValues */
59
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
+ export class CoStream<Item = any> extends CoValueBase implements CoValue {
61
+ static Of<Item>(item: IfCo<Item, Item>): typeof CoStream<Item> {
49
62
  return class CoStreamOf extends CoStream<Item> {
50
- [val.items] = item;
63
+ [co.items] = item;
51
64
  };
52
65
  }
53
66
 
54
- id!: ID<this>;
55
- _type!: "CoStream";
67
+ declare id: ID<this>;
68
+ declare _type: "CoStream";
56
69
  static {
57
70
  this.prototype._type = "CoStream";
58
71
  }
59
- _raw!: RawCoStream;
72
+ declare _raw: RawCoStream;
60
73
 
61
74
  /** @internal This is only a marker type and doesn't exist at runtime */
62
75
  [ItemsSym]!: Item;
63
- static _encoding: any;
64
- get _encoding(): {
65
- [ItemsSym]: EncodingFor<Item>;
76
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
77
+ static _schema: any;
78
+ get _schema(): {
79
+ [ItemsSym]: SchemaFor<Item>;
66
80
  } {
67
- return (this.constructor as typeof CoStream)._encoding;
81
+ return (this.constructor as typeof CoStream)._schema;
68
82
  }
69
83
 
70
84
  [key: ID<Account>]: CoStreamEntry<Item>;
@@ -76,19 +90,16 @@ export class CoStream<Item extends ValidItem<Item, "CoStream"> = any>
76
90
  [key: SessionID]: CoStreamEntry<Item>;
77
91
  };
78
92
  get inCurrentSession(): CoStreamEntry<Item> | undefined {
79
- return this.perSession[this._loadedAs.sessionID];
93
+ return this.perSession[this._loadedAs.sessionID!];
80
94
  }
81
95
 
82
- [InitValues]?: {
83
- init?: Item[];
84
- owner: Account | Group;
85
- };
96
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
97
+ [InitValues]?: any;
86
98
 
87
- constructor(_init: undefined, options: { fromRaw: RawCoStream });
88
- constructor(init: Item[], options: { owner: Account | Group });
89
99
  constructor(
90
- init: Item[] | undefined,
91
- options: { owner: Account | Group } | { fromRaw: RawCoStream }
100
+ options:
101
+ | { init: Item[]; owner: Account | Group }
102
+ | { fromRaw: RawCoStream },
92
103
  ) {
93
104
  super();
94
105
 
@@ -102,7 +113,7 @@ export class CoStream<Item extends ValidItem<Item, "CoStream"> = any>
102
113
  });
103
114
  } else {
104
115
  this[InitValues] = {
105
- init,
116
+ init: options.init,
106
117
  owner: options.owner,
107
118
  };
108
119
  }
@@ -110,6 +121,14 @@ export class CoStream<Item extends ValidItem<Item, "CoStream"> = any>
110
121
  return new Proxy(this, CoStreamProxyHandler as ProxyHandler<this>);
111
122
  }
112
123
 
124
+ static create<S extends CoStream>(
125
+ this: CoValueClass<S>,
126
+ init: S extends CoStream<infer Item> ? UnCo<Item>[] : never,
127
+ options: { owner: Account | Group },
128
+ ) {
129
+ return new this({ init, owner: options.owner });
130
+ }
131
+
113
132
  push(...items: Item[]) {
114
133
  for (const item of items) {
115
134
  this.pushItem(item);
@@ -117,24 +136,24 @@ export class CoStream<Item extends ValidItem<Item, "CoStream"> = any>
117
136
  }
118
137
 
119
138
  private pushItem(item: Item) {
120
- const itemDescriptor = this._encoding[ItemsSym] as Encoding;
139
+ const itemDescriptor = this._schema[ItemsSym] as Schema;
121
140
 
122
141
  if (itemDescriptor === "json") {
123
142
  this._raw.push(item as JsonValue);
124
143
  } else if ("encoded" in itemDescriptor) {
125
- this._raw.push(Schema.encodeSync(itemDescriptor.encoded)(item));
126
- } else if ("ref" in itemDescriptor) {
144
+ this._raw.push(encodeSync(itemDescriptor.encoded)(item));
145
+ } else if (isRefEncoded(itemDescriptor)) {
127
146
  this._raw.push((item as unknown as CoValue).id);
128
147
  }
129
148
  }
130
149
 
131
150
  toJSON() {
132
- const itemDescriptor = this._encoding[ItemsSym] as Encoding;
151
+ const itemDescriptor = this._schema[ItemsSym] as Schema;
133
152
  const mapper =
134
153
  itemDescriptor === "json"
135
154
  ? (v: unknown) => v
136
155
  : "encoded" in itemDescriptor
137
- ? Schema.encodeSync(itemDescriptor.encoded)
156
+ ? encodeSync(itemDescriptor.encoded)
138
157
  : (v: unknown) => v && (v as CoValue).id;
139
158
 
140
159
  return {
@@ -144,13 +163,13 @@ export class CoStream<Item extends ValidItem<Item, "CoStream"> = any>
144
163
  Object.entries(this).map(([account, entry]) => [
145
164
  account,
146
165
  mapper(entry.value),
147
- ])
166
+ ]),
148
167
  ),
149
168
  in: Object.fromEntries(
150
169
  Object.entries(this.perSession).map(([session, entry]) => [
151
170
  session,
152
171
  mapper(entry.value),
153
- ])
172
+ ]),
154
173
  ),
155
174
  };
156
175
  }
@@ -159,12 +178,69 @@ export class CoStream<Item extends ValidItem<Item, "CoStream"> = any>
159
178
  return this.toJSON();
160
179
  }
161
180
 
162
- static encoding<V extends CoStream>(
181
+ static schema<V extends CoStream>(
182
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
163
183
  this: { new (...args: any): V } & typeof CoStream,
164
- def: { [ItemsSym]: V["_encoding"][ItemsSym] }
184
+ def: { [ItemsSym]: V["_schema"][ItemsSym] },
165
185
  ) {
166
- this._encoding ||= {};
167
- Object.assign(this._encoding, def);
186
+ this._schema ||= {};
187
+ Object.assign(this._schema, def);
188
+ }
189
+
190
+ /** @category Subscription & Loading */
191
+ static load<S extends CoStream, Depth>(
192
+ this: CoValueClass<S>,
193
+ id: ID<S>,
194
+ as: Account,
195
+ depth: Depth & DepthsIn<S>,
196
+ ): Promise<DeeplyLoaded<S, Depth> | undefined> {
197
+ return loadCoValue(this, id, as, depth);
198
+ }
199
+
200
+ /** @category Subscription & Loading */
201
+ static loadEf<S extends CoStream, Depth>(
202
+ this: CoValueClass<S>,
203
+ id: ID<S>,
204
+ depth: Depth & DepthsIn<S>,
205
+ ): Effect.Effect<DeeplyLoaded<S, Depth>, UnavailableError, AccountCtx> {
206
+ return loadCoValueEf<S, Depth>(this, id, depth);
207
+ }
208
+
209
+ /** @category Subscription & Loading */
210
+ static subscribe<S extends CoStream, Depth>(
211
+ this: CoValueClass<S>,
212
+ id: ID<S>,
213
+ as: Account,
214
+ depth: Depth & DepthsIn<S>,
215
+ listener: (value: DeeplyLoaded<S, Depth>) => void,
216
+ ): () => void {
217
+ return subscribeToCoValue<S, Depth>(this, id, as, depth, listener);
218
+ }
219
+
220
+ /** @category Subscription & Loading */
221
+ static subscribeEf<S extends CoStream, Depth>(
222
+ this: CoValueClass<S>,
223
+ id: ID<S>,
224
+ depth: Depth & DepthsIn<S>,
225
+ ): Stream.Stream<DeeplyLoaded<S, Depth>, UnavailableError, AccountCtx> {
226
+ return subscribeToCoValueEf<S, Depth>(this, id, depth);
227
+ }
228
+
229
+ /** @category Subscription & Loading */
230
+ ensureLoaded<S extends CoStream, Depth>(
231
+ this: S,
232
+ depth: Depth & DepthsIn<S>,
233
+ ): Promise<DeeplyLoaded<S, Depth> | undefined> {
234
+ return ensureCoValueLoaded(this, depth);
235
+ }
236
+
237
+ /** @category Subscription & Loading */
238
+ subscribe<S extends CoStream, Depth>(
239
+ this: S,
240
+ depth: Depth & DepthsIn<S>,
241
+ listener: (value: DeeplyLoaded<S, Depth>) => void,
242
+ ): () => void {
243
+ return subscribeToExistingCoValue(this, depth, listener);
168
244
  }
169
245
  }
170
246
 
@@ -176,38 +252,64 @@ function entryFromRawEntry<Item>(
176
252
  at: Date;
177
253
  value: JsonValue;
178
254
  },
179
- loadedAs: Account & Me,
255
+ loadedAs: Account,
180
256
  accountID: ID<Account> | undefined,
181
- itemField: Encoding
182
- ) {
257
+ itemField: Schema,
258
+ ): Omit<CoStreamEntry<Item>, "all"> {
183
259
  return {
184
- get value(): Item | undefined {
260
+ get value(): NonNullable<Item> extends CoValue
261
+ ? (CoValue & Item) | null
262
+ : Item {
185
263
  if (itemField === "json") {
186
- return rawEntry.value as Item;
264
+ return rawEntry.value as NonNullable<Item> extends CoValue
265
+ ? (CoValue & Item) | null
266
+ : Item;
187
267
  } else if ("encoded" in itemField) {
188
- return Schema.decodeSync(itemField.encoded)(rawEntry.value);
189
- } else if ("ref" in itemField) {
190
- return this.ref?.accessFrom(accessFrom) as Item;
268
+ return decodeSync(itemField.encoded)(rawEntry.value);
269
+ } else if (isRefEncoded(itemField)) {
270
+ return this.ref?.accessFrom(
271
+ accessFrom,
272
+ rawEntry.by +
273
+ rawEntry.tx.sessionID +
274
+ rawEntry.tx.txIndex +
275
+ ".value",
276
+ ) as NonNullable<Item> extends CoValue
277
+ ? (CoValue & Item) | null
278
+ : Item;
279
+ } else {
280
+ throw new Error("Invalid item field schema");
191
281
  }
192
282
  },
193
- get ref() {
194
- if (itemField !== "json" && "ref" in itemField) {
283
+ get ref(): NonNullable<Item> extends CoValue
284
+ ? Ref<NonNullable<Item>>
285
+ : never {
286
+ if (itemField !== "json" && isRefEncoded(itemField)) {
195
287
  const rawId = rawEntry.value;
196
288
  return new Ref(
197
289
  rawId as unknown as ID<CoValue>,
198
290
  loadedAs,
199
- itemField
200
- );
291
+ itemField,
292
+ ) as NonNullable<Item> extends CoValue
293
+ ? Ref<NonNullable<Item>>
294
+ : never;
295
+ } else {
296
+ return undefined as never;
201
297
  }
202
298
  },
203
299
  get by() {
204
300
  return (
205
301
  accountID &&
206
- new Ref(
302
+ new Ref<Account>(
207
303
  accountID as unknown as ID<Account>,
208
304
  loadedAs,
209
- {ref: () => Account}
210
- )?.accessFrom(accessFrom)
305
+ { ref: Account, optional: false },
306
+ )?.accessFrom(
307
+ accessFrom,
308
+ rawEntry.by +
309
+ rawEntry.tx.sessionID +
310
+ rawEntry.tx.txIndex +
311
+ ".by",
312
+ )
211
313
  );
212
314
  },
213
315
  madeAt: rawEntry.at,
@@ -242,15 +344,40 @@ export const CoStreamProxyHandler: ProxyHandler<CoStream> = {
242
344
  const rawEntry = target._raw.lastItemBy(key as AccountID);
243
345
 
244
346
  if (!rawEntry) return;
245
- return entryFromRawEntry(
347
+ const entry = entryFromRawEntry(
246
348
  receiver,
247
349
  rawEntry,
248
350
  target._loadedAs,
249
351
  key as unknown as ID<Account>,
250
- target._encoding[ItemsSym]
352
+ target._schema[ItemsSym],
251
353
  );
354
+
355
+ Object.defineProperty(entry, "all", {
356
+ get: () => {
357
+ const allRawEntries = target._raw.itemsBy(key as AccountID);
358
+ return (function* () {
359
+ while (true) {
360
+ const rawEntry = allRawEntries.next();
361
+ if (rawEntry.done) return;
362
+ yield entryFromRawEntry(
363
+ receiver,
364
+ rawEntry.value,
365
+ target._loadedAs,
366
+ key as unknown as ID<Account>,
367
+ target._schema[ItemsSym],
368
+ );
369
+ }
370
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
371
+ })() satisfies IterableIterator<SingleCoStreamEntry<any>>;
372
+ },
373
+ });
374
+
375
+ return entry;
252
376
  } else if (key === "perSession") {
253
- return new Proxy(receiver, CoStreamPerSessionProxyHandler);
377
+ return new Proxy(
378
+ {},
379
+ CoStreamPerSessionProxyHandler(target, receiver),
380
+ );
254
381
  } else {
255
382
  return Reflect.get(target, key, receiver);
256
383
  }
@@ -261,8 +388,8 @@ export const CoStreamProxyHandler: ProxyHandler<CoStream> = {
261
388
  typeof value === "object" &&
262
389
  SchemaInit in value
263
390
  ) {
264
- (target.constructor as typeof CoStream)._encoding ||= {};
265
- (target.constructor as typeof CoStream)._encoding[ItemsSym] =
391
+ (target.constructor as typeof CoStream)._schema ||= {};
392
+ (target.constructor as typeof CoStream)._schema[ItemsSym] =
266
393
  value[SchemaInit];
267
394
  init(target);
268
395
  return true;
@@ -277,8 +404,8 @@ export const CoStreamProxyHandler: ProxyHandler<CoStream> = {
277
404
  typeof descriptor.value === "object" &&
278
405
  SchemaInit in descriptor.value
279
406
  ) {
280
- (target.constructor as typeof CoStream)._encoding ||= {};
281
- (target.constructor as typeof CoStream)._encoding[ItemsSym] =
407
+ (target.constructor as typeof CoStream)._schema ||= {};
408
+ (target.constructor as typeof CoStream)._schema[ItemsSym] =
282
409
  descriptor.value[SchemaInit];
283
410
  init(target);
284
411
  return true;
@@ -308,46 +435,85 @@ export const CoStreamProxyHandler: ProxyHandler<CoStream> = {
308
435
  },
309
436
  };
310
437
 
311
- const CoStreamPerSessionProxyHandler: ProxyHandler<CoStream> = {
312
- get(target, key, receiver) {
438
+ const CoStreamPerSessionProxyHandler = (
439
+ innerTarget: CoStream,
440
+ accessFrom: CoStream,
441
+ ): ProxyHandler<Record<string, never>> => ({
442
+ get(_target, key, receiver) {
313
443
  if (typeof key === "string" && key.includes("session")) {
314
444
  const sessionID = key as SessionID;
315
- const rawEntry = target._raw.lastItemIn(sessionID);
445
+ const rawEntry = innerTarget._raw.lastItemIn(sessionID);
316
446
 
317
447
  if (!rawEntry) return;
318
448
  const by = cojsonInternals.accountOrAgentIDfromSessionID(sessionID);
319
- return entryFromRawEntry(
320
- target,
449
+
450
+ const entry = entryFromRawEntry(
451
+ accessFrom,
321
452
  rawEntry,
322
- target._loadedAs,
453
+ innerTarget._loadedAs,
323
454
  cojsonInternals.isAccountID(by)
324
455
  ? (by as unknown as ID<Account>)
325
456
  : undefined,
326
- target._encoding[ItemsSym]
457
+ innerTarget._schema[ItemsSym],
327
458
  );
459
+
460
+ Object.defineProperty(entry, "all", {
461
+ get: () => {
462
+ const allRawEntries = innerTarget._raw.itemsIn(sessionID);
463
+ return (function* () {
464
+ while (true) {
465
+ const rawEntry = allRawEntries.next();
466
+ if (rawEntry.done) return;
467
+ yield entryFromRawEntry(
468
+ accessFrom,
469
+ rawEntry.value,
470
+ innerTarget._loadedAs,
471
+ cojsonInternals.isAccountID(by)
472
+ ? (by as unknown as ID<Account>)
473
+ : undefined,
474
+ innerTarget._schema[ItemsSym],
475
+ );
476
+ }
477
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
478
+ })() satisfies IterableIterator<SingleCoStreamEntry<any>>;
479
+ },
480
+ });
481
+
482
+ return entry;
328
483
  } else {
329
- return Reflect.get(target, key, receiver);
484
+ return Reflect.get(innerTarget, key, receiver);
330
485
  }
331
486
  },
332
- };
487
+ ownKeys() {
488
+ return innerTarget._raw.sessions();
489
+ },
490
+ getOwnPropertyDescriptor(target, key) {
491
+ if (typeof key === "string" && key.startsWith("co_")) {
492
+ return {
493
+ configurable: true,
494
+ enumerable: true,
495
+ writable: false,
496
+ };
497
+ } else {
498
+ return Reflect.getOwnPropertyDescriptor(target, key);
499
+ }
500
+ },
501
+ });
333
502
 
334
- export class BinaryCoStream
335
- extends CoValueBase
336
- implements CoValue<"BinaryCoStream", RawBinaryCoStream>
337
- {
338
- id!: ID<this>;
339
- _type!: "BinaryCoStream";
340
- _raw!: RawBinaryCoStream;
503
+ /** @category CoValues */
504
+ export class BinaryCoStream extends CoValueBase implements CoValue {
505
+ declare id: ID<this>;
506
+ declare _type: "BinaryCoStream";
507
+ declare _raw: RawBinaryCoStream;
341
508
 
342
509
  constructor(
343
- init: [] | undefined,
344
510
  options:
345
511
  | {
346
512
  owner: Account | Group;
347
513
  }
348
514
  | {
349
515
  fromRaw: RawBinaryCoStream;
350
- }
516
+ },
351
517
  ) {
352
518
  super();
353
519
 
@@ -365,10 +531,18 @@ export class BinaryCoStream
365
531
  value: raw.id,
366
532
  enumerable: false,
367
533
  },
534
+ _type: { value: "BinaryCoStream", enumerable: false },
368
535
  _raw: { value: raw, enumerable: false },
369
536
  });
370
537
  }
371
538
 
539
+ static create<S extends BinaryCoStream>(
540
+ this: CoValueClass<S>,
541
+ options: { owner: Account | Group },
542
+ ) {
543
+ return new this(options);
544
+ }
545
+
372
546
  getChunks(options?: {
373
547
  allowUnfinished?: boolean;
374
548
  }):
@@ -389,6 +563,77 @@ export class BinaryCoStream
389
563
  this._raw.endBinaryStream();
390
564
  }
391
565
 
566
+ toBlob(options?: { allowUnfinished?: boolean }): Blob | undefined {
567
+ const chunks = this.getChunks({
568
+ allowUnfinished: options?.allowUnfinished,
569
+ });
570
+
571
+ if (!chunks) {
572
+ return undefined;
573
+ }
574
+
575
+ return new Blob(chunks.chunks, { type: chunks.mimeType });
576
+ }
577
+
578
+ static async loadAsBlob(
579
+ id: ID<BinaryCoStream>,
580
+ as: Account,
581
+ options?: {
582
+ allowUnfinished?: boolean;
583
+ },
584
+ ): Promise<Blob | undefined> {
585
+ const stream = await this.load(id, as, []);
586
+
587
+ return stream?.toBlob({
588
+ allowUnfinished: options?.allowUnfinished,
589
+ });
590
+ }
591
+
592
+ static async createFromBlob(
593
+ blob: Blob | File,
594
+ options: {
595
+ owner: Group | Account;
596
+ onProgress?: (progress: number) => void;
597
+ },
598
+ ): Promise<BinaryCoStream> {
599
+ const stream = this.create({ owner: options.owner });
600
+
601
+ const start = Date.now();
602
+
603
+ const data = new Uint8Array(await blob.arrayBuffer());
604
+ stream.start({
605
+ mimeType: blob.type,
606
+ totalSizeBytes: blob.size,
607
+ fileName: blob instanceof File ? blob.name : undefined,
608
+ });
609
+ const chunkSize = MAX_RECOMMENDED_TX_SIZE;
610
+
611
+ let lastProgressUpdate = Date.now();
612
+
613
+ for (let idx = 0; idx < data.length; idx += chunkSize) {
614
+ stream.push(data.slice(idx, idx + chunkSize));
615
+
616
+ if (Date.now() - lastProgressUpdate > 100) {
617
+ options.onProgress?.(idx / data.length);
618
+ lastProgressUpdate = Date.now();
619
+ }
620
+
621
+ await new Promise((resolve) => setTimeout(resolve, 0));
622
+ }
623
+ stream.end();
624
+ const end = Date.now();
625
+
626
+ console.debug(
627
+ "Finished creating binary stream in",
628
+ (end - start) / 1000,
629
+ "s - Throughput in MB/s",
630
+ (1000 * (blob.size / (end - start))) / (1024 * 1024),
631
+ );
632
+ options.onProgress?.(1);
633
+
634
+ return stream;
635
+ }
636
+
392
637
  toJSON() {
393
638
  return {
394
639
  id: this.id,
@@ -400,4 +645,60 @@ export class BinaryCoStream
400
645
  [inspect]() {
401
646
  return this.toJSON();
402
647
  }
648
+
649
+ /** @category Subscription & Loading */
650
+ static load<B extends BinaryCoStream, Depth>(
651
+ this: CoValueClass<B>,
652
+ id: ID<B>,
653
+ as: Account,
654
+ depth: Depth & DepthsIn<B>,
655
+ ): Promise<DeeplyLoaded<B, Depth> | undefined> {
656
+ return loadCoValue(this, id, as, depth);
657
+ }
658
+
659
+ /** @category Subscription & Loading */
660
+ static loadEf<B extends BinaryCoStream, Depth>(
661
+ this: CoValueClass<B>,
662
+ id: ID<B>,
663
+ depth: Depth & DepthsIn<B>,
664
+ ): Effect.Effect<DeeplyLoaded<B, Depth>, UnavailableError, AccountCtx> {
665
+ return loadCoValueEf<B, Depth>(this, id, depth);
666
+ }
667
+
668
+ /** @category Subscription & Loading */
669
+ static subscribe<B extends BinaryCoStream, Depth>(
670
+ this: CoValueClass<B>,
671
+ id: ID<B>,
672
+ as: Account,
673
+ depth: Depth & DepthsIn<B>,
674
+ listener: (value: DeeplyLoaded<B, Depth>) => void,
675
+ ): () => void {
676
+ return subscribeToCoValue<B, Depth>(this, id, as, depth, listener);
677
+ }
678
+
679
+ /** @category Subscription & Loading */
680
+ static subscribeEf<B extends BinaryCoStream, Depth>(
681
+ this: CoValueClass<B>,
682
+ id: ID<B>,
683
+ depth: Depth & DepthsIn<B>,
684
+ ): Stream.Stream<DeeplyLoaded<B, Depth>, UnavailableError, AccountCtx> {
685
+ return subscribeToCoValueEf<B, Depth>(this, id, depth);
686
+ }
687
+
688
+ /** @category Subscription & Loading */
689
+ ensureLoaded<B extends BinaryCoStream, Depth>(
690
+ this: B,
691
+ depth: Depth & DepthsIn<B>,
692
+ ): Promise<DeeplyLoaded<B, Depth> | undefined> {
693
+ return ensureCoValueLoaded(this, depth);
694
+ }
695
+
696
+ /** @category Subscription & Loading */
697
+ subscribe<B extends BinaryCoStream, Depth>(
698
+ this: B,
699
+ depth: Depth & DepthsIn<B>,
700
+ listener: (value: DeeplyLoaded<B, Depth>) => void,
701
+ ): () => void {
702
+ return subscribeToExistingCoValue(this, depth, listener);
703
+ }
403
704
  }