jazz-tools 0.19.6 → 0.19.8
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 +51 -47
- package/CHANGELOG.md +24 -0
- package/dist/{chunk-GCBXV2KC.js → chunk-2S3Z2CN6.js} +122 -53
- package/dist/chunk-2S3Z2CN6.js.map +1 -0
- package/dist/expo/polyfills.js +22 -0
- package/dist/expo/polyfills.js.map +1 -0
- package/dist/index.js +26 -6
- package/dist/index.js.map +1 -1
- package/dist/react-core/hooks.d.ts.map +1 -1
- package/dist/react-core/index.js +17 -6
- package/dist/react-core/index.js.map +1 -1
- package/dist/react-native/index.d.ts +1 -1
- package/dist/react-native/index.d.ts.map +1 -1
- package/dist/react-native/index.js +713 -9
- package/dist/react-native/index.js.map +1 -1
- package/dist/react-native/polyfills.js +22 -0
- package/dist/react-native/polyfills.js.map +1 -0
- package/dist/react-native-core/index.d.ts.map +1 -1
- package/dist/react-native-core/index.js.map +1 -1
- package/dist/testing.js +1 -1
- package/dist/tools/coValues/account.d.ts +3 -3
- package/dist/tools/coValues/account.d.ts.map +1 -1
- package/dist/tools/coValues/coFeed.d.ts +3 -3
- package/dist/tools/coValues/coFeed.d.ts.map +1 -1
- package/dist/tools/coValues/coList.d.ts +4 -4
- package/dist/tools/coValues/coList.d.ts.map +1 -1
- package/dist/tools/coValues/coMap.d.ts +7 -7
- package/dist/tools/coValues/coMap.d.ts.map +1 -1
- package/dist/tools/coValues/coPlainText.d.ts +2 -2
- package/dist/tools/coValues/coPlainText.d.ts.map +1 -1
- package/dist/tools/coValues/coVector.d.ts +2 -2
- package/dist/tools/coValues/coVector.d.ts.map +1 -1
- package/dist/tools/coValues/deepLoading.d.ts +24 -0
- package/dist/tools/coValues/deepLoading.d.ts.map +1 -1
- package/dist/tools/coValues/group.d.ts +2 -2
- package/dist/tools/coValues/group.d.ts.map +1 -1
- package/dist/tools/coValues/interfaces.d.ts +6 -6
- package/dist/tools/coValues/interfaces.d.ts.map +1 -1
- package/dist/tools/coValues/schemaUnion.d.ts +2 -2
- package/dist/tools/coValues/schemaUnion.d.ts.map +1 -1
- package/dist/tools/config.d.ts +3 -0
- package/dist/tools/config.d.ts.map +1 -0
- package/dist/tools/exports.d.ts +2 -0
- package/dist/tools/exports.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/AccountSchema.d.ts +2 -2
- package/dist/tools/implementation/zodSchema/schemaTypes/AccountSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoDiscriminatedUnionSchema.d.ts +2 -2
- package/dist/tools/implementation/zodSchema/schemaTypes/CoDiscriminatedUnionSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoFeedSchema.d.ts +2 -2
- package/dist/tools/implementation/zodSchema/schemaTypes/CoFeedSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoListSchema.d.ts +4 -4
- package/dist/tools/implementation/zodSchema/schemaTypes/CoListSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoMapSchema.d.ts +4 -4
- package/dist/tools/implementation/zodSchema/schemaTypes/CoMapSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoRecordSchema.d.ts +4 -4
- package/dist/tools/implementation/zodSchema/schemaTypes/CoRecordSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.d.ts +2 -2
- package/dist/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/GroupSchema.d.ts +2 -2
- package/dist/tools/implementation/zodSchema/schemaTypes/GroupSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/PlainTextSchema.d.ts +2 -2
- package/dist/tools/implementation/zodSchema/schemaTypes/PlainTextSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/RichTextSchema.d.ts +2 -2
- package/dist/tools/implementation/zodSchema/schemaTypes/RichTextSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/zodCo.d.ts.map +1 -1
- package/dist/tools/subscribe/JazzError.d.ts.map +1 -1
- package/dist/tools/subscribe/SubscriptionScope.d.ts +10 -1
- package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
- package/dist/tools/subscribe/errorReporting.d.ts +31 -0
- package/dist/tools/subscribe/errorReporting.d.ts.map +1 -0
- package/dist/tools/testing.d.ts.map +1 -1
- package/dist/tools/tests/errorReporting.test.d.ts +2 -0
- package/dist/tools/tests/errorReporting.test.d.ts.map +1 -0
- package/package.json +13 -5
- package/src/react-core/hooks.ts +16 -0
- package/src/react-native/index.ts +1 -1
- package/src/react-native-core/index.ts +2 -0
- package/src/react-native-core/polyfills/index.js +28 -0
- package/src/tools/coValues/account.ts +3 -4
- package/src/tools/coValues/coFeed.ts +3 -2
- package/src/tools/coValues/coList.ts +4 -4
- package/src/tools/coValues/coMap.ts +4 -4
- package/src/tools/coValues/coPlainText.ts +2 -2
- package/src/tools/coValues/coVector.ts +2 -2
- package/src/tools/coValues/deepLoading.ts +31 -0
- package/src/tools/coValues/group.ts +2 -2
- package/src/tools/coValues/interfaces.ts +19 -23
- package/src/tools/coValues/schemaUnion.ts +2 -2
- package/src/tools/config.ts +9 -0
- package/src/tools/exports.ts +4 -0
- package/src/tools/implementation/zodSchema/schemaTypes/AccountSchema.ts +2 -2
- package/src/tools/implementation/zodSchema/schemaTypes/CoDiscriminatedUnionSchema.ts +2 -2
- package/src/tools/implementation/zodSchema/schemaTypes/CoFeedSchema.ts +2 -2
- package/src/tools/implementation/zodSchema/schemaTypes/CoListSchema.ts +4 -4
- package/src/tools/implementation/zodSchema/schemaTypes/CoMapSchema.ts +4 -4
- package/src/tools/implementation/zodSchema/schemaTypes/CoRecordSchema.ts +4 -10
- package/src/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.ts +2 -2
- package/src/tools/implementation/zodSchema/schemaTypes/GroupSchema.ts +2 -2
- package/src/tools/implementation/zodSchema/schemaTypes/PlainTextSchema.ts +2 -2
- package/src/tools/implementation/zodSchema/schemaTypes/RichTextSchema.ts +2 -2
- package/src/tools/subscribe/JazzError.ts +9 -6
- package/src/tools/subscribe/SubscriptionScope.ts +126 -36
- package/src/tools/subscribe/errorReporting.ts +67 -0
- package/src/tools/tests/coDiscriminatedUnion.test.ts +69 -2
- package/src/tools/tests/deepLoading.test.ts +47 -47
- package/src/tools/tests/errorReporting.test.ts +103 -0
- package/src/tools/tests/load.test.ts +21 -1
- package/src/tools/tests/request.test.ts +2 -1
- package/src/tools/tests/subscribe.test.ts +44 -0
- package/tsup.config.ts +16 -0
- package/dist/chunk-GCBXV2KC.js.map +0 -1
|
@@ -24,6 +24,10 @@ import type {
|
|
|
24
24
|
} from "./types.js";
|
|
25
25
|
import { CoValueLoadingState, NotLoadedCoValueState } from "./types.js";
|
|
26
26
|
import { createCoValue, myRoleForRawValue } from "./utils.js";
|
|
27
|
+
import {
|
|
28
|
+
captureError,
|
|
29
|
+
isCustomErrorReportingEnabled,
|
|
30
|
+
} from "./errorReporting.js";
|
|
27
31
|
|
|
28
32
|
export class SubscriptionScope<D extends CoValue> {
|
|
29
33
|
childNodes = new Map<string, SubscriptionScope<CoValue>>();
|
|
@@ -58,6 +62,13 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
58
62
|
|
|
59
63
|
silenceUpdates = false;
|
|
60
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Stack trace captured at subscription creation time.
|
|
67
|
+
* This helps identify which component/hook created the subscription
|
|
68
|
+
* when debugging "value unavailable" errors.
|
|
69
|
+
*/
|
|
70
|
+
callerStack: Error | undefined;
|
|
71
|
+
|
|
61
72
|
constructor(
|
|
62
73
|
public node: LocalNode,
|
|
63
74
|
resolve: RefsToResolve<D>,
|
|
@@ -66,7 +77,10 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
66
77
|
public skipRetry = false,
|
|
67
78
|
public bestEffortResolution = false,
|
|
68
79
|
public unstable_branch?: BranchDefinition,
|
|
80
|
+
callerStack?: Error | undefined,
|
|
69
81
|
) {
|
|
82
|
+
// Use caller stack if provided, otherwise capture here (less useful but better than nothing)
|
|
83
|
+
this.callerStack = callerStack;
|
|
70
84
|
this.resolve = resolve;
|
|
71
85
|
this.value = { type: CoValueLoadingState.LOADING, id };
|
|
72
86
|
|
|
@@ -91,7 +105,11 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
91
105
|
// - Run the migration only once
|
|
92
106
|
// - Skip all the updates until the migration is done
|
|
93
107
|
// - Trigger handleUpdate only with the final value
|
|
94
|
-
if (
|
|
108
|
+
if (
|
|
109
|
+
!this.migrated &&
|
|
110
|
+
value !== CoValueLoadingState.UNAVAILABLE &&
|
|
111
|
+
hasAccessToCoValue(value)
|
|
112
|
+
) {
|
|
95
113
|
if (this.migrating) {
|
|
96
114
|
return;
|
|
97
115
|
}
|
|
@@ -122,44 +140,40 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
122
140
|
handleUpdate(update: RawCoValue | typeof CoValueLoadingState.UNAVAILABLE) {
|
|
123
141
|
if (update === CoValueLoadingState.UNAVAILABLE) {
|
|
124
142
|
if (this.value.type === CoValueLoadingState.LOADING) {
|
|
125
|
-
this.
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
id: this.id,
|
|
132
|
-
},
|
|
133
|
-
path: [],
|
|
143
|
+
const error = new JazzError(this.id, CoValueLoadingState.UNAVAILABLE, [
|
|
144
|
+
{
|
|
145
|
+
code: CoValueLoadingState.UNAVAILABLE,
|
|
146
|
+
message: `Jazz Unavailable Error: unable to load ${this.id}`,
|
|
147
|
+
params: {
|
|
148
|
+
id: this.id,
|
|
134
149
|
},
|
|
135
|
-
|
|
136
|
-
|
|
150
|
+
path: [],
|
|
151
|
+
},
|
|
152
|
+
]);
|
|
153
|
+
|
|
154
|
+
this.updateValue(error);
|
|
137
155
|
}
|
|
156
|
+
|
|
138
157
|
this.triggerUpdate();
|
|
139
158
|
return;
|
|
140
159
|
}
|
|
141
160
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
// Groups and accounts are accessible by everyone, for the other coValues we use the role to check access
|
|
145
|
-
const hasAccess =
|
|
146
|
-
ruleset.type !== "ownedByGroup" ||
|
|
147
|
-
myRoleForRawValue(update) !== undefined;
|
|
148
|
-
|
|
149
|
-
if (!hasAccess) {
|
|
161
|
+
if (!hasAccessToCoValue(update)) {
|
|
150
162
|
if (this.value.type !== CoValueLoadingState.UNAUTHORIZED) {
|
|
151
|
-
this.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
path: [],
|
|
163
|
+
const message = `Jazz Authorization Error: The current user (${this.node.getCurrentAgent().id}) is not authorized to access ${this.id}`;
|
|
164
|
+
|
|
165
|
+
const error = new JazzError(this.id, CoValueLoadingState.UNAUTHORIZED, [
|
|
166
|
+
{
|
|
167
|
+
code: CoValueLoadingState.UNAUTHORIZED,
|
|
168
|
+
message,
|
|
169
|
+
params: {
|
|
170
|
+
id: this.id,
|
|
160
171
|
},
|
|
161
|
-
|
|
162
|
-
|
|
172
|
+
path: [],
|
|
173
|
+
},
|
|
174
|
+
]);
|
|
175
|
+
|
|
176
|
+
this.updateValue(error);
|
|
163
177
|
this.triggerUpdate();
|
|
164
178
|
}
|
|
165
179
|
return;
|
|
@@ -299,6 +313,8 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
299
313
|
return unloadedValue;
|
|
300
314
|
}
|
|
301
315
|
|
|
316
|
+
lastErrorLogged: JazzError | undefined;
|
|
317
|
+
|
|
302
318
|
getCurrentValue(): MaybeLoaded<D> {
|
|
303
319
|
const rawValue = this.getCurrentRawValue();
|
|
304
320
|
|
|
@@ -307,6 +323,7 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
307
323
|
rawValue === CoValueLoadingState.UNAVAILABLE ||
|
|
308
324
|
rawValue === CoValueLoadingState.LOADING
|
|
309
325
|
) {
|
|
326
|
+
this.logError();
|
|
310
327
|
return this.getUnloadedValue(rawValue);
|
|
311
328
|
}
|
|
312
329
|
|
|
@@ -318,7 +335,6 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
318
335
|
this.value.type === CoValueLoadingState.UNAUTHORIZED ||
|
|
319
336
|
this.value.type === CoValueLoadingState.UNAVAILABLE
|
|
320
337
|
) {
|
|
321
|
-
console.error(this.value.toString());
|
|
322
338
|
return this.value.type;
|
|
323
339
|
}
|
|
324
340
|
|
|
@@ -327,7 +343,6 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
327
343
|
}
|
|
328
344
|
|
|
329
345
|
if (this.errorFromChildren) {
|
|
330
|
-
console.error(this.errorFromChildren.toString());
|
|
331
346
|
return this.errorFromChildren.type;
|
|
332
347
|
}
|
|
333
348
|
|
|
@@ -338,6 +353,71 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
338
353
|
return CoValueLoadingState.LOADING;
|
|
339
354
|
}
|
|
340
355
|
|
|
356
|
+
getCreationStackLines() {
|
|
357
|
+
const stack = this.callerStack?.stack;
|
|
358
|
+
|
|
359
|
+
if (!stack) {
|
|
360
|
+
return "";
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const creationStackLines = stack.split("\n").slice(2, 15);
|
|
364
|
+
const creationAppFrame = creationStackLines.find(
|
|
365
|
+
(line) =>
|
|
366
|
+
!line.includes("node_modules") &&
|
|
367
|
+
!line.includes("useCoValueSubscription") &&
|
|
368
|
+
!line.includes("useCoState") &&
|
|
369
|
+
!line.includes("useAccount") &&
|
|
370
|
+
!line.includes("jazz-tools"),
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
let result = "\n\n";
|
|
374
|
+
|
|
375
|
+
if (creationAppFrame) {
|
|
376
|
+
(result += "Subscription created "), (result += creationAppFrame.trim());
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
result += "\nFull subscription creation stack:";
|
|
380
|
+
for (const line of creationStackLines.slice(0, 8)) {
|
|
381
|
+
result += "\n " + line.trim();
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return result;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
logError() {
|
|
388
|
+
let error: JazzError | undefined;
|
|
389
|
+
|
|
390
|
+
if (
|
|
391
|
+
this.value.type === CoValueLoadingState.UNAUTHORIZED ||
|
|
392
|
+
this.value.type === CoValueLoadingState.UNAVAILABLE
|
|
393
|
+
) {
|
|
394
|
+
error = this.value;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (this.errorFromChildren) {
|
|
398
|
+
error = this.errorFromChildren;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (!error || this.lastErrorLogged === error) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (error.type === CoValueLoadingState.UNAVAILABLE && this.skipRetry) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
this.lastErrorLogged = error;
|
|
410
|
+
|
|
411
|
+
if (isCustomErrorReportingEnabled()) {
|
|
412
|
+
captureError(new Error(error.toString(), { cause: this.callerStack }), {
|
|
413
|
+
getPrettyStackTrace: () => this.getCreationStackLines(),
|
|
414
|
+
jazzError: error,
|
|
415
|
+
});
|
|
416
|
+
} else {
|
|
417
|
+
console.error(`${error.toString()}${this.getCreationStackLines()}`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
341
421
|
triggerUpdate() {
|
|
342
422
|
if (!this.shouldSendUpdates()) return;
|
|
343
423
|
if (!this.dirty) return;
|
|
@@ -580,7 +660,7 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
580
660
|
new JazzError(undefined, CoValueLoadingState.UNAVAILABLE, [
|
|
581
661
|
{
|
|
582
662
|
code: "validationError",
|
|
583
|
-
message: `The ref on position ${key}
|
|
663
|
+
message: `Jazz Validation Error: The ref on position ${key} is missing`,
|
|
584
664
|
params: {},
|
|
585
665
|
path: [key],
|
|
586
666
|
},
|
|
@@ -645,7 +725,7 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
645
725
|
new JazzError(undefined, CoValueLoadingState.UNAVAILABLE, [
|
|
646
726
|
{
|
|
647
727
|
code: "validationError",
|
|
648
|
-
message: `The ref ${key}
|
|
728
|
+
message: `Jazz Validation Error: The ref ${key} is required but missing`,
|
|
649
729
|
params: {},
|
|
650
730
|
path: [key],
|
|
651
731
|
},
|
|
@@ -684,7 +764,7 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
684
764
|
new JazzError(undefined, CoValueLoadingState.UNAVAILABLE, [
|
|
685
765
|
{
|
|
686
766
|
code: "validationError",
|
|
687
|
-
message: `The ref on position ${key}
|
|
767
|
+
message: `Jazz Validation Error: The ref on position ${key} is required but missing`,
|
|
688
768
|
params: {},
|
|
689
769
|
path: [key],
|
|
690
770
|
},
|
|
@@ -759,3 +839,13 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
759
839
|
this.childNodes.forEach((child) => child.destroy());
|
|
760
840
|
}
|
|
761
841
|
}
|
|
842
|
+
|
|
843
|
+
function hasAccessToCoValue(rawCoValue: RawCoValue): boolean {
|
|
844
|
+
const ruleset = rawCoValue.core.verified.header.ruleset;
|
|
845
|
+
|
|
846
|
+
// Groups and accounts are accessible by everyone, for the other coValues we use the role to check access
|
|
847
|
+
return (
|
|
848
|
+
ruleset.type !== "ownedByGroup" ||
|
|
849
|
+
myRoleForRawValue(rawCoValue) !== undefined
|
|
850
|
+
);
|
|
851
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { JazzError } from "./JazzError";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A platform agnostic way to check if we're in development mode
|
|
5
|
+
*
|
|
6
|
+
* Works in Node.js and bundled code, falls back to false if process is not available
|
|
7
|
+
*/
|
|
8
|
+
const isDev = (function () {
|
|
9
|
+
try {
|
|
10
|
+
return process.env.NODE_ENV === "development";
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
})();
|
|
15
|
+
|
|
16
|
+
type CustomErrorReporterProps = {
|
|
17
|
+
getPrettyStackTrace: () => string;
|
|
18
|
+
jazzError: JazzError;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type CustomErrorReporter = (
|
|
22
|
+
error: Error,
|
|
23
|
+
props: CustomErrorReporterProps,
|
|
24
|
+
) => void;
|
|
25
|
+
|
|
26
|
+
let customErrorReporter: CustomErrorReporter | undefined;
|
|
27
|
+
let captureErrorCause: boolean = isDev;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Turns on the additonal debug info coming from React hooks on the original subscription of the errors.
|
|
31
|
+
*
|
|
32
|
+
* Enabled by default in development mode.
|
|
33
|
+
*/
|
|
34
|
+
export function enableCaptureErrorCause(capture: boolean) {
|
|
35
|
+
captureErrorCause = capture;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Set a custom error reporter to be used instead of the default console.error.
|
|
40
|
+
*
|
|
41
|
+
* Useful for sending errors to a logging service or silence some annoying errors in production.
|
|
42
|
+
*/
|
|
43
|
+
export function setCustomErrorReporter(reporter?: CustomErrorReporter) {
|
|
44
|
+
customErrorReporter = reporter;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Check if we're in development mode.
|
|
49
|
+
* Stack traces are only captured in development to avoid overhead in production.
|
|
50
|
+
*/
|
|
51
|
+
export function isCustomErrorReportingEnabled(): boolean {
|
|
52
|
+
return customErrorReporter !== undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Capture a stack trace only in development mode.
|
|
57
|
+
* Returns undefined in production to avoid overhead.
|
|
58
|
+
*/
|
|
59
|
+
export function captureStack() {
|
|
60
|
+
return captureErrorCause ? new Error() : undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function captureError(error: Error, props: CustomErrorReporterProps) {
|
|
64
|
+
if (customErrorReporter) {
|
|
65
|
+
customErrorReporter(error, props);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
|
-
import {
|
|
1
|
+
import { assert, beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
CoPlainText,
|
|
4
|
+
CoValueLoadingState,
|
|
5
|
+
Group,
|
|
6
|
+
Loaded,
|
|
7
|
+
co,
|
|
8
|
+
loadCoValue,
|
|
9
|
+
z,
|
|
10
|
+
} from "../exports.js";
|
|
3
11
|
import { createJazzTestAccount, setupJazzTestSync } from "../testing.js";
|
|
4
12
|
import { assertLoaded, waitFor } from "./utils.js";
|
|
5
13
|
import type { Account } from "jazz-tools";
|
|
@@ -713,4 +721,63 @@ describe("co.discriminatedUnion", () => {
|
|
|
713
721
|
}
|
|
714
722
|
});
|
|
715
723
|
});
|
|
724
|
+
|
|
725
|
+
test("cannot create a co.discriminatedUnion member if it has no matching discriminator value", async () => {
|
|
726
|
+
const Dog = co.map({
|
|
727
|
+
type: z.literal("dog"),
|
|
728
|
+
name: z.string(),
|
|
729
|
+
});
|
|
730
|
+
const Cat = co.map({
|
|
731
|
+
type: z.literal("cat"),
|
|
732
|
+
name: z.string(),
|
|
733
|
+
});
|
|
734
|
+
const Parrot = co.map({
|
|
735
|
+
type2: z.literal("parrot"),
|
|
736
|
+
name: z.string(),
|
|
737
|
+
});
|
|
738
|
+
const Pet = co.discriminatedUnion("type", [Dog, Cat, Parrot]);
|
|
739
|
+
const Pets = co.list(Pet);
|
|
740
|
+
|
|
741
|
+
expect(() => Pets.create([{ type2: "parrot", name: "Polly" }])).toThrow(
|
|
742
|
+
"co.discriminatedUnion() of collaborative types with no matching discriminator value found",
|
|
743
|
+
);
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
test("can load a discriminated union CoList even if some elements are not accessible", async () => {
|
|
747
|
+
const Dog = co.map({
|
|
748
|
+
type: z.literal("dog"),
|
|
749
|
+
name: z.string(),
|
|
750
|
+
});
|
|
751
|
+
const Cat = co.map({
|
|
752
|
+
type: z.literal("cat"),
|
|
753
|
+
name: z.string(),
|
|
754
|
+
});
|
|
755
|
+
const Pet = co.discriminatedUnion("type", [Dog, Cat]);
|
|
756
|
+
const Pets = co.list(Pet);
|
|
757
|
+
|
|
758
|
+
const publicGroup = Group.create().makePublic();
|
|
759
|
+
const privateGroup = Group.create();
|
|
760
|
+
const pets = Pets.create(
|
|
761
|
+
[
|
|
762
|
+
Dog.create({ type: "dog", name: "Rex" }, publicGroup),
|
|
763
|
+
Cat.create({ type: "cat", name: "Whiskers" }, privateGroup),
|
|
764
|
+
],
|
|
765
|
+
{ owner: publicGroup },
|
|
766
|
+
);
|
|
767
|
+
|
|
768
|
+
const anotherAccount = await createJazzTestAccount();
|
|
769
|
+
const loadedPets = await Pets.load(pets.$jazz.id, {
|
|
770
|
+
resolve: { $each: { $onError: "catch" } },
|
|
771
|
+
loadAs: anotherAccount,
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
assertLoaded(loadedPets);
|
|
775
|
+
expect(loadedPets.length).toEqual(2);
|
|
776
|
+
assert(loadedPets[0]);
|
|
777
|
+
assertLoaded(loadedPets[0]);
|
|
778
|
+
expect(loadedPets[0].type).toEqual("dog");
|
|
779
|
+
expect(loadedPets[1]?.$jazz.loadingState).toEqual(
|
|
780
|
+
CoValueLoadingState.UNAUTHORIZED,
|
|
781
|
+
);
|
|
782
|
+
});
|
|
716
783
|
});
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { cojsonInternals } from "cojson";
|
|
2
2
|
import { WasmCrypto } from "cojson/crypto/WasmCrypto";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
assert,
|
|
5
|
+
beforeEach,
|
|
6
|
+
describe,
|
|
7
|
+
expect,
|
|
8
|
+
expectTypeOf,
|
|
9
|
+
test,
|
|
10
|
+
vi,
|
|
11
|
+
} from "vitest";
|
|
4
12
|
import {
|
|
5
13
|
Group,
|
|
6
14
|
ID,
|
|
@@ -11,17 +19,17 @@ import {
|
|
|
11
19
|
} from "../index.js";
|
|
12
20
|
import {
|
|
13
21
|
Account,
|
|
14
|
-
CoList,
|
|
15
22
|
Loaded,
|
|
16
23
|
MaybeLoaded,
|
|
17
|
-
|
|
24
|
+
Settled,
|
|
18
25
|
co,
|
|
19
26
|
randomSessionProvider,
|
|
20
27
|
CoValueLoadingState,
|
|
21
|
-
|
|
28
|
+
CoValueErrorState,
|
|
22
29
|
} from "../internal.js";
|
|
23
30
|
import { createJazzTestAccount, linkAccounts } from "../testing.js";
|
|
24
31
|
import { assertLoaded, waitFor } from "./utils.js";
|
|
32
|
+
import { setCustomErrorReporter } from "../config.js";
|
|
25
33
|
|
|
26
34
|
const Crypto = await WasmCrypto.create();
|
|
27
35
|
const { connectedPeers } = cojsonInternals;
|
|
@@ -43,6 +51,15 @@ const TestMap = co.map({
|
|
|
43
51
|
optionalRef: co.optional(InnermostMap),
|
|
44
52
|
});
|
|
45
53
|
|
|
54
|
+
let lastError: Error | undefined;
|
|
55
|
+
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
lastError = undefined;
|
|
58
|
+
setCustomErrorReporter((error) => {
|
|
59
|
+
lastError = error;
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
46
63
|
describe("Deep loading with depth arg", async () => {
|
|
47
64
|
const me = await Account.create({
|
|
48
65
|
creationProps: { name: "Hermes Puggington" },
|
|
@@ -345,7 +362,7 @@ test("Deep loading a record-like coMap", async () => {
|
|
|
345
362
|
},
|
|
346
363
|
});
|
|
347
364
|
expectTypeOf(recordLoaded).branded.toEqualTypeOf<
|
|
348
|
-
|
|
365
|
+
Settled<
|
|
349
366
|
Loaded<typeof RecordLike> & {
|
|
350
367
|
readonly [key: string]: Loaded<typeof TestMap> & {
|
|
351
368
|
readonly list: Loaded<typeof TestList> &
|
|
@@ -494,17 +511,14 @@ describe("Deep loading with unauthorized account", async () => {
|
|
|
494
511
|
CoValueLoadingState.UNAUTHORIZED,
|
|
495
512
|
);
|
|
496
513
|
|
|
497
|
-
expect(
|
|
498
|
-
`The current user (${alice.$jazz.id}) is not authorized to access
|
|
514
|
+
expect(lastError?.message).toBe(
|
|
515
|
+
`Jazz Authorization Error: The current user (${alice.$jazz.id}) is not authorized to access ${map.$jazz.id}`,
|
|
499
516
|
);
|
|
500
|
-
|
|
501
|
-
errorSpy.mockReset();
|
|
502
517
|
});
|
|
503
518
|
|
|
504
519
|
test("unaccessible list", async () => {
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
const map = TestMap.create({ list: TestList.create([], onlyBob) }, group);
|
|
520
|
+
const innerList = TestList.create([], onlyBob);
|
|
521
|
+
const map = TestMap.create({ list: innerList }, group);
|
|
508
522
|
|
|
509
523
|
const mapOnAlice = await TestMap.load(map.$jazz.id, { loadAs: alice });
|
|
510
524
|
expect(mapOnAlice).toBeTruthy();
|
|
@@ -518,16 +532,12 @@ describe("Deep loading with unauthorized account", async () => {
|
|
|
518
532
|
CoValueLoadingState.UNAUTHORIZED,
|
|
519
533
|
);
|
|
520
534
|
|
|
521
|
-
expect(
|
|
522
|
-
`The current user (${alice.$jazz.id}) is not authorized to access
|
|
535
|
+
expect(lastError?.message).toBe(
|
|
536
|
+
`Jazz Authorization Error: The current user (${alice.$jazz.id}) is not authorized to access ${innerList.$jazz.id}. Subscription starts from ${map.$jazz.id} and the value is on path list`,
|
|
523
537
|
);
|
|
524
|
-
|
|
525
|
-
errorSpy.mockReset();
|
|
526
538
|
});
|
|
527
539
|
|
|
528
540
|
test("unaccessible list element", async () => {
|
|
529
|
-
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
530
|
-
|
|
531
541
|
const map = TestMap.create(
|
|
532
542
|
{
|
|
533
543
|
list: TestList.create(
|
|
@@ -554,16 +564,12 @@ describe("Deep loading with unauthorized account", async () => {
|
|
|
554
564
|
CoValueLoadingState.UNAUTHORIZED,
|
|
555
565
|
);
|
|
556
566
|
|
|
557
|
-
expect(
|
|
558
|
-
`The current user (${alice.$jazz.id}) is not authorized to access
|
|
567
|
+
expect(lastError?.message).toBe(
|
|
568
|
+
`Jazz Authorization Error: The current user (${alice.$jazz.id}) is not authorized to access ${map.list[0]!.$jazz.id}. Subscription starts from ${map.$jazz.id} and the value is on path list.0`,
|
|
559
569
|
);
|
|
560
|
-
|
|
561
|
-
errorSpy.mockReset();
|
|
562
570
|
});
|
|
563
571
|
|
|
564
572
|
test("unaccessible optional element", async () => {
|
|
565
|
-
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
566
|
-
|
|
567
573
|
const map = TestMap.create(
|
|
568
574
|
{
|
|
569
575
|
list: TestList.create([], group),
|
|
@@ -579,11 +585,9 @@ describe("Deep loading with unauthorized account", async () => {
|
|
|
579
585
|
expect(mapOnAlice.$jazz.loadingState).toBe(
|
|
580
586
|
CoValueLoadingState.UNAUTHORIZED,
|
|
581
587
|
);
|
|
582
|
-
expect(
|
|
583
|
-
`The current user (${alice.$jazz.id}) is not authorized to access
|
|
588
|
+
expect(lastError?.message).toBe(
|
|
589
|
+
`Jazz Authorization Error: The current user (${alice.$jazz.id}) is not authorized to access ${map.optionalRef!.$jazz.id}. Subscription starts from ${map.$jazz.id} and the value is on path optionalRef`,
|
|
584
590
|
);
|
|
585
|
-
|
|
586
|
-
errorSpy.mockReset();
|
|
587
591
|
});
|
|
588
592
|
|
|
589
593
|
test("unaccessible optional element via autoload", async () => {
|
|
@@ -611,17 +615,20 @@ describe("Deep loading with unauthorized account", async () => {
|
|
|
611
615
|
});
|
|
612
616
|
|
|
613
617
|
expect(result?.$jazz.loadingState).toBe(CoValueLoadingState.UNAUTHORIZED);
|
|
618
|
+
expect(lastError?.message).toBe(
|
|
619
|
+
`Jazz Authorization Error: The current user (${alice.$jazz.id}) is not authorized to access ${map.optionalRef!.$jazz.id}`,
|
|
620
|
+
);
|
|
614
621
|
});
|
|
615
622
|
|
|
616
623
|
test("unaccessible stream", async () => {
|
|
617
|
-
const
|
|
624
|
+
const stream = TestFeed.create([], onlyBob);
|
|
618
625
|
const map = TestMap.create(
|
|
619
626
|
{
|
|
620
627
|
list: TestList.create(
|
|
621
628
|
[
|
|
622
629
|
InnerMap.create(
|
|
623
630
|
{
|
|
624
|
-
stream
|
|
631
|
+
stream,
|
|
625
632
|
},
|
|
626
633
|
group,
|
|
627
634
|
),
|
|
@@ -641,16 +648,12 @@ describe("Deep loading with unauthorized account", async () => {
|
|
|
641
648
|
CoValueLoadingState.UNAUTHORIZED,
|
|
642
649
|
);
|
|
643
650
|
|
|
644
|
-
expect(
|
|
645
|
-
`The current user (${alice.$jazz.id}) is not authorized to access
|
|
651
|
+
expect(lastError?.message).toBe(
|
|
652
|
+
`Jazz Authorization Error: The current user (${alice.$jazz.id}) is not authorized to access ${stream.$jazz.id}. Subscription starts from ${map.$jazz.id} and the value is on path list.0.stream`,
|
|
646
653
|
);
|
|
647
|
-
|
|
648
|
-
errorSpy.mockReset();
|
|
649
654
|
});
|
|
650
655
|
|
|
651
656
|
test("unaccessible stream element", async () => {
|
|
652
|
-
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
653
|
-
|
|
654
657
|
const value = InnermostMap.create({ value: "hello" }, onlyBob);
|
|
655
658
|
|
|
656
659
|
const map = TestMap.create(
|
|
@@ -679,11 +682,9 @@ describe("Deep loading with unauthorized account", async () => {
|
|
|
679
682
|
CoValueLoadingState.UNAUTHORIZED,
|
|
680
683
|
);
|
|
681
684
|
|
|
682
|
-
expect(
|
|
683
|
-
`The current user (${alice.$jazz.id}) is not authorized to access
|
|
685
|
+
expect(lastError?.message).toBe(
|
|
686
|
+
`Jazz Authorization Error: The current user (${alice.$jazz.id}) is not authorized to access ${value.$jazz.id}. Subscription starts from ${map.$jazz.id} and the value is on path list.0.stream.${value.$jazz.id}`,
|
|
684
687
|
);
|
|
685
|
-
|
|
686
|
-
errorSpy.mockReset();
|
|
687
688
|
});
|
|
688
689
|
|
|
689
690
|
test("setting undefined via proxy", async () => {
|
|
@@ -1229,7 +1230,7 @@ describe("$isLoaded", async () => {
|
|
|
1229
1230
|
|
|
1230
1231
|
const map = TestMap.create({ list: [] }, { owner: me });
|
|
1231
1232
|
|
|
1232
|
-
test("$isLoaded narrows
|
|
1233
|
+
test("$isLoaded narrows a maybe-loaded CoValue to a loaded CoValue", async () => {
|
|
1233
1234
|
const maybeLoadedMap = await TestMap.load(map.$jazz.id, {
|
|
1234
1235
|
loadAs: me,
|
|
1235
1236
|
});
|
|
@@ -1244,19 +1245,18 @@ describe("$isLoaded", async () => {
|
|
|
1244
1245
|
} else {
|
|
1245
1246
|
expectTypeOf(
|
|
1246
1247
|
maybeLoadedMap.$jazz.loadingState,
|
|
1247
|
-
).toEqualTypeOf<
|
|
1248
|
+
).toEqualTypeOf<CoValueErrorState>();
|
|
1248
1249
|
}
|
|
1249
1250
|
});
|
|
1250
1251
|
|
|
1251
|
-
test("$isLoaded narrows
|
|
1252
|
+
test("$isLoaded narrows a maybe-loaded CoValue to a not loaded CoValue", async () => {
|
|
1252
1253
|
const otherAccount = await Account.create({
|
|
1253
1254
|
creationProps: { name: "Other Account" },
|
|
1254
1255
|
crypto: Crypto,
|
|
1255
1256
|
});
|
|
1256
|
-
const unloadedMap
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
);
|
|
1257
|
+
const unloadedMap = await TestMap.load(map.$jazz.id, {
|
|
1258
|
+
loadAs: otherAccount,
|
|
1259
|
+
});
|
|
1260
1260
|
|
|
1261
1261
|
expect(unloadedMap.$isLoaded).toBe(false);
|
|
1262
1262
|
if (!unloadedMap.$isLoaded) {
|