jazz-tools 0.19.11 → 0.19.12
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 +46 -46
- package/CHANGELOG.md +12 -0
- package/dist/{chunk-HX5S6W5E.js → chunk-AGF4HEDH.js} +56 -27
- package/dist/chunk-AGF4HEDH.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/inspector/{chunk-C6BJPHBQ.js → chunk-YQNK5Y7B.js} +47 -35
- package/dist/inspector/chunk-YQNK5Y7B.js.map +1 -0
- package/dist/inspector/{custom-element-GJVBPZES.js → custom-element-KYV64IOC.js} +47 -35
- package/dist/inspector/{custom-element-GJVBPZES.js.map → custom-element-KYV64IOC.js.map} +1 -1
- package/dist/inspector/index.js +1 -1
- package/dist/inspector/register-custom-element.js +1 -1
- package/dist/inspector/standalone.js +1 -1
- package/dist/inspector/tests/utils/transactions-changes.test.d.ts +2 -0
- package/dist/inspector/tests/utils/transactions-changes.test.d.ts.map +1 -0
- package/dist/inspector/utils/transactions-changes.d.ts +13 -13
- package/dist/inspector/utils/transactions-changes.d.ts.map +1 -1
- package/dist/react/index.js +4 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/provider.d.ts.map +1 -1
- package/dist/react-core/index.js +2 -2
- package/dist/react-core/index.js.map +1 -1
- package/dist/react-native/index.js +4 -1
- package/dist/react-native/index.js.map +1 -1
- package/dist/react-native-core/index.js +4 -1
- package/dist/react-native-core/index.js.map +1 -1
- package/dist/react-native-core/provider.d.ts.map +1 -1
- package/dist/testing.js +1 -1
- package/dist/tools/implementation/ContextManager.d.ts.map +1 -1
- package/dist/tools/subscribe/SubscriptionScope.d.ts +3 -6
- package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/inspector/tests/utils/transactions-changes.test.ts +102 -0
- package/src/inspector/ui/icons/add-icon.tsx +3 -3
- package/src/inspector/utils/history.ts +6 -6
- package/src/inspector/utils/transactions-changes.ts +37 -3
- package/src/inspector/viewer/history-view.tsx +13 -13
- package/src/react/provider.tsx +6 -1
- package/src/react-core/hooks.ts +2 -2
- package/src/react-core/tests/useSuspenseCoState.test.tsx +47 -0
- package/src/react-native-core/provider.tsx +6 -1
- package/src/tools/implementation/ContextManager.ts +10 -0
- package/src/tools/subscribe/SubscriptionScope.ts +61 -39
- package/dist/chunk-HX5S6W5E.js.map +0 -1
- package/dist/inspector/chunk-C6BJPHBQ.js.map +0 -1
|
@@ -128,7 +128,7 @@ function mapTransactionToAction(
|
|
|
128
128
|
coValue: RawCoValue,
|
|
129
129
|
): string {
|
|
130
130
|
// Group changes
|
|
131
|
-
if (TransactionChanges.isUserPromotion(change)) {
|
|
131
|
+
if (TransactionChanges.isUserPromotion(coValue, change)) {
|
|
132
132
|
if (change.value === "revoked") {
|
|
133
133
|
return `${change.key} has been revoked`;
|
|
134
134
|
}
|
|
@@ -136,28 +136,28 @@ function mapTransactionToAction(
|
|
|
136
136
|
return `${change.key} has been promoted to ${change.value}`;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
if (TransactionChanges.isGroupExtension(change)) {
|
|
139
|
+
if (TransactionChanges.isGroupExtension(coValue, change)) {
|
|
140
140
|
const child = change.key.slice(6);
|
|
141
141
|
return `Group became a member of ${child}`;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
if (TransactionChanges.isGroupExtendRevocation(change)) {
|
|
144
|
+
if (TransactionChanges.isGroupExtendRevocation(coValue, change)) {
|
|
145
145
|
const child = change.key.slice(6);
|
|
146
146
|
return `Group's membership of ${child} has been revoked.`;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
if (TransactionChanges.isGroupPromotion(change)) {
|
|
149
|
+
if (TransactionChanges.isGroupPromotion(coValue, change)) {
|
|
150
150
|
const parent = change.key.slice(7);
|
|
151
151
|
return `Group ${parent} has been promoted to ${change.value}`;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
if (TransactionChanges.isKeyRevelation(change)) {
|
|
154
|
+
if (TransactionChanges.isKeyRevelation(coValue, change)) {
|
|
155
155
|
const [key, target] = change.key.split("_for_");
|
|
156
156
|
return `Key "${key}" has been revealed to "${target}"`;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
// coList changes
|
|
160
|
-
if (TransactionChanges.isItemAppend(change)) {
|
|
160
|
+
if (TransactionChanges.isItemAppend(coValue, change)) {
|
|
161
161
|
if (change.after === "start") {
|
|
162
162
|
return `"${change.value}" has been appended`;
|
|
163
163
|
}
|
|
@@ -171,7 +171,7 @@ function mapTransactionToAction(
|
|
|
171
171
|
return `"${change.value}" has been inserted after "${(after as any).value}"`;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
if (TransactionChanges.isItemPrepend(change)) {
|
|
174
|
+
if (TransactionChanges.isItemPrepend(coValue, change)) {
|
|
175
175
|
if (change.before === "end") {
|
|
176
176
|
return `"${change.value}" has been prepended`;
|
|
177
177
|
}
|
|
@@ -185,7 +185,7 @@ function mapTransactionToAction(
|
|
|
185
185
|
return `"${change.value}" has been inserted before "${(before as any).value}"`;
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
if (TransactionChanges.isItemDeletion(change)) {
|
|
188
|
+
if (TransactionChanges.isItemDeletion(coValue, change)) {
|
|
189
189
|
const insertion = findListChange(change.insertion, coValue);
|
|
190
190
|
if (insertion === undefined) {
|
|
191
191
|
return `An undefined item has been deleted`;
|
|
@@ -195,24 +195,24 @@ function mapTransactionToAction(
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
// coStream changes
|
|
198
|
-
if (TransactionChanges.isStreamStart(change)) {
|
|
198
|
+
if (TransactionChanges.isStreamStart(coValue, change)) {
|
|
199
199
|
return `Stream started with mime type "${change.mimeType}" and file name "${change.fileName}"`;
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
if (TransactionChanges.isStreamChunk(change)) {
|
|
202
|
+
if (TransactionChanges.isStreamChunk(coValue, change)) {
|
|
203
203
|
return `Stream chunk added`;
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
if (TransactionChanges.isStreamEnd(change)) {
|
|
206
|
+
if (TransactionChanges.isStreamEnd(coValue, change)) {
|
|
207
207
|
return `Stream ended`;
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
// coMap changes
|
|
211
|
-
if (TransactionChanges.isPropertySet(change)) {
|
|
211
|
+
if (TransactionChanges.isPropertySet(coValue, change)) {
|
|
212
212
|
return `Property "${change.key}" has been set to ${JSON.stringify(change.value)}`;
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
if (TransactionChanges.isPropertyDeletion(change)) {
|
|
215
|
+
if (TransactionChanges.isPropertyDeletion(coValue, change)) {
|
|
216
216
|
return `Property "${change.key}" has been deleted`;
|
|
217
217
|
}
|
|
218
218
|
|
package/src/react/provider.tsx
CHANGED
|
@@ -58,6 +58,9 @@ export function JazzReactProvider<
|
|
|
58
58
|
);
|
|
59
59
|
const logoutReplacementActiveRef = useRef(false);
|
|
60
60
|
logoutReplacementActiveRef.current = Boolean(logOutReplacement);
|
|
61
|
+
const onAnonymousAccountDiscardedEnabled = Boolean(
|
|
62
|
+
onAnonymousAccountDiscarded,
|
|
63
|
+
);
|
|
61
64
|
|
|
62
65
|
const value = React.useSyncExternalStore<
|
|
63
66
|
JazzContextType<InstanceOfSchema<S>> | undefined
|
|
@@ -74,7 +77,9 @@ export function JazzReactProvider<
|
|
|
74
77
|
logOutReplacement: logoutReplacementActiveRef.current
|
|
75
78
|
? logOutReplacementRefCallback
|
|
76
79
|
: undefined,
|
|
77
|
-
onAnonymousAccountDiscarded:
|
|
80
|
+
onAnonymousAccountDiscarded: onAnonymousAccountDiscardedEnabled
|
|
81
|
+
? onAnonymousAccountDiscardedRefCallback
|
|
82
|
+
: undefined,
|
|
78
83
|
} satisfies JazzContextManagerProps<S>;
|
|
79
84
|
|
|
80
85
|
if (contextManager.propsChanged(props)) {
|
package/src/react-core/hooks.ts
CHANGED
|
@@ -489,7 +489,7 @@ export function useSuspenseCoState<
|
|
|
489
489
|
throw new Error("Subscription not found");
|
|
490
490
|
}
|
|
491
491
|
|
|
492
|
-
use(subscription.
|
|
492
|
+
use(subscription.getCachedPromise());
|
|
493
493
|
|
|
494
494
|
const getCurrentValue = () => {
|
|
495
495
|
const value = subscription.getCurrentValue();
|
|
@@ -824,7 +824,7 @@ export function useSuspenseAccount<
|
|
|
824
824
|
);
|
|
825
825
|
}
|
|
826
826
|
|
|
827
|
-
use(subscription.
|
|
827
|
+
use(subscription.getCachedPromise());
|
|
828
828
|
|
|
829
829
|
const getCurrentValue = () => {
|
|
830
830
|
const value = subscription.getCurrentValue();
|
|
@@ -370,6 +370,53 @@ describe("useSuspenseCoState", () => {
|
|
|
370
370
|
});
|
|
371
371
|
});
|
|
372
372
|
|
|
373
|
+
it("should throw error when CoValue becomes unauthorized", async () => {
|
|
374
|
+
const TestMap = co.map({
|
|
375
|
+
value: z.string(),
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
const group = Group.create();
|
|
379
|
+
group.addMember("everyone", "reader");
|
|
380
|
+
|
|
381
|
+
// Create CoValue owned by another account without sharing
|
|
382
|
+
const map = TestMap.create(
|
|
383
|
+
{
|
|
384
|
+
value: "123",
|
|
385
|
+
},
|
|
386
|
+
group,
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
await createJazzTestAccount({
|
|
390
|
+
isCurrentActiveAccount: true,
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
const TestComponent = () => {
|
|
394
|
+
const value = useSuspenseCoState(TestMap, map.$jazz.id);
|
|
395
|
+
return <div>{value.value}</div>;
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
const { container } = await act(async () => {
|
|
399
|
+
return render(
|
|
400
|
+
<ErrorBoundary fallback={<div>Error!</div>}>
|
|
401
|
+
<Suspense fallback={<div>Loading...</div>}>
|
|
402
|
+
<TestComponent />
|
|
403
|
+
</Suspense>
|
|
404
|
+
</ErrorBoundary>,
|
|
405
|
+
);
|
|
406
|
+
});
|
|
407
|
+
await waitFor(() => {
|
|
408
|
+
expect(container.textContent).toContain("123");
|
|
409
|
+
expect(container.textContent).not.toContain("Loading...");
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
group.removeMember("everyone");
|
|
413
|
+
|
|
414
|
+
// Wait for error to be thrown (unauthorized access)
|
|
415
|
+
await waitFor(() => {
|
|
416
|
+
expect(container.textContent).toContain("Error!");
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
|
|
373
420
|
it("should update value when CoValue changes", async () => {
|
|
374
421
|
const TestMap = co.map({
|
|
375
422
|
value: z.string(),
|
|
@@ -55,6 +55,9 @@ export function JazzProviderCore<
|
|
|
55
55
|
);
|
|
56
56
|
const logoutReplacementActiveRef = useRef(false);
|
|
57
57
|
logoutReplacementActiveRef.current = Boolean(logOutReplacement);
|
|
58
|
+
const onAnonymousAccountDiscardedEnabled = Boolean(
|
|
59
|
+
onAnonymousAccountDiscarded,
|
|
60
|
+
);
|
|
58
61
|
|
|
59
62
|
const value = React.useSyncExternalStore<
|
|
60
63
|
JazzContextType<InstanceOfSchema<S>> | undefined
|
|
@@ -71,7 +74,9 @@ export function JazzProviderCore<
|
|
|
71
74
|
logOutReplacement: logoutReplacementActiveRef.current
|
|
72
75
|
? logOutReplacementRefCallback
|
|
73
76
|
: undefined,
|
|
74
|
-
onAnonymousAccountDiscarded:
|
|
77
|
+
onAnonymousAccountDiscarded: onAnonymousAccountDiscardedEnabled
|
|
78
|
+
? onAnonymousAccountDiscardedRefCallback
|
|
79
|
+
: undefined,
|
|
75
80
|
CryptoProvider,
|
|
76
81
|
} satisfies JazzContextManagerProps<S>;
|
|
77
82
|
|
|
@@ -351,6 +351,16 @@ export class JazzContextManager<
|
|
|
351
351
|
// The storage is reachable through currentContext using the connectedPeers
|
|
352
352
|
prevContext.node.removeStorage();
|
|
353
353
|
|
|
354
|
+
// Ensure that the new context is the only peer connected to the previous context
|
|
355
|
+
// This way all the changes made in the previous context are synced only to the new context
|
|
356
|
+
for (const peer of Object.values(prevContext.node.syncManager.peers)) {
|
|
357
|
+
if (!peer.closed) {
|
|
358
|
+
peer.gracefulShutdown();
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
prevContext.node.syncManager.peers = {};
|
|
363
|
+
|
|
354
364
|
currentContext.node.syncManager.addPeer(prevAccountAsPeer);
|
|
355
365
|
prevContext.node.syncManager.addPeer(currentAccountAsPeer);
|
|
356
366
|
|
|
@@ -309,64 +309,86 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
309
309
|
|
|
310
310
|
unloadedValue: NotLoaded<D> | undefined;
|
|
311
311
|
|
|
312
|
-
lastPromise:
|
|
313
|
-
| {
|
|
314
|
-
value: MaybeLoaded<D> | undefined;
|
|
315
|
-
promise: PromiseWithStatus<MaybeLoaded<D>>;
|
|
316
|
-
}
|
|
317
|
-
| undefined;
|
|
318
|
-
|
|
319
|
-
cachePromise(value: MaybeLoaded<D>, callback: () => PromiseWithStatus<D>) {
|
|
320
|
-
if (this.lastPromise?.value === value) {
|
|
321
|
-
return this.lastPromise.promise;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
const promise = callback();
|
|
325
|
-
this.lastPromise = { value, promise };
|
|
326
|
-
|
|
327
|
-
return promise;
|
|
328
|
-
}
|
|
312
|
+
lastPromise: PromiseWithStatus<D> | undefined;
|
|
329
313
|
|
|
330
314
|
getPromise() {
|
|
331
315
|
const currentValue = this.getCurrentValue();
|
|
332
316
|
|
|
333
317
|
if (currentValue.$isLoaded) {
|
|
334
|
-
return resolvedPromise(currentValue);
|
|
318
|
+
return resolvedPromise<D>(currentValue);
|
|
335
319
|
}
|
|
336
320
|
|
|
337
321
|
if (currentValue.$jazz.loadingState !== CoValueLoadingState.LOADING) {
|
|
338
322
|
const error = this.getError();
|
|
339
|
-
return rejectedPromise(
|
|
323
|
+
return rejectedPromise<D>(
|
|
340
324
|
new Error(error?.toString() ?? "Unknown error", {
|
|
341
325
|
cause: this.callerStack,
|
|
342
326
|
}),
|
|
343
327
|
);
|
|
344
328
|
}
|
|
345
329
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const unsubscribe = this.subscribe(() => {
|
|
350
|
-
const currentValue = this.getCurrentValue();
|
|
330
|
+
const promise = new Promise<D>((resolve, reject) => {
|
|
331
|
+
const unsubscribe = this.subscribe(() => {
|
|
332
|
+
const currentValue = this.getCurrentValue();
|
|
351
333
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
334
|
+
if (currentValue.$jazz.loadingState === CoValueLoadingState.LOADING) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
355
337
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
)
|
|
364
|
-
|
|
338
|
+
if (currentValue.$isLoaded) {
|
|
339
|
+
promise.status = "fulfilled";
|
|
340
|
+
promise.value = currentValue;
|
|
341
|
+
resolve(currentValue);
|
|
342
|
+
} else {
|
|
343
|
+
promise.status = "rejected";
|
|
344
|
+
promise.reason = new Error(
|
|
345
|
+
this.getError()?.toString() ?? "Unknown error",
|
|
346
|
+
{
|
|
347
|
+
cause: this.callerStack,
|
|
348
|
+
},
|
|
349
|
+
);
|
|
350
|
+
reject(
|
|
351
|
+
new Error(this.getError()?.toString() ?? "Unknown error", {
|
|
352
|
+
cause: this.callerStack,
|
|
353
|
+
}),
|
|
354
|
+
);
|
|
355
|
+
}
|
|
365
356
|
|
|
366
|
-
|
|
367
|
-
});
|
|
357
|
+
unsubscribe();
|
|
368
358
|
});
|
|
369
|
-
})
|
|
359
|
+
}) as PromiseWithStatus<D>;
|
|
360
|
+
|
|
361
|
+
promise.status = "pending";
|
|
362
|
+
|
|
363
|
+
return promise;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
getCachedPromise() {
|
|
367
|
+
if (this.lastPromise) {
|
|
368
|
+
const value = this.getCurrentValue();
|
|
369
|
+
|
|
370
|
+
// if the value is loaded, we update the promise state
|
|
371
|
+
// to ensure that the value provided is always up to date
|
|
372
|
+
if (value.$isLoaded) {
|
|
373
|
+
this.lastPromise.status = "fulfilled";
|
|
374
|
+
this.lastPromise.value = value;
|
|
375
|
+
} else if (value.$jazz.loadingState !== CoValueLoadingState.LOADING) {
|
|
376
|
+
this.lastPromise.status = "rejected";
|
|
377
|
+
this.lastPromise.reason = new Error(
|
|
378
|
+
this.getError()?.toString() ?? "Unknown error",
|
|
379
|
+
{
|
|
380
|
+
cause: this.callerStack,
|
|
381
|
+
},
|
|
382
|
+
);
|
|
383
|
+
} else if (this.lastPromise.status !== "pending") {
|
|
384
|
+
// Value got into loading state, we need to suspend again
|
|
385
|
+
this.lastPromise = this.getPromise();
|
|
386
|
+
}
|
|
387
|
+
} else {
|
|
388
|
+
this.lastPromise = this.getPromise();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return this.lastPromise;
|
|
370
392
|
}
|
|
371
393
|
|
|
372
394
|
private getUnloadedValue(reason: NotLoadedCoValueState): NotLoaded<D> {
|