jazz-tools 0.15.0 → 0.15.1
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.
- package/.turbo/turbo-build.log +44 -44
- package/CHANGELOG.md +12 -0
- package/dist/{chunk-FSIM7N33.js → chunk-VBDJM6Z5.js} +142 -31
- package/dist/chunk-VBDJM6Z5.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/inspector/index.js +30 -4
- package/dist/inspector/index.js.map +1 -1
- package/dist/inspector/viewer/new-app.d.ts.map +1 -1
- package/dist/inspector/viewer/use-open-inspector.d.ts +2 -0
- package/dist/inspector/viewer/use-open-inspector.d.ts.map +1 -0
- package/dist/inspector/viewer/use-page-path.d.ts.map +1 -1
- package/dist/react/index.js +0 -2
- package/dist/react/index.js.map +1 -1
- package/dist/react/testing.js +0 -2
- package/dist/react/testing.js.map +1 -1
- package/dist/react-native-core/index.js +2 -18
- package/dist/react-native-core/index.js.map +1 -1
- package/dist/react-native-core/media.d.ts.map +1 -1
- package/dist/testing.js +1 -1
- package/dist/tools/coValues/coFeed.d.ts +9 -0
- package/dist/tools/coValues/coFeed.d.ts.map +1 -1
- package/dist/tools/coValues/coMap.d.ts +98 -2
- package/dist/tools/coValues/coMap.d.ts.map +1 -1
- package/dist/tools/coValues/interfaces.d.ts +3 -0
- package/dist/tools/coValues/interfaces.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoMapSchema.d.ts +12 -0
- package/dist/tools/implementation/zodSchema/schemaTypes/CoMapSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/zodCo.d.ts.map +1 -1
- package/dist/tools/subscribe/CoValueCoreSubscription.d.ts +2 -1
- package/dist/tools/subscribe/CoValueCoreSubscription.d.ts.map +1 -1
- package/dist/tools/subscribe/SubscriptionScope.d.ts +2 -1
- package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/inspector/viewer/new-app.tsx +2 -1
- package/src/inspector/viewer/use-open-inspector.ts +18 -0
- package/src/inspector/viewer/use-page-path.ts +14 -1
- package/src/react-native-core/media.tsx +2 -22
- package/src/tools/coValues/coFeed.ts +38 -0
- package/src/tools/coValues/coMap.ts +118 -14
- package/src/tools/coValues/interfaces.ts +14 -4
- package/src/tools/implementation/zodSchema/schemaTypes/CoMapSchema.ts +38 -0
- package/src/tools/implementation/zodSchema/zodCo.ts +6 -0
- package/src/tools/subscribe/CoValueCoreSubscription.ts +12 -9
- package/src/tools/subscribe/SubscriptionScope.ts +31 -19
- package/src/tools/tests/coFeed.test.ts +69 -0
- package/src/tools/tests/coMap.test.ts +480 -4
- package/src/tools/tests/load.test.ts +2 -1
- package/dist/chunk-FSIM7N33.js.map +0 -1
@@ -10,12 +10,10 @@ import {
|
|
10
10
|
} from "cojson";
|
11
11
|
import type {
|
12
12
|
AnonymousJazzAgent,
|
13
|
-
AnyAccountSchema,
|
14
13
|
CoValue,
|
15
14
|
CoValueClass,
|
16
15
|
Group,
|
17
16
|
ID,
|
18
|
-
InstanceOfSchema,
|
19
17
|
RefEncoded,
|
20
18
|
RefIfCoValue,
|
21
19
|
RefsToResolve,
|
@@ -319,18 +317,7 @@ export class CoMap extends CoValueBase implements CoValue {
|
|
319
317
|
) {
|
320
318
|
const instance = new this();
|
321
319
|
|
322
|
-
|
323
|
-
const raw = instance.rawFromInit(init, owner, uniqueness);
|
324
|
-
|
325
|
-
Object.defineProperties(instance, {
|
326
|
-
id: {
|
327
|
-
value: raw.id,
|
328
|
-
enumerable: false,
|
329
|
-
},
|
330
|
-
_raw: { value: raw, enumerable: false },
|
331
|
-
});
|
332
|
-
|
333
|
-
return instance;
|
320
|
+
return instance._createCoMap(init, options);
|
334
321
|
}
|
335
322
|
|
336
323
|
/**
|
@@ -388,6 +375,30 @@ export class CoMap extends CoValueBase implements CoValue {
|
|
388
375
|
return this.toJSON();
|
389
376
|
}
|
390
377
|
|
378
|
+
_createCoMap(
|
379
|
+
init: Simplify<CoMapInit<typeof this>>,
|
380
|
+
options?:
|
381
|
+
| {
|
382
|
+
owner: Account | Group;
|
383
|
+
unique?: CoValueUniqueness["uniqueness"];
|
384
|
+
}
|
385
|
+
| Account
|
386
|
+
| Group,
|
387
|
+
): typeof this {
|
388
|
+
const { owner, uniqueness } = parseCoValueCreateOptions(options);
|
389
|
+
const raw = this.rawFromInit(init, owner, uniqueness);
|
390
|
+
|
391
|
+
Object.defineProperties(this, {
|
392
|
+
id: {
|
393
|
+
value: raw.id,
|
394
|
+
enumerable: false,
|
395
|
+
},
|
396
|
+
_raw: { value: raw, enumerable: false },
|
397
|
+
});
|
398
|
+
|
399
|
+
return this;
|
400
|
+
}
|
401
|
+
|
391
402
|
/**
|
392
403
|
* Create a new `RawCoMap` from an initialization object
|
393
404
|
* @internal
|
@@ -490,6 +501,7 @@ export class CoMap extends CoValueBase implements CoValue {
|
|
490
501
|
options?: {
|
491
502
|
resolve?: RefsToResolveStrict<M, R>;
|
492
503
|
loadAs?: Account | AnonymousJazzAgent;
|
504
|
+
skipRetry?: boolean;
|
493
505
|
},
|
494
506
|
): Promise<Resolved<M, R> | null> {
|
495
507
|
return loadCoValueWithoutMe(this, id, options);
|
@@ -542,11 +554,22 @@ export class CoMap extends CoValueBase implements CoValue {
|
|
542
554
|
return subscribeToCoValueWithoutMe<M, R>(this, id, options, listener);
|
543
555
|
}
|
544
556
|
|
557
|
+
/** @deprecated Use `CoMap.upsertUnique` and `CoMap.loadUnique` instead. */
|
545
558
|
static findUnique<M extends CoMap>(
|
546
559
|
this: CoValueClass<M>,
|
547
560
|
unique: CoValueUniqueness["uniqueness"],
|
548
561
|
ownerID: ID<Account> | ID<Group>,
|
549
562
|
as?: Account | Group | AnonymousJazzAgent,
|
563
|
+
) {
|
564
|
+
return CoMap._findUnique(unique, ownerID, as);
|
565
|
+
}
|
566
|
+
|
567
|
+
/** @internal */
|
568
|
+
static _findUnique<M extends CoMap>(
|
569
|
+
this: CoValueClass<M>,
|
570
|
+
unique: CoValueUniqueness["uniqueness"],
|
571
|
+
ownerID: ID<Account> | ID<Group>,
|
572
|
+
as?: Account | Group | AnonymousJazzAgent,
|
550
573
|
) {
|
551
574
|
as ||= activeAccountContext.get();
|
552
575
|
|
@@ -564,6 +587,87 @@ export class CoMap extends CoValueBase implements CoValue {
|
|
564
587
|
return cojsonInternals.idforHeader(header, crypto) as ID<M>;
|
565
588
|
}
|
566
589
|
|
590
|
+
/**
|
591
|
+
* Given some data, updates an existing CoMap or initialises a new one if none exists.
|
592
|
+
*
|
593
|
+
* Note: This method respects resolve options, and thus can return `null` if the references cannot be resolved.
|
594
|
+
*
|
595
|
+
* @example
|
596
|
+
* ```ts
|
597
|
+
* const activeEvent = await Event.upsertUnique(
|
598
|
+
* sourceData.identifier,
|
599
|
+
* workspace.id,
|
600
|
+
* {
|
601
|
+
* title: sourceData.title,
|
602
|
+
* identifier: sourceData.identifier,
|
603
|
+
* external_id: sourceData._id,
|
604
|
+
* },
|
605
|
+
* workspace
|
606
|
+
* );
|
607
|
+
* ```
|
608
|
+
*
|
609
|
+
* @param options The options for creating or loading the CoMap. This includes the intended state of the CoMap, its unique identifier, its owner, and the references to resolve.
|
610
|
+
* @returns Either an existing & modified CoMap, or a new initialised CoMap if none exists.
|
611
|
+
* @category Subscription & Loading
|
612
|
+
*/
|
613
|
+
static async upsertUnique<
|
614
|
+
M extends CoMap,
|
615
|
+
const R extends RefsToResolve<M> = true,
|
616
|
+
>(
|
617
|
+
this: CoValueClass<M>,
|
618
|
+
options: {
|
619
|
+
value: Simplify<CoMapInit<M>>;
|
620
|
+
unique: CoValueUniqueness["uniqueness"];
|
621
|
+
owner: Account | Group;
|
622
|
+
resolve?: RefsToResolveStrict<M, R>;
|
623
|
+
},
|
624
|
+
): Promise<Resolved<M, R> | null> {
|
625
|
+
let mapId = CoMap._findUnique(options.unique, options.owner.id);
|
626
|
+
let map: Resolved<M, R> | null = await loadCoValueWithoutMe(this, mapId, {
|
627
|
+
...options,
|
628
|
+
loadAs: options.owner._loadedAs,
|
629
|
+
skipRetry: true,
|
630
|
+
});
|
631
|
+
if (!map) {
|
632
|
+
const instance = new this();
|
633
|
+
map = instance._createCoMap(options.value, {
|
634
|
+
owner: options.owner,
|
635
|
+
unique: options.unique,
|
636
|
+
}) as Resolved<M, R>;
|
637
|
+
} else {
|
638
|
+
(map as M).applyDiff(options.value as Partial<CoMapInit<M>>);
|
639
|
+
}
|
640
|
+
|
641
|
+
return await loadCoValueWithoutMe(this, mapId, {
|
642
|
+
...options,
|
643
|
+
loadAs: options.owner._loadedAs,
|
644
|
+
skipRetry: true,
|
645
|
+
});
|
646
|
+
}
|
647
|
+
|
648
|
+
/**
|
649
|
+
* Loads a CoMap by its unique identifier and owner's ID.
|
650
|
+
* @param unique The unique identifier of the CoMap to load.
|
651
|
+
* @param ownerID The ID of the owner of the CoMap.
|
652
|
+
* @param options Additional options for loading the CoMap.
|
653
|
+
* @returns The loaded CoMap, or null if unavailable.
|
654
|
+
*/
|
655
|
+
static loadUnique<M extends CoMap, const R extends RefsToResolve<M> = true>(
|
656
|
+
this: CoValueClass<M>,
|
657
|
+
unique: CoValueUniqueness["uniqueness"],
|
658
|
+
ownerID: ID<Account> | ID<Group>,
|
659
|
+
options?: {
|
660
|
+
resolve?: RefsToResolveStrict<M, R>;
|
661
|
+
loadAs?: Account | AnonymousJazzAgent;
|
662
|
+
},
|
663
|
+
): Promise<Resolved<M, R> | null> {
|
664
|
+
return loadCoValueWithoutMe(
|
665
|
+
this,
|
666
|
+
CoMap._findUnique(unique, ownerID, options?.loadAs),
|
667
|
+
{ ...options, skipRetry: true },
|
668
|
+
);
|
669
|
+
}
|
670
|
+
|
567
671
|
/**
|
568
672
|
* Given an already loaded `CoMap`, ensure that the specified fields are loaded to the specified depth.
|
569
673
|
*
|
@@ -90,6 +90,7 @@ export function loadCoValueWithoutMe<
|
|
90
90
|
options?: {
|
91
91
|
resolve?: RefsToResolveStrict<V, R>;
|
92
92
|
loadAs?: Account | AnonymousJazzAgent;
|
93
|
+
skipRetry?: boolean;
|
93
94
|
},
|
94
95
|
): Promise<Resolved<V, R> | null> {
|
95
96
|
return loadCoValue(cls, id, {
|
@@ -107,6 +108,7 @@ export function loadCoValue<
|
|
107
108
|
options: {
|
108
109
|
resolve?: RefsToResolveStrict<V, R>;
|
109
110
|
loadAs: Account | AnonymousJazzAgent;
|
111
|
+
skipRetry?: boolean;
|
110
112
|
},
|
111
113
|
): Promise<Resolved<V, R> | null> {
|
112
114
|
return new Promise((resolve) => {
|
@@ -117,6 +119,7 @@ export function loadCoValue<
|
|
117
119
|
resolve: options.resolve,
|
118
120
|
loadAs: options.loadAs,
|
119
121
|
syncResolution: true,
|
122
|
+
skipRetry: options.skipRetry,
|
120
123
|
onUnavailable: () => {
|
121
124
|
resolve(null);
|
122
125
|
},
|
@@ -242,6 +245,7 @@ export function subscribeToCoValue<
|
|
242
245
|
onUnavailable?: () => void;
|
243
246
|
onUnauthorized?: () => void;
|
244
247
|
syncResolution?: boolean;
|
248
|
+
skipRetry?: boolean;
|
245
249
|
},
|
246
250
|
listener: SubscribeListener<V, R>,
|
247
251
|
): () => void {
|
@@ -252,10 +256,16 @@ export function subscribeToCoValue<
|
|
252
256
|
|
253
257
|
let unsubscribed = false;
|
254
258
|
|
255
|
-
const rootNode = new SubscriptionScope<V>(
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
+
const rootNode = new SubscriptionScope<V>(
|
260
|
+
node,
|
261
|
+
resolve,
|
262
|
+
id as ID<V>,
|
263
|
+
{
|
264
|
+
ref: cls,
|
265
|
+
optional: false,
|
266
|
+
},
|
267
|
+
options.skipRetry,
|
268
|
+
);
|
259
269
|
|
260
270
|
const handleUpdate = (value: SubscriptionValue<V, any>) => {
|
261
271
|
if (unsubscribed) return;
|
@@ -57,6 +57,7 @@ export type CoMapSchema<
|
|
57
57
|
R
|
58
58
|
>;
|
59
59
|
loadAs?: Account | AnonymousJazzAgent;
|
60
|
+
skipRetry?: boolean;
|
60
61
|
},
|
61
62
|
): Promise<Resolved<
|
62
63
|
Simplify<CoMapInstanceCoValuesNullable<Shape>> & CoMap,
|
@@ -82,12 +83,49 @@ export type CoMapSchema<
|
|
82
83
|
) => void,
|
83
84
|
): () => void;
|
84
85
|
|
86
|
+
/** @deprecated Use `CoMap.upsertUnique` and `CoMap.loadUnique` instead. */
|
85
87
|
findUnique(
|
86
88
|
unique: CoValueUniqueness["uniqueness"],
|
87
89
|
ownerID: string,
|
88
90
|
as?: Account | Group | AnonymousJazzAgent,
|
89
91
|
): string;
|
90
92
|
|
93
|
+
upsertUnique: <
|
94
|
+
const R extends RefsToResolve<
|
95
|
+
Simplify<CoMapInstanceCoValuesNullable<Shape>> & CoMap
|
96
|
+
> = true,
|
97
|
+
>(options: {
|
98
|
+
value: Simplify<CoMapInitZod<Shape>>;
|
99
|
+
unique: CoValueUniqueness["uniqueness"];
|
100
|
+
owner: Owner;
|
101
|
+
resolve?: RefsToResolveStrict<
|
102
|
+
Simplify<CoMapInstanceCoValuesNullable<Shape>> & CoMap,
|
103
|
+
R
|
104
|
+
>;
|
105
|
+
}) => Promise<Resolved<
|
106
|
+
Simplify<CoMapInstanceCoValuesNullable<Shape>> & CoMap,
|
107
|
+
R
|
108
|
+
> | null>;
|
109
|
+
|
110
|
+
loadUnique<
|
111
|
+
const R extends RefsToResolve<
|
112
|
+
Simplify<CoMapInstanceCoValuesNullable<Shape>> & CoMap
|
113
|
+
> = true,
|
114
|
+
>(
|
115
|
+
unique: CoValueUniqueness["uniqueness"],
|
116
|
+
ownerID: string,
|
117
|
+
options?: {
|
118
|
+
resolve?: RefsToResolveStrict<
|
119
|
+
Simplify<CoMapInstanceCoValuesNullable<Shape>> & CoMap,
|
120
|
+
R
|
121
|
+
>;
|
122
|
+
loadAs?: Account | AnonymousJazzAgent;
|
123
|
+
},
|
124
|
+
): Promise<Resolved<
|
125
|
+
Simplify<CoMapInstanceCoValuesNullable<Shape>> & CoMap,
|
126
|
+
R
|
127
|
+
> | null>;
|
128
|
+
|
91
129
|
catchall<T extends z.core.$ZodType>(
|
92
130
|
schema: T,
|
93
131
|
): CoMapSchema<Shape, z.core.$catchall<T>>;
|
@@ -44,6 +44,12 @@ function enrichCoMapSchema<Shape extends z.core.$ZodLooseShape>(
|
|
44
44
|
findUnique: (...args: any[]) => {
|
45
45
|
return coSchema.findUnique(...args);
|
46
46
|
},
|
47
|
+
upsertUnique: (...args: any[]) => {
|
48
|
+
return coSchema.upsertUnique(...args);
|
49
|
+
},
|
50
|
+
loadUnique: (...args: any[]) => {
|
51
|
+
return coSchema.loadUnique(...args);
|
52
|
+
},
|
47
53
|
catchall: (index: z.core.$ZodType) => {
|
48
54
|
return enrichCoMapSchema(baseCatchall(index));
|
49
55
|
},
|
@@ -10,22 +10,25 @@ export class CoValueCoreSubscription {
|
|
10
10
|
public node: LocalNode,
|
11
11
|
public id: string,
|
12
12
|
public listener: (value: RawCoValue | "unavailable") => void,
|
13
|
+
public skipRetry?: boolean,
|
13
14
|
) {
|
14
15
|
const entry = this.node.getCoValue(this.id as any);
|
15
16
|
|
16
17
|
if (entry?.isAvailable()) {
|
17
18
|
this.subscribe(entry.getCurrentContent());
|
18
19
|
} else {
|
19
|
-
this.node
|
20
|
-
|
20
|
+
this.node
|
21
|
+
.loadCoValueCore(this.id as any, undefined, skipRetry)
|
22
|
+
.then((value) => {
|
23
|
+
if (this.unsubscribed) return;
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
25
|
+
if (value.isAvailable()) {
|
26
|
+
this.subscribe(value.getCurrentContent());
|
27
|
+
} else {
|
28
|
+
this.subscribeToState();
|
29
|
+
this.listener("unavailable");
|
30
|
+
}
|
31
|
+
});
|
29
32
|
}
|
30
33
|
}
|
31
34
|
|
@@ -44,34 +44,46 @@ export class SubscriptionScope<D extends CoValue> {
|
|
44
44
|
resolve: RefsToResolve<D>,
|
45
45
|
public id: ID<D>,
|
46
46
|
public schema: RefEncoded<D>,
|
47
|
+
public skipRetry?: boolean,
|
47
48
|
) {
|
48
49
|
this.resolve = resolve;
|
49
50
|
this.value = { type: "unloaded", id };
|
50
51
|
|
51
52
|
let lastUpdate: RawCoValue | "unavailable" | undefined;
|
52
|
-
this.subscription = new CoValueCoreSubscription(
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
if (!this.migrated && value !== "unavailable") {
|
62
|
-
if (this.migrating) {
|
53
|
+
this.subscription = new CoValueCoreSubscription(
|
54
|
+
node,
|
55
|
+
id,
|
56
|
+
(value) => {
|
57
|
+
lastUpdate = value;
|
58
|
+
|
59
|
+
if (skipRetry && value === "unavailable") {
|
60
|
+
this.handleUpdate(value);
|
61
|
+
this.destroy();
|
63
62
|
return;
|
64
63
|
}
|
65
64
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
65
|
+
// Need all these checks because the migration can trigger new syncronous updates
|
66
|
+
//
|
67
|
+
// We want to:
|
68
|
+
// - Run the migration only once
|
69
|
+
// - Skip all the updates until the migration is done
|
70
|
+
// - Trigger handleUpdate only with the final value
|
71
|
+
if (!this.migrated && value !== "unavailable") {
|
72
|
+
if (this.migrating) {
|
73
|
+
return;
|
74
|
+
}
|
75
|
+
|
76
|
+
this.migrating = true;
|
77
|
+
applyCoValueMigrations(instantiateRefEncoded(this.schema, value));
|
78
|
+
this.migrated = true;
|
79
|
+
this.handleUpdate(lastUpdate);
|
80
|
+
return;
|
81
|
+
}
|
72
82
|
|
73
|
-
|
74
|
-
|
83
|
+
this.handleUpdate(value);
|
84
|
+
},
|
85
|
+
skipRetry,
|
86
|
+
);
|
75
87
|
}
|
76
88
|
|
77
89
|
updateValue(value: SubscriptionValue<D, any>) {
|
@@ -688,6 +688,75 @@ describe("FileStream.loadAsBlob", async () => {
|
|
688
688
|
});
|
689
689
|
});
|
690
690
|
|
691
|
+
describe("FileStream.loadAsBase64", async () => {
|
692
|
+
async function setup() {
|
693
|
+
const me = await Account.create({
|
694
|
+
creationProps: { name: "Hermes Puggington" },
|
695
|
+
crypto: Crypto,
|
696
|
+
});
|
697
|
+
|
698
|
+
const stream = FileStream.create({ owner: me });
|
699
|
+
|
700
|
+
stream.start({ mimeType: "text/plain" });
|
701
|
+
|
702
|
+
return { stream, me };
|
703
|
+
}
|
704
|
+
|
705
|
+
test("resolves only when the stream is ended", async () => {
|
706
|
+
const { stream, me } = await setup();
|
707
|
+
stream.push(new Uint8Array([1]));
|
708
|
+
|
709
|
+
const promise = FileStream.loadAsBase64(stream.id, { loadAs: me });
|
710
|
+
|
711
|
+
stream.push(new Uint8Array([2]));
|
712
|
+
stream.end();
|
713
|
+
|
714
|
+
const base64 = await promise;
|
715
|
+
|
716
|
+
// The promise resolves only when the stream is ended
|
717
|
+
// so we get a blob with all the chunks
|
718
|
+
expect(base64).toBe("AQI=");
|
719
|
+
});
|
720
|
+
|
721
|
+
test("resolves with a data URL if dataURL: true", async () => {
|
722
|
+
const { stream, me } = await setup();
|
723
|
+
stream.push(new Uint8Array([1]));
|
724
|
+
|
725
|
+
const promise = FileStream.loadAsBase64(stream.id, {
|
726
|
+
loadAs: me,
|
727
|
+
dataURL: true,
|
728
|
+
});
|
729
|
+
|
730
|
+
stream.push(new Uint8Array([2]));
|
731
|
+
stream.end();
|
732
|
+
|
733
|
+
const base64 = await promise;
|
734
|
+
|
735
|
+
// The promise resolves only when the stream is ended
|
736
|
+
// so we get a blob with all the chunks
|
737
|
+
expect(base64).toBe("data:text/plain;base64,AQI=");
|
738
|
+
});
|
739
|
+
|
740
|
+
test("resolves with a partial base64 if allowUnfinished: true", async () => {
|
741
|
+
const { stream, me } = await setup();
|
742
|
+
stream.push(new Uint8Array([1]));
|
743
|
+
|
744
|
+
const promise = FileStream.loadAsBase64(stream.id, {
|
745
|
+
loadAs: me,
|
746
|
+
allowUnfinished: true,
|
747
|
+
});
|
748
|
+
|
749
|
+
const base64 = await promise;
|
750
|
+
|
751
|
+
stream.push(new Uint8Array([2]));
|
752
|
+
stream.end();
|
753
|
+
|
754
|
+
// The promise resolves before the stream is ended
|
755
|
+
// so we get a blob only with the first chunk
|
756
|
+
expect(base64).toBe("AQ==");
|
757
|
+
});
|
758
|
+
});
|
759
|
+
|
691
760
|
describe("FileStream progress tracking", async () => {
|
692
761
|
test("createFromBlob should report upload progress correctly", async () => {
|
693
762
|
// Create 5MB test blob
|