jazz-tools 0.7.0-alpha.4 → 0.7.0-alpha.41

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. package/.eslintrc.cjs +3 -10
  2. package/.prettierrc.js +9 -0
  3. package/.turbo/turbo-build.log +3 -19
  4. package/.turbo/turbo-lint.log +4 -0
  5. package/.turbo/turbo-test.log +140 -0
  6. package/CHANGELOG.md +243 -0
  7. package/README.md +10 -2
  8. package/dist/coValues/account.js +104 -50
  9. package/dist/coValues/account.js.map +1 -1
  10. package/dist/coValues/coList.js +165 -112
  11. package/dist/coValues/coList.js.map +1 -1
  12. package/dist/coValues/coMap.js +243 -163
  13. package/dist/coValues/coMap.js.map +1 -1
  14. package/dist/coValues/coStream.js +256 -73
  15. package/dist/coValues/coStream.js.map +1 -1
  16. package/dist/coValues/deepLoading.js +57 -0
  17. package/dist/coValues/deepLoading.js.map +1 -0
  18. package/dist/coValues/extensions/imageDef.js +14 -8
  19. package/dist/coValues/extensions/imageDef.js.map +1 -1
  20. package/dist/coValues/group.js +49 -38
  21. package/dist/coValues/group.js.map +1 -1
  22. package/dist/coValues/interfaces.js +66 -26
  23. package/dist/coValues/interfaces.js.map +1 -1
  24. package/dist/implementation/devtoolsFormatters.js +114 -0
  25. package/dist/implementation/devtoolsFormatters.js.map +1 -0
  26. package/dist/implementation/refs.js +60 -19
  27. package/dist/implementation/refs.js.map +1 -1
  28. package/dist/implementation/schema.js +44 -1
  29. package/dist/implementation/schema.js.map +1 -1
  30. package/dist/implementation/subscriptionScope.js +19 -1
  31. package/dist/implementation/subscriptionScope.js.map +1 -1
  32. package/dist/implementation/symbols.js +5 -0
  33. package/dist/implementation/symbols.js.map +1 -0
  34. package/dist/index.js +4 -5
  35. package/dist/index.js.map +1 -1
  36. package/dist/internal.js +4 -1
  37. package/dist/internal.js.map +1 -1
  38. package/dist/tests/coList.test.js +51 -52
  39. package/dist/tests/coList.test.js.map +1 -1
  40. package/dist/tests/coMap.test.js +196 -75
  41. package/dist/tests/coMap.test.js.map +1 -1
  42. package/dist/tests/coStream.test.js +95 -85
  43. package/dist/tests/coStream.test.js.map +1 -1
  44. package/dist/tests/deepLoading.test.js +188 -0
  45. package/dist/tests/deepLoading.test.js.map +1 -0
  46. package/dist/tests/groupsAndAccounts.test.js +83 -0
  47. package/dist/tests/groupsAndAccounts.test.js.map +1 -0
  48. package/package.json +17 -9
  49. package/src/coValues/account.ts +183 -152
  50. package/src/coValues/coList.ts +220 -173
  51. package/src/coValues/coMap.ts +322 -312
  52. package/src/coValues/coStream.ts +397 -135
  53. package/src/coValues/deepLoading.ts +215 -0
  54. package/src/coValues/extensions/imageDef.ts +16 -17
  55. package/src/coValues/group.ts +95 -111
  56. package/src/coValues/interfaces.ts +217 -115
  57. package/src/implementation/devtoolsFormatters.ts +110 -0
  58. package/src/implementation/inspect.ts +1 -1
  59. package/src/implementation/refs.ts +91 -38
  60. package/src/implementation/schema.ts +87 -46
  61. package/src/implementation/subscriptionScope.ts +44 -12
  62. package/src/implementation/symbols.ts +11 -0
  63. package/src/index.ts +13 -9
  64. package/src/internal.ts +6 -2
  65. package/src/tests/coList.test.ts +67 -66
  66. package/src/tests/coMap.test.ts +226 -123
  67. package/src/tests/coStream.test.ts +141 -131
  68. package/src/tests/deepLoading.test.ts +301 -0
  69. package/src/tests/groupsAndAccounts.test.ts +91 -0
@@ -8,187 +8,160 @@ 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
- EnsureItemNullable,
15
- FieldDescriptor,
16
- FieldDescriptorFor,
14
+ Schema,
15
+ SchemaFor,
17
16
  Group,
18
17
  ID,
19
- Me,
18
+ IfCo,
19
+ ClassOf,
20
+ UnCo,
20
21
  } from "../internal.js";
21
- import { Account, CoValueBase, ValueRef, inspect } from "../internal.js";
22
- import { Schema } from "@effect/schema";
22
+ import {
23
+ ItemsSym,
24
+ Account,
25
+ CoValueBase,
26
+ Ref,
27
+ inspect,
28
+ co,
29
+ InitValues,
30
+ SchemaInit,
31
+ isRefEncoded,
32
+ } from "../internal.js";
33
+ import { encodeSync, decodeSync } from "@effect/schema/Schema";
34
+
35
+ export type CoStreamEntry<Item> = SingleCoStreamEntry<Item> & {
36
+ all: IterableIterator<SingleCoStreamEntry<Item>>;
37
+ };
23
38
 
24
- export type CoStreamEntry<Item> = {
39
+ export type SingleCoStreamEntry<Item> = {
25
40
  value: NonNullable<Item> extends CoValue ? NonNullable<Item> | null : Item;
26
- ref?: NonNullable<Item> extends CoValue
27
- ? ValueRef<NonNullable<Item>>
28
- : never;
29
- by?: Account;
41
+ ref: NonNullable<Item> extends CoValue ? Ref<NonNullable<Item>> : never;
42
+ by?: Account | null;
30
43
  madeAt: Date;
31
44
  tx: CojsonInternalTypes.TransactionID;
32
45
  };
33
46
 
34
- export class CoStream<Item extends EnsureItemNullable<Item, "Co.Stream"> = any>
47
+ /** @category CoValues */
48
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
+ export class CoStream<Item = any>
35
50
  extends CoValueBase
36
51
  implements CoValue<"CoStream", RawCoStream>
37
52
  {
38
- id!: ID<this>;
39
- _type!: "CoStream";
53
+ static Of<Item>(item: IfCo<Item, Item>): typeof CoStream<Item> {
54
+ return class CoStreamOf extends CoStream<Item> {
55
+ [co.items] = item;
56
+ };
57
+ }
58
+
59
+ declare id: ID<this>;
60
+ declare _type: "CoStream";
40
61
  static {
41
62
  this.prototype._type = "CoStream";
42
63
  }
43
- _raw!: RawCoStream;
64
+ declare _raw: RawCoStream;
44
65
 
45
66
  /** @internal This is only a marker type and doesn't exist at runtime */
46
- _item!: Item;
47
- static _encoding: any;
48
- get _encoding(): {
49
- _item: FieldDescriptorFor<Item>;
67
+ [ItemsSym]!: Item;
68
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
69
+ static _schema: any;
70
+ get _schema(): {
71
+ [ItemsSym]: SchemaFor<Item>;
50
72
  } {
51
- return (this.constructor as typeof CoStream)._encoding;
73
+ return (this.constructor as typeof CoStream)._schema;
52
74
  }
53
75
 
54
- by: {
55
- [key: ID<Account>]: CoStreamEntry<Item>;
56
- } = {};
76
+ [key: ID<Account>]: CoStreamEntry<Item>;
77
+
57
78
  get byMe(): CoStreamEntry<Item> | undefined {
58
- return this.by[this._loadedAs.id];
79
+ return this[this._loadedAs.id];
59
80
  }
60
- in: {
81
+ perSession!: {
61
82
  [key: SessionID]: CoStreamEntry<Item>;
62
- } = {};
83
+ };
63
84
  get inCurrentSession(): CoStreamEntry<Item> | undefined {
64
- return this.in[this._loadedAs.sessionID];
85
+ return this.perSession[this._loadedAs.sessionID!];
65
86
  }
66
87
 
67
- constructor(_init: undefined, options: { fromRaw: RawCoStream });
68
- constructor(init: Item[], options: { owner: Account | Group });
88
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
+ [InitValues]?: any;
90
+
69
91
  constructor(
70
- init: Item[] | undefined,
71
- options: { owner: Account | Group } | { fromRaw: RawCoStream }
92
+ options:
93
+ | { init: Item[]; owner: Account | Group }
94
+ | { fromRaw: RawCoStream },
72
95
  ) {
73
96
  super();
74
97
 
75
- let raw: RawCoStream;
76
-
77
98
  if ("fromRaw" in options) {
78
- raw = options.fromRaw;
99
+ Object.defineProperties(this, {
100
+ id: {
101
+ value: options.fromRaw.id,
102
+ enumerable: false,
103
+ },
104
+ _raw: { value: options.fromRaw, enumerable: false },
105
+ });
79
106
  } else {
80
- const rawOwner = options.owner._raw;
81
-
82
- raw = rawOwner.createStream();
107
+ this[InitValues] = {
108
+ init: options.init,
109
+ owner: options.owner,
110
+ };
83
111
  }
84
112
 
85
- Object.defineProperties(this, {
86
- id: {
87
- value: raw.id,
88
- enumerable: false,
89
- },
90
- _raw: { value: raw, enumerable: false },
91
- });
92
-
93
- if (init !== undefined) {
94
- for (const item of init) {
95
- this.pushItem(item);
96
- }
97
- }
98
-
99
- this.updateEntries();
113
+ return new Proxy(this, CoStreamProxyHandler as ProxyHandler<this>);
100
114
  }
101
115
 
102
- private updateEntries() {
103
- for (const accountID of this._raw.accounts()) {
104
- Object.defineProperty(this.by, accountID, {
105
- get: () => {
106
- const rawEntry = this._raw.lastItemBy(accountID);
107
-
108
- if (!rawEntry) return;
109
- return entryFromRawEntry(
110
- this,
111
- rawEntry,
112
- this._loadedAs,
113
- accountID as unknown as ID<Account>,
114
- this._encoding._item
115
- );
116
- },
117
- configurable: true,
118
- enumerable: true,
119
- });
120
- }
121
-
122
- for (const sessionID of this._raw.sessions()) {
123
- Object.defineProperty(this.in, sessionID, {
124
- get: () => {
125
- const rawEntry = this._raw.lastItemIn(
126
- sessionID as unknown as SessionID
127
- );
128
-
129
- if (!rawEntry) return;
130
- const by =
131
- cojsonInternals.accountOrAgentIDfromSessionID(
132
- sessionID
133
- );
134
- return entryFromRawEntry(
135
- this,
136
- rawEntry,
137
- this._loadedAs,
138
- cojsonInternals.isAccountID(by)
139
- ? (by as unknown as ID<Account>)
140
- : undefined,
141
- this._encoding._item
142
- );
143
- },
144
- configurable: true,
145
- enumerable: true,
146
- });
147
- }
116
+ static create<S extends CoStream>(
117
+ this: ClassOf<S>,
118
+ init: S extends CoStream<infer Item> ? UnCo<Item>[] : never,
119
+ options: { owner: Account | Group },
120
+ ) {
121
+ return new this({ init, owner: options.owner });
148
122
  }
149
123
 
150
124
  push(...items: Item[]) {
151
125
  for (const item of items) {
152
126
  this.pushItem(item);
153
127
  }
154
- this.updateEntries();
155
128
  }
156
129
 
157
130
  private pushItem(item: Item) {
158
- const itemDescriptor = this._encoding._item as FieldDescriptor;
131
+ const itemDescriptor = this._schema[ItemsSym] as Schema;
159
132
 
160
133
  if (itemDescriptor === "json") {
161
134
  this._raw.push(item as JsonValue);
162
135
  } else if ("encoded" in itemDescriptor) {
163
- this._raw.push(Schema.encodeSync(itemDescriptor.encoded)(item));
164
- } else if ("ref" in itemDescriptor) {
136
+ this._raw.push(encodeSync(itemDescriptor.encoded)(item));
137
+ } else if (isRefEncoded(itemDescriptor)) {
165
138
  this._raw.push((item as unknown as CoValue).id);
166
139
  }
167
140
  }
168
141
 
169
142
  toJSON() {
170
- const itemDescriptor = this._encoding._item as FieldDescriptor;
143
+ const itemDescriptor = this._schema[ItemsSym] as Schema;
171
144
  const mapper =
172
145
  itemDescriptor === "json"
173
146
  ? (v: unknown) => v
174
147
  : "encoded" in itemDescriptor
175
- ? Schema.encodeSync(itemDescriptor.encoded)
148
+ ? encodeSync(itemDescriptor.encoded)
176
149
  : (v: unknown) => v && (v as CoValue).id;
177
150
 
178
151
  return {
179
152
  id: this.id,
180
153
  _type: this._type,
181
- by: Object.fromEntries(
182
- Object.entries(this.by).map(([account, entry]) => [
154
+ ...Object.fromEntries(
155
+ Object.entries(this).map(([account, entry]) => [
183
156
  account,
184
157
  mapper(entry.value),
185
- ])
158
+ ]),
186
159
  ),
187
160
  in: Object.fromEntries(
188
- Object.entries(this.in).map(([session, entry]) => [
161
+ Object.entries(this.perSession).map(([session, entry]) => [
189
162
  session,
190
163
  mapper(entry.value),
191
- ])
164
+ ]),
192
165
  ),
193
166
  };
194
167
  }
@@ -197,12 +170,13 @@ export class CoStream<Item extends EnsureItemNullable<Item, "Co.Stream"> = any>
197
170
  return this.toJSON();
198
171
  }
199
172
 
200
- static encoding<V extends CoStream>(
173
+ static schema<V extends CoStream>(
174
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
201
175
  this: { new (...args: any): V } & typeof CoStream,
202
- def: { _item: V["_encoding"]["_item"] }
176
+ def: { [ItemsSym]: V["_schema"][ItemsSym] },
203
177
  ) {
204
- this._encoding ||= {};
205
- Object.assign(this._encoding, def);
178
+ this._schema ||= {};
179
+ Object.assign(this._schema, def);
206
180
  }
207
181
  }
208
182
 
@@ -214,38 +188,64 @@ function entryFromRawEntry<Item>(
214
188
  at: Date;
215
189
  value: JsonValue;
216
190
  },
217
- loadedAs: Account & Me,
191
+ loadedAs: Account,
218
192
  accountID: ID<Account> | undefined,
219
- itemField: FieldDescriptor
220
- ) {
193
+ itemField: Schema,
194
+ ): Omit<CoStreamEntry<Item>, "all"> {
221
195
  return {
222
- get value(): Item | undefined {
196
+ get value(): NonNullable<Item> extends CoValue
197
+ ? (CoValue & Item) | null
198
+ : Item {
223
199
  if (itemField === "json") {
224
- return rawEntry.value as Item;
200
+ return rawEntry.value as NonNullable<Item> extends CoValue
201
+ ? (CoValue & Item) | null
202
+ : Item;
225
203
  } else if ("encoded" in itemField) {
226
- return Schema.decodeSync(itemField.encoded)(rawEntry.value);
227
- } else if ("ref" in itemField) {
228
- return this.ref?.accessFrom(accessFrom) as Item;
204
+ return decodeSync(itemField.encoded)(rawEntry.value);
205
+ } else if (isRefEncoded(itemField)) {
206
+ return this.ref?.accessFrom(
207
+ accessFrom,
208
+ rawEntry.by +
209
+ rawEntry.tx.sessionID +
210
+ rawEntry.tx.txIndex +
211
+ ".value",
212
+ ) as NonNullable<Item> extends CoValue
213
+ ? (CoValue & Item) | null
214
+ : Item;
215
+ } else {
216
+ throw new Error("Invalid item field schema");
229
217
  }
230
218
  },
231
- get ref() {
232
- if (itemField !== "json" && "ref" in itemField) {
219
+ get ref(): NonNullable<Item> extends CoValue
220
+ ? Ref<NonNullable<Item>>
221
+ : never {
222
+ if (itemField !== "json" && isRefEncoded(itemField)) {
233
223
  const rawId = rawEntry.value;
234
- return new ValueRef(
224
+ return new Ref(
235
225
  rawId as unknown as ID<CoValue>,
236
226
  loadedAs,
237
- itemField.ref()
238
- );
227
+ itemField,
228
+ ) as NonNullable<Item> extends CoValue
229
+ ? Ref<NonNullable<Item>>
230
+ : never;
231
+ } else {
232
+ return undefined as never;
239
233
  }
240
234
  },
241
235
  get by() {
242
236
  return (
243
237
  accountID &&
244
- new ValueRef(
238
+ new Ref<Account>(
245
239
  accountID as unknown as ID<Account>,
246
240
  loadedAs,
247
- Account
248
- )?.accessFrom(accessFrom)
241
+ Account,
242
+ )?.accessFrom(
243
+ accessFrom,
244
+ rawEntry.by +
245
+ rawEntry.tx.sessionID +
246
+ rawEntry.tx.txIndex +
247
+ ".by",
248
+ )
249
249
  );
250
250
  },
251
251
  madeAt: rawEntry.at,
@@ -253,23 +253,206 @@ function entryFromRawEntry<Item>(
253
253
  };
254
254
  }
255
255
 
256
+ function init(stream: CoStream) {
257
+ const init = stream[InitValues];
258
+ if (!init) return;
259
+
260
+ const raw = init.owner._raw.createStream();
261
+
262
+ Object.defineProperties(stream, {
263
+ id: {
264
+ value: raw.id,
265
+ enumerable: false,
266
+ },
267
+ _raw: { value: raw, enumerable: false },
268
+ });
269
+
270
+ if (init.init) {
271
+ stream.push(...init.init);
272
+ }
273
+
274
+ delete stream[InitValues];
275
+ }
276
+
277
+ export const CoStreamProxyHandler: ProxyHandler<CoStream> = {
278
+ get(target, key, receiver) {
279
+ if (typeof key === "string" && key.startsWith("co_")) {
280
+ const rawEntry = target._raw.lastItemBy(key as AccountID);
281
+
282
+ if (!rawEntry) return;
283
+ const entry = entryFromRawEntry(
284
+ receiver,
285
+ rawEntry,
286
+ target._loadedAs,
287
+ key as unknown as ID<Account>,
288
+ target._schema[ItemsSym],
289
+ );
290
+
291
+ Object.defineProperty(entry, "all", {
292
+ get: () => {
293
+ const allRawEntries = target._raw.itemsBy(key as AccountID);
294
+ return (function* () {
295
+ while (true) {
296
+ const rawEntry = allRawEntries.next();
297
+ if (rawEntry.done) return;
298
+ yield entryFromRawEntry(
299
+ receiver,
300
+ rawEntry.value,
301
+ target._loadedAs,
302
+ key as unknown as ID<Account>,
303
+ target._schema[ItemsSym],
304
+ );
305
+ }
306
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
307
+ })() satisfies IterableIterator<SingleCoStreamEntry<any>>;
308
+ },
309
+ });
310
+
311
+ return entry;
312
+ } else if (key === "perSession") {
313
+ return new Proxy(
314
+ {},
315
+ CoStreamPerSessionProxyHandler(target, receiver),
316
+ );
317
+ } else {
318
+ return Reflect.get(target, key, receiver);
319
+ }
320
+ },
321
+ set(target, key, value, receiver) {
322
+ if (
323
+ key === ItemsSym &&
324
+ typeof value === "object" &&
325
+ SchemaInit in value
326
+ ) {
327
+ (target.constructor as typeof CoStream)._schema ||= {};
328
+ (target.constructor as typeof CoStream)._schema[ItemsSym] =
329
+ value[SchemaInit];
330
+ init(target);
331
+ return true;
332
+ } else {
333
+ return Reflect.set(target, key, value, receiver);
334
+ }
335
+ },
336
+ defineProperty(target, key, descriptor) {
337
+ if (
338
+ descriptor.value &&
339
+ key === ItemsSym &&
340
+ typeof descriptor.value === "object" &&
341
+ SchemaInit in descriptor.value
342
+ ) {
343
+ (target.constructor as typeof CoStream)._schema ||= {};
344
+ (target.constructor as typeof CoStream)._schema[ItemsSym] =
345
+ descriptor.value[SchemaInit];
346
+ init(target);
347
+ return true;
348
+ } else {
349
+ return Reflect.defineProperty(target, key, descriptor);
350
+ }
351
+ },
352
+ ownKeys(target) {
353
+ const keys = Reflect.ownKeys(target);
354
+
355
+ for (const accountID of target._raw.accounts()) {
356
+ keys.push(accountID);
357
+ }
358
+
359
+ return keys;
360
+ },
361
+ getOwnPropertyDescriptor(target, key) {
362
+ if (typeof key === "string" && key.startsWith("co_")) {
363
+ return {
364
+ configurable: true,
365
+ enumerable: true,
366
+ writable: false,
367
+ };
368
+ } else {
369
+ return Reflect.getOwnPropertyDescriptor(target, key);
370
+ }
371
+ },
372
+ };
373
+
374
+ const CoStreamPerSessionProxyHandler = (
375
+ innerTarget: CoStream,
376
+ accessFrom: CoStream,
377
+ ): ProxyHandler<Record<string, never>> => ({
378
+ get(_target, key, receiver) {
379
+ if (typeof key === "string" && key.includes("session")) {
380
+ const sessionID = key as SessionID;
381
+ const rawEntry = innerTarget._raw.lastItemIn(sessionID);
382
+
383
+ if (!rawEntry) return;
384
+ const by = cojsonInternals.accountOrAgentIDfromSessionID(sessionID);
385
+
386
+ const entry = entryFromRawEntry(
387
+ accessFrom,
388
+ rawEntry,
389
+ innerTarget._loadedAs,
390
+ cojsonInternals.isAccountID(by)
391
+ ? (by as unknown as ID<Account>)
392
+ : undefined,
393
+ innerTarget._schema[ItemsSym],
394
+ );
395
+
396
+ Object.defineProperty(entry, "all", {
397
+ get: () => {
398
+ const allRawEntries = innerTarget._raw.itemsIn(sessionID);
399
+ return (function* () {
400
+ while (true) {
401
+ const rawEntry = allRawEntries.next();
402
+ if (rawEntry.done) return;
403
+ yield entryFromRawEntry(
404
+ accessFrom,
405
+ rawEntry.value,
406
+ innerTarget._loadedAs,
407
+ cojsonInternals.isAccountID(by)
408
+ ? (by as unknown as ID<Account>)
409
+ : undefined,
410
+ innerTarget._schema[ItemsSym],
411
+ );
412
+ }
413
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
414
+ })() satisfies IterableIterator<SingleCoStreamEntry<any>>;
415
+ },
416
+ });
417
+
418
+ return entry;
419
+ } else {
420
+ return Reflect.get(innerTarget, key, receiver);
421
+ }
422
+ },
423
+ ownKeys() {
424
+ return innerTarget._raw.sessions();
425
+ },
426
+ getOwnPropertyDescriptor(target, key) {
427
+ if (typeof key === "string" && key.startsWith("co_")) {
428
+ return {
429
+ configurable: true,
430
+ enumerable: true,
431
+ writable: false,
432
+ };
433
+ } else {
434
+ return Reflect.getOwnPropertyDescriptor(target, key);
435
+ }
436
+ },
437
+ });
438
+
439
+ /** @category CoValues */
256
440
  export class BinaryCoStream
257
441
  extends CoValueBase
258
442
  implements CoValue<"BinaryCoStream", RawBinaryCoStream>
259
443
  {
260
- id!: ID<this>;
261
- _type!: "BinaryCoStream";
262
- _raw!: RawBinaryCoStream;
444
+ declare id: ID<this>;
445
+ declare _type: "BinaryCoStream";
446
+ declare _raw: RawBinaryCoStream;
263
447
 
264
448
  constructor(
265
- init: [] | undefined,
266
449
  options:
267
450
  | {
268
451
  owner: Account | Group;
269
452
  }
270
453
  | {
271
454
  fromRaw: RawBinaryCoStream;
272
- }
455
+ },
273
456
  ) {
274
457
  super();
275
458
 
@@ -287,10 +470,18 @@ export class BinaryCoStream
287
470
  value: raw.id,
288
471
  enumerable: false,
289
472
  },
473
+ _type: { value: "BinaryCoStream", enumerable: false },
290
474
  _raw: { value: raw, enumerable: false },
291
475
  });
292
476
  }
293
477
 
478
+ static create<S extends BinaryCoStream>(
479
+ this: ClassOf<S>,
480
+ options: { owner: Account | Group },
481
+ ) {
482
+ return new this(options);
483
+ }
484
+
294
485
  getChunks(options?: {
295
486
  allowUnfinished?: boolean;
296
487
  }):
@@ -311,6 +502,77 @@ export class BinaryCoStream
311
502
  this._raw.endBinaryStream();
312
503
  }
313
504
 
505
+ toBlob(options?: { allowUnfinished?: boolean }): Blob | undefined {
506
+ const chunks = this.getChunks({
507
+ allowUnfinished: options?.allowUnfinished,
508
+ });
509
+
510
+ if (!chunks) {
511
+ return undefined;
512
+ }
513
+
514
+ return new Blob(chunks.chunks, { type: chunks.mimeType });
515
+ }
516
+
517
+ static async loadAsBlob(
518
+ id: ID<BinaryCoStream>,
519
+ as: Account,
520
+ options?: {
521
+ allowUnfinished?: boolean;
522
+ },
523
+ ): Promise<Blob | undefined> {
524
+ const stream = await this.load(id, as, []);
525
+
526
+ return stream?.toBlob({
527
+ allowUnfinished: options?.allowUnfinished,
528
+ });
529
+ }
530
+
531
+ static async createFromBlob(
532
+ blob: Blob | File,
533
+ options: {
534
+ owner: Group | Account;
535
+ onProgress?: (progress: number) => void;
536
+ },
537
+ ): Promise<BinaryCoStream> {
538
+ const stream = this.create({ owner: options.owner });
539
+
540
+ const start = Date.now();
541
+
542
+ const data = new Uint8Array(await blob.arrayBuffer());
543
+ stream.start({
544
+ mimeType: blob.type,
545
+ totalSizeBytes: blob.size,
546
+ fileName: blob instanceof File ? blob.name : undefined,
547
+ });
548
+ const chunkSize = MAX_RECOMMENDED_TX_SIZE;
549
+
550
+ let lastProgressUpdate = Date.now();
551
+
552
+ for (let idx = 0; idx < data.length; idx += chunkSize) {
553
+ stream.push(data.slice(idx, idx + chunkSize));
554
+
555
+ if (Date.now() - lastProgressUpdate > 100) {
556
+ options.onProgress?.(idx / data.length);
557
+ lastProgressUpdate = Date.now();
558
+ }
559
+
560
+ await new Promise((resolve) => setTimeout(resolve, 0));
561
+ }
562
+ stream.end();
563
+ const end = Date.now();
564
+
565
+ console.debug(
566
+ "Finished creating binary stream in",
567
+ (end - start) / 1000,
568
+ "s - Throughput in MB/s",
569
+ (1000 * (blob.size / (end - start))) / (1024 * 1024),
570
+ );
571
+ options.onProgress?.(1);
572
+
573
+ return stream;
574
+ }
575
+
314
576
  toJSON() {
315
577
  return {
316
578
  id: this.id,