jazz-tools 0.20.0 → 0.20.2
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 +56 -56
- package/CHANGELOG.md +21 -0
- package/dist/{chunk-3CAPPS2F.js → chunk-Q5RNSSUM.js} +269 -95
- package/dist/chunk-Q5RNSSUM.js.map +1 -0
- package/dist/chunk-ZQWSQH6L.js +20 -0
- package/dist/index.js +2 -2
- package/dist/inspector/{chunk-MCTB5ZJC.js → chunk-6JPVMI3V.js} +302 -182
- package/dist/inspector/chunk-6JPVMI3V.js.map +1 -0
- package/dist/inspector/{custom-element-5YWVZBWA.js → custom-element-PWRX4VCA.js} +1337 -206
- package/dist/inspector/custom-element-PWRX4VCA.js.map +1 -0
- package/dist/inspector/in-app.d.ts +1 -0
- package/dist/inspector/in-app.d.ts.map +1 -1
- package/dist/inspector/index.d.ts +1 -0
- package/dist/inspector/index.d.ts.map +1 -1
- package/dist/inspector/index.js +1044 -17
- package/dist/inspector/index.js.map +1 -1
- package/dist/inspector/pages/home.d.ts +4 -1
- package/dist/inspector/pages/home.d.ts.map +1 -1
- package/dist/inspector/pages/performance/PerformancePage.d.ts +7 -0
- package/dist/inspector/pages/performance/PerformancePage.d.ts.map +1 -0
- package/dist/inspector/pages/performance/SubscriptionDetailPanel.d.ts +8 -0
- package/dist/inspector/pages/performance/SubscriptionDetailPanel.d.ts.map +1 -0
- package/dist/inspector/pages/performance/SubscriptionRow.d.ts +11 -0
- package/dist/inspector/pages/performance/SubscriptionRow.d.ts.map +1 -0
- package/dist/inspector/pages/performance/Timeline.d.ts +12 -0
- package/dist/inspector/pages/performance/Timeline.d.ts.map +1 -0
- package/dist/inspector/pages/performance/helpers.d.ts +5 -0
- package/dist/inspector/pages/performance/helpers.d.ts.map +1 -0
- package/dist/inspector/pages/performance/index.d.ts +3 -0
- package/dist/inspector/pages/performance/index.d.ts.map +1 -0
- package/dist/inspector/pages/performance/types.d.ts +13 -0
- package/dist/inspector/pages/performance/types.d.ts.map +1 -0
- package/dist/inspector/pages/performance/usePerformanceEntries.d.ts +3 -0
- package/dist/inspector/pages/performance/usePerformanceEntries.d.ts.map +1 -0
- package/dist/inspector/register-custom-element.js +3 -1
- package/dist/inspector/register-custom-element.js.map +1 -1
- package/dist/inspector/standalone.js +1 -1
- package/dist/inspector/tests/pages/performance/PerformancePage.test.d.ts +2 -0
- package/dist/inspector/tests/pages/performance/PerformancePage.test.d.ts.map +1 -0
- package/dist/inspector/tests/pages/performance/SubscriptionDetailPanel.test.d.ts +2 -0
- package/dist/inspector/tests/pages/performance/SubscriptionDetailPanel.test.d.ts.map +1 -0
- package/dist/inspector/tests/pages/performance/SubscriptionRow.test.d.ts +2 -0
- package/dist/inspector/tests/pages/performance/SubscriptionRow.test.d.ts.map +1 -0
- package/dist/inspector/tests/pages/performance/Timeline.test.d.ts +2 -0
- package/dist/inspector/tests/pages/performance/Timeline.test.d.ts.map +1 -0
- package/dist/inspector/tests/pages/performance/helpers.test.d.ts +2 -0
- package/dist/inspector/tests/pages/performance/helpers.test.d.ts.map +1 -0
- package/dist/inspector/viewer/delete-local-data.d.ts.map +1 -1
- package/dist/inspector/viewer/header.d.ts +4 -2
- package/dist/inspector/viewer/header.d.ts.map +1 -1
- package/dist/inspector/viewer/page-stack.d.ts +3 -1
- package/dist/inspector/viewer/page-stack.d.ts.map +1 -1
- package/dist/react-core/hooks.d.ts +2 -2
- package/dist/react-core/hooks.d.ts.map +1 -1
- package/dist/react-core/index.js +50 -18
- package/dist/react-core/index.js.map +1 -1
- package/dist/react-core/subscription-provider.d.ts.map +1 -1
- package/dist/react-native-core/media/image.d.ts +1 -1
- package/dist/svelte/jazz.class.svelte.d.ts.map +1 -1
- package/dist/svelte/jazz.class.svelte.js +27 -22
- package/dist/testing.js +2 -2
- package/dist/tools/coValues/interfaces.d.ts.map +1 -1
- package/dist/tools/exports.d.ts +1 -1
- package/dist/tools/exports.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/runtimeConverters/coValueSchemaTransformation.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/AccountSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoFeedSchema.d.ts +2 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoFeedSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoListSchema.d.ts +2 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoListSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoMapSchema.d.ts +2 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoMapSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoVectorSchema.d.ts +3 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/CoVectorSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.d.ts +3 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/PlainTextSchema.d.ts +2 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/PlainTextSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/RichTextSchema.d.ts +6 -1
- 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/ssr.js +1 -1
- package/dist/tools/subscribe/SubscriptionCache.d.ts +2 -2
- package/dist/tools/subscribe/SubscriptionCache.d.ts.map +1 -1
- package/dist/tools/subscribe/SubscriptionScope.d.ts +19 -12
- package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
- package/dist/tools/subscribe/errorReporting.d.ts +6 -0
- package/dist/tools/subscribe/errorReporting.d.ts.map +1 -1
- package/dist/tools/subscribe/index.d.ts +4 -4
- package/dist/tools/subscribe/index.d.ts.map +1 -1
- package/dist/tools/subscribe/types.d.ts +48 -3
- package/dist/tools/subscribe/types.d.ts.map +1 -1
- package/dist/tools/subscribe/utils.d.ts +1 -1
- package/dist/tools/subscribe/utils.d.ts.map +1 -1
- package/dist/tools/tests/SubscriptionScope.performance.test.d.ts +2 -0
- package/dist/tools/tests/SubscriptionScope.performance.test.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/inspector/in-app.tsx +41 -3
- package/src/inspector/index.tsx +5 -1
- package/src/inspector/pages/home.tsx +26 -3
- package/src/inspector/pages/performance/PerformancePage.tsx +215 -0
- package/src/inspector/pages/performance/SubscriptionDetailPanel.tsx +182 -0
- package/src/inspector/pages/performance/SubscriptionRow.tsx +242 -0
- package/src/inspector/pages/performance/Timeline.tsx +513 -0
- package/src/inspector/pages/performance/helpers.ts +70 -0
- package/src/inspector/pages/performance/index.ts +2 -0
- package/src/inspector/pages/performance/types.ts +12 -0
- package/src/inspector/pages/performance/usePerformanceEntries.ts +53 -0
- package/src/inspector/register-custom-element.ts +3 -0
- package/src/inspector/tests/pages/performance/PerformancePage.test.tsx +83 -0
- package/src/inspector/tests/pages/performance/SubscriptionDetailPanel.test.tsx +68 -0
- package/src/inspector/tests/pages/performance/SubscriptionRow.test.tsx +93 -0
- package/src/inspector/tests/pages/performance/Timeline.test.tsx +57 -0
- package/src/inspector/tests/pages/performance/helpers.test.ts +91 -0
- package/src/inspector/viewer/delete-local-data.tsx +24 -5
- package/src/inspector/viewer/header.tsx +96 -17
- package/src/inspector/viewer/page-stack.tsx +22 -18
- package/src/react-core/hooks.ts +34 -4
- package/src/react-core/subscription-provider.tsx +17 -8
- package/src/svelte/jazz.class.svelte.ts +51 -33
- package/src/tools/coValues/coList.ts +73 -37
- package/src/tools/coValues/interfaces.ts +3 -0
- package/src/tools/exports.ts +1 -0
- package/src/tools/implementation/zodSchema/runtimeConverters/coValueSchemaTransformation.ts +13 -0
- package/src/tools/implementation/zodSchema/schemaTypes/AccountSchema.ts +0 -2
- package/src/tools/implementation/zodSchema/schemaTypes/CoFeedSchema.ts +5 -2
- package/src/tools/implementation/zodSchema/schemaTypes/CoListSchema.ts +5 -2
- package/src/tools/implementation/zodSchema/schemaTypes/CoMapSchema.ts +5 -2
- package/src/tools/implementation/zodSchema/schemaTypes/CoVectorSchema.ts +6 -2
- package/src/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.ts +6 -2
- package/src/tools/implementation/zodSchema/schemaTypes/PlainTextSchema.ts +5 -2
- package/src/tools/implementation/zodSchema/schemaTypes/RichTextSchema.ts +9 -2
- package/src/tools/subscribe/SubscriptionCache.ts +6 -4
- package/src/tools/subscribe/SubscriptionScope.ts +141 -23
- package/src/tools/subscribe/errorReporting.ts +1 -1
- package/src/tools/subscribe/index.ts +1 -1
- package/src/tools/subscribe/types.ts +62 -9
- package/src/tools/subscribe/utils.ts +2 -2
- package/src/tools/tests/SubscriptionScope.performance.test.ts +149 -0
- package/src/tools/tests/coList.test.ts +262 -0
- package/src/tools/tests/schema.withPermissions.test.ts +27 -4
- package/dist/chunk-3CAPPS2F.js.map +0 -1
- package/dist/chunk-PZ5AY32C.js +0 -10
- package/dist/inspector/chunk-MCTB5ZJC.js.map +0 -1
- package/dist/inspector/custom-element-5YWVZBWA.js.map +0 -1
- /package/dist/{chunk-PZ5AY32C.js.map → chunk-ZQWSQH6L.js.map} +0 -0
|
@@ -33,7 +33,14 @@ export class RichTextSchema implements CoreRichTextSchema {
|
|
|
33
33
|
readonly builtin = "CoRichText" as const;
|
|
34
34
|
readonly resolveQuery = true as const;
|
|
35
35
|
|
|
36
|
-
permissions: SchemaPermissions =
|
|
36
|
+
#permissions: SchemaPermissions | null = null;
|
|
37
|
+
/**
|
|
38
|
+
* Permissions to be used when creating or composing CoValues
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
get permissions(): SchemaPermissions {
|
|
42
|
+
return this.#permissions ?? DEFAULT_SCHEMA_PERMISSIONS;
|
|
43
|
+
}
|
|
37
44
|
|
|
38
45
|
constructor(private coValueClass: typeof CoRichText) {}
|
|
39
46
|
|
|
@@ -102,7 +109,7 @@ export class RichTextSchema implements CoreRichTextSchema {
|
|
|
102
109
|
*/
|
|
103
110
|
withPermissions(permissions: SchemaPermissions): RichTextSchema {
|
|
104
111
|
const copy = new RichTextSchema(this.coValueClass);
|
|
105
|
-
copy
|
|
112
|
+
copy.#permissions = permissions;
|
|
106
113
|
return copy;
|
|
107
114
|
}
|
|
108
115
|
}
|
|
@@ -2,6 +2,7 @@ import { LocalNode } from "cojson";
|
|
|
2
2
|
import type {
|
|
3
3
|
CoValue,
|
|
4
4
|
CoValueClassOrSchema,
|
|
5
|
+
Loaded,
|
|
5
6
|
RefEncoded,
|
|
6
7
|
RefsToResolve,
|
|
7
8
|
ResolveQuery,
|
|
@@ -188,7 +189,7 @@ export class SubscriptionCache {
|
|
|
188
189
|
skipRetry?: boolean,
|
|
189
190
|
bestEffortResolution?: boolean,
|
|
190
191
|
branch?: BranchDefinition,
|
|
191
|
-
): SubscriptionScope<
|
|
192
|
+
): SubscriptionScope<Loaded<S, ResolveQuery<S>>> {
|
|
192
193
|
// Handle undefined/null id case
|
|
193
194
|
if (!id) {
|
|
194
195
|
throw new Error("Cannot create subscription with undefined or null id");
|
|
@@ -201,7 +202,9 @@ export class SubscriptionCache {
|
|
|
201
202
|
// Found existing entry - cancel any pending cleanup since we're reusing it
|
|
202
203
|
this.cancelCleanup(matchingEntry);
|
|
203
204
|
|
|
204
|
-
return matchingEntry.subscriptionScope as SubscriptionScope<
|
|
205
|
+
return matchingEntry.subscriptionScope as SubscriptionScope<
|
|
206
|
+
Loaded<S, ResolveQuery<S>>
|
|
207
|
+
>;
|
|
205
208
|
}
|
|
206
209
|
|
|
207
210
|
// Create new SubscriptionScope
|
|
@@ -212,9 +215,8 @@ export class SubscriptionCache {
|
|
|
212
215
|
};
|
|
213
216
|
|
|
214
217
|
// Create new SubscriptionScope with all required parameters
|
|
215
|
-
const subscriptionScope = new SubscriptionScope<
|
|
218
|
+
const subscriptionScope = new SubscriptionScope<Loaded<S, ResolveQuery<S>>>(
|
|
216
219
|
node,
|
|
217
|
-
// @ts-expect-error the SubscriptionScope is too generic for TS to infer its instances are CoValues
|
|
218
220
|
resolve,
|
|
219
221
|
id,
|
|
220
222
|
refEncoded,
|
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
CoList,
|
|
5
5
|
CoMap,
|
|
6
6
|
type CoValue,
|
|
7
|
-
type ID,
|
|
8
7
|
MaybeLoaded,
|
|
9
8
|
NotLoaded,
|
|
10
9
|
type RefEncoded,
|
|
@@ -23,6 +22,7 @@ import {
|
|
|
23
22
|
} from "./JazzError.js";
|
|
24
23
|
import type {
|
|
25
24
|
BranchDefinition,
|
|
25
|
+
SubscriptionPerformanceDetail,
|
|
26
26
|
SubscriptionValue,
|
|
27
27
|
SubscriptionValueLoading,
|
|
28
28
|
} from "./types.js";
|
|
@@ -30,6 +30,7 @@ import { CoValueLoadingState, NotLoadedCoValueState } from "./types.js";
|
|
|
30
30
|
import {
|
|
31
31
|
captureError,
|
|
32
32
|
isCustomErrorReportingEnabled,
|
|
33
|
+
isDev,
|
|
33
34
|
} from "./errorReporting.js";
|
|
34
35
|
import {
|
|
35
36
|
createCoValue,
|
|
@@ -40,11 +41,21 @@ import {
|
|
|
40
41
|
} from "./utils.js";
|
|
41
42
|
|
|
42
43
|
export class SubscriptionScope<D extends CoValue> {
|
|
44
|
+
static isProfilingEnabled = isDev;
|
|
45
|
+
|
|
46
|
+
static setProfilingEnabled(enabled: boolean) {
|
|
47
|
+
this.isProfilingEnabled = enabled;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
static enableProfiling() {
|
|
51
|
+
this.isProfilingEnabled = true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private performanceUuid: string | undefined;
|
|
55
|
+
private performanceSource: string | undefined;
|
|
56
|
+
|
|
43
57
|
childNodes = new Map<string, SubscriptionScope<CoValue>>();
|
|
44
|
-
childValues: Map<string, SubscriptionValue<
|
|
45
|
-
string,
|
|
46
|
-
SubscriptionValue<D, any>
|
|
47
|
-
>();
|
|
58
|
+
childValues: Map<string, SubscriptionValue<CoValue>> = new Map();
|
|
48
59
|
/**
|
|
49
60
|
* Explicitly-loaded child ids that are unloaded
|
|
50
61
|
*/
|
|
@@ -53,7 +64,7 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
53
64
|
* Autoloaded child ids that are unloaded
|
|
54
65
|
*/
|
|
55
66
|
private pendingAutoloadedChildren: Set<string> = new Set();
|
|
56
|
-
value: SubscriptionValue<D
|
|
67
|
+
value: SubscriptionValue<D> | SubscriptionValueLoading;
|
|
57
68
|
private childErrors: Map<string, JazzError> = new Map();
|
|
58
69
|
private validationErrors: Map<string, JazzError> = new Map();
|
|
59
70
|
errorFromChildren: JazzError | undefined;
|
|
@@ -81,9 +92,9 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
81
92
|
|
|
82
93
|
constructor(
|
|
83
94
|
public node: LocalNode,
|
|
84
|
-
resolve: RefsToResolve<
|
|
85
|
-
public id:
|
|
86
|
-
public schema: RefEncoded<
|
|
95
|
+
resolve: RefsToResolve<any>,
|
|
96
|
+
public id: string,
|
|
97
|
+
public schema: RefEncoded<CoValue>,
|
|
87
98
|
public skipRetry = false,
|
|
88
99
|
public bestEffortResolution = false,
|
|
89
100
|
public unstable_branch?: BranchDefinition,
|
|
@@ -141,7 +152,110 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
141
152
|
);
|
|
142
153
|
}
|
|
143
154
|
|
|
144
|
-
|
|
155
|
+
trackLoadingPerformance(source: string) {
|
|
156
|
+
if (!SubscriptionScope.isProfilingEnabled) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Already tracking this subscription
|
|
161
|
+
if (this.performanceUuid) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const currentState = this.getCurrentRawValue();
|
|
166
|
+
|
|
167
|
+
this.performanceUuid = crypto.randomUUID();
|
|
168
|
+
this.performanceSource = source;
|
|
169
|
+
|
|
170
|
+
const detail: SubscriptionPerformanceDetail = {
|
|
171
|
+
type: "jazz-subscription",
|
|
172
|
+
uuid: this.performanceUuid,
|
|
173
|
+
id: this.id,
|
|
174
|
+
source,
|
|
175
|
+
resolve: this.resolve,
|
|
176
|
+
status: "pending",
|
|
177
|
+
startTime: performance.now(),
|
|
178
|
+
callerStack: this.callerStack?.stack,
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
performance.mark(`jazz.subscription.start:${this.performanceUuid}`, {
|
|
182
|
+
detail,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
if (currentState !== CoValueLoadingState.LOADING) {
|
|
186
|
+
this.emitLoadingComplete(currentState);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Subscribe to get notified when loading completes
|
|
191
|
+
const unsubscribe = this.subscribe(() => {
|
|
192
|
+
const rawValue = this.getCurrentRawValue();
|
|
193
|
+
|
|
194
|
+
if (rawValue === CoValueLoadingState.LOADING) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
this.emitLoadingComplete(rawValue);
|
|
199
|
+
unsubscribe();
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private emitLoadingComplete(rawValue: D | NotLoadedCoValueState) {
|
|
204
|
+
if (!this.performanceUuid) return;
|
|
205
|
+
|
|
206
|
+
const isError = typeof rawValue === "string";
|
|
207
|
+
const endTime = performance.now();
|
|
208
|
+
|
|
209
|
+
let errorType: SubscriptionPerformanceDetail["errorType"];
|
|
210
|
+
if (isError) {
|
|
211
|
+
if (
|
|
212
|
+
rawValue === CoValueLoadingState.UNAVAILABLE ||
|
|
213
|
+
rawValue === CoValueLoadingState.UNAUTHORIZED ||
|
|
214
|
+
rawValue === CoValueLoadingState.DELETED
|
|
215
|
+
) {
|
|
216
|
+
errorType = rawValue;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const detail: SubscriptionPerformanceDetail = {
|
|
221
|
+
type: "jazz-subscription",
|
|
222
|
+
uuid: this.performanceUuid,
|
|
223
|
+
id: this.id,
|
|
224
|
+
source: this.performanceSource ?? "unknown",
|
|
225
|
+
resolve: this.resolve,
|
|
226
|
+
status: isError ? "error" : "loaded",
|
|
227
|
+
startTime: 0, // Will be calculated from measure
|
|
228
|
+
endTime,
|
|
229
|
+
errorType,
|
|
230
|
+
devtools: {
|
|
231
|
+
track: "Jazz 🎶",
|
|
232
|
+
properties: [
|
|
233
|
+
["id", this.id],
|
|
234
|
+
["source", this.performanceSource ?? "unknown"],
|
|
235
|
+
],
|
|
236
|
+
tooltipText: this.getCreationStackLines(false),
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
performance.mark(`jazz.subscription.end:${this.performanceUuid}`, {
|
|
241
|
+
detail,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
performance.measure(
|
|
246
|
+
`${detail.source}(${this.id}, ${JSON.stringify(this.resolve)})`,
|
|
247
|
+
{
|
|
248
|
+
start: `jazz.subscription.start:${this.performanceUuid}`,
|
|
249
|
+
end: `jazz.subscription.end:${this.performanceUuid}`,
|
|
250
|
+
detail,
|
|
251
|
+
},
|
|
252
|
+
);
|
|
253
|
+
} catch {
|
|
254
|
+
// Marks may have been cleared
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
updateValue(value: SubscriptionValue<D>) {
|
|
145
259
|
this.value = value;
|
|
146
260
|
|
|
147
261
|
// Flags that the value has changed and we need to trigger an update
|
|
@@ -285,7 +399,7 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
285
399
|
|
|
286
400
|
handleChildUpdate(
|
|
287
401
|
id: string,
|
|
288
|
-
value: SubscriptionValue<
|
|
402
|
+
value: SubscriptionValue<CoValue> | SubscriptionValueLoading,
|
|
289
403
|
key?: string,
|
|
290
404
|
) {
|
|
291
405
|
if (value.type === CoValueLoadingState.LOADING) {
|
|
@@ -469,7 +583,7 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
469
583
|
return CoValueLoadingState.LOADING;
|
|
470
584
|
}
|
|
471
585
|
|
|
472
|
-
private getCreationStackLines() {
|
|
586
|
+
private getCreationStackLines(fullFrame: boolean = true) {
|
|
473
587
|
const stack = this.callerStack?.stack;
|
|
474
588
|
|
|
475
589
|
if (!stack) {
|
|
@@ -492,6 +606,10 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
492
606
|
(result += "Subscription created "), (result += creationAppFrame.trim());
|
|
493
607
|
}
|
|
494
608
|
|
|
609
|
+
if (!fullFrame) {
|
|
610
|
+
return result;
|
|
611
|
+
}
|
|
612
|
+
|
|
495
613
|
result += "\nFull subscription creation stack:";
|
|
496
614
|
for (const line of creationStackLines.slice(0, 8)) {
|
|
497
615
|
result += "\n " + line.trim();
|
|
@@ -555,7 +673,7 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
555
673
|
this.dirty = false;
|
|
556
674
|
}
|
|
557
675
|
|
|
558
|
-
subscribers = new Set<(value: SubscriptionValue<D
|
|
676
|
+
subscribers = new Set<(value: SubscriptionValue<D>) => void>();
|
|
559
677
|
subscriberChangeCallbacks = new Set<(count: number) => void>();
|
|
560
678
|
|
|
561
679
|
/**
|
|
@@ -578,7 +696,7 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
578
696
|
});
|
|
579
697
|
}
|
|
580
698
|
|
|
581
|
-
subscribe(listener: (value: SubscriptionValue<D
|
|
699
|
+
subscribe(listener: (value: SubscriptionValue<D>) => void) {
|
|
582
700
|
this.subscribers.add(listener);
|
|
583
701
|
this.notifySubscriberChange();
|
|
584
702
|
|
|
@@ -588,7 +706,7 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
588
706
|
};
|
|
589
707
|
}
|
|
590
708
|
|
|
591
|
-
setListener(listener: (value: SubscriptionValue<D
|
|
709
|
+
setListener(listener: (value: SubscriptionValue<D>) => void) {
|
|
592
710
|
const hadListener = this.subscribers.has(listener);
|
|
593
711
|
this.subscribers.add(listener);
|
|
594
712
|
// Only notify if this is a new listener (count actually changed)
|
|
@@ -624,11 +742,11 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
624
742
|
this.silenceUpdates = true;
|
|
625
743
|
|
|
626
744
|
if (value[TypeSym] === "CoMap" || value[TypeSym] === "Account") {
|
|
627
|
-
const map = value as CoMap;
|
|
745
|
+
const map = value as unknown as CoMap;
|
|
628
746
|
|
|
629
747
|
this.loadCoMapKey(map, key, true);
|
|
630
748
|
} else if (value[TypeSym] === "CoList") {
|
|
631
|
-
const list = value as CoList;
|
|
749
|
+
const list = value as unknown as CoList;
|
|
632
750
|
|
|
633
751
|
this.loadCoListKey(list, key, true);
|
|
634
752
|
}
|
|
@@ -650,7 +768,7 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
650
768
|
*
|
|
651
769
|
* Used to make the autoload work on closed subscription scopes
|
|
652
770
|
*/
|
|
653
|
-
pullValue(listener: (value: SubscriptionValue<D
|
|
771
|
+
pullValue(listener: (value: SubscriptionValue<D>) => void) {
|
|
654
772
|
if (!this.closed) {
|
|
655
773
|
throw new Error("Cannot pull a non-closed subscription scope");
|
|
656
774
|
}
|
|
@@ -707,7 +825,7 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
707
825
|
const child = new SubscriptionScope(
|
|
708
826
|
this.node,
|
|
709
827
|
true,
|
|
710
|
-
id
|
|
828
|
+
id,
|
|
711
829
|
descriptor,
|
|
712
830
|
this.skipRetry,
|
|
713
831
|
this.bestEffortResolution,
|
|
@@ -751,7 +869,7 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
751
869
|
coValueType === "Account" ||
|
|
752
870
|
coValueType === "Group"
|
|
753
871
|
) {
|
|
754
|
-
const map = value as CoMap;
|
|
872
|
+
const map = value as unknown as CoMap;
|
|
755
873
|
const keys =
|
|
756
874
|
"$each" in depth ? map.$jazz.raw.keys() : Object.keys(depth);
|
|
757
875
|
|
|
@@ -763,7 +881,7 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
763
881
|
}
|
|
764
882
|
}
|
|
765
883
|
} else if (value[TypeSym] === "CoList") {
|
|
766
|
-
const list = value as CoList;
|
|
884
|
+
const list = value as unknown as CoList;
|
|
767
885
|
|
|
768
886
|
const descriptor = list.$jazz.getItemsDescriptor();
|
|
769
887
|
|
|
@@ -782,7 +900,7 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
782
900
|
}
|
|
783
901
|
}
|
|
784
902
|
} else if (value[TypeSym] === "CoStream") {
|
|
785
|
-
const stream = value as CoFeed;
|
|
903
|
+
const stream = value as unknown as CoFeed;
|
|
786
904
|
const descriptor = stream.$jazz.getItemsDescriptor();
|
|
787
905
|
|
|
788
906
|
if (descriptor && isRefEncoded(descriptor)) {
|
|
@@ -969,7 +1087,7 @@ export class SubscriptionScope<D extends CoValue> {
|
|
|
969
1087
|
const child = new SubscriptionScope(
|
|
970
1088
|
this.node,
|
|
971
1089
|
resolve,
|
|
972
|
-
id
|
|
1090
|
+
id,
|
|
973
1091
|
descriptor,
|
|
974
1092
|
this.skipRetry,
|
|
975
1093
|
this.bestEffortResolution,
|
|
@@ -5,7 +5,7 @@ import type { JazzError } from "./JazzError";
|
|
|
5
5
|
*
|
|
6
6
|
* Works in Node.js and bundled code, falls back to false if process is not available
|
|
7
7
|
*/
|
|
8
|
-
const isDev = (function () {
|
|
8
|
+
export const isDev = (function () {
|
|
9
9
|
try {
|
|
10
10
|
return process.env.NODE_ENV === "development";
|
|
11
11
|
} catch {
|
|
@@ -100,7 +100,7 @@ export function accessChildById<D extends CoValue>(
|
|
|
100
100
|
|
|
101
101
|
// TODO: this doesn't check the subscription tree loading state
|
|
102
102
|
if (value?.type === CoValueLoadingState.LOADED) {
|
|
103
|
-
return value.value;
|
|
103
|
+
return value.value as D;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
const childNode = subscriptionScope.childNodes.get(childId);
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
Account,
|
|
3
|
-
CoValue,
|
|
4
|
-
Group,
|
|
5
|
-
RefsToResolve,
|
|
6
|
-
Resolved,
|
|
7
|
-
} from "../internal.js";
|
|
1
|
+
import type { Account, CoValue, Group, Resolved } from "../internal.js";
|
|
8
2
|
import type { JazzError } from "./JazzError.js";
|
|
9
3
|
|
|
10
4
|
export const CoValueLoadingState = {
|
|
@@ -42,10 +36,10 @@ export type NotLoadedCoValueState =
|
|
|
42
36
|
| typeof CoValueLoadingState.LOADING
|
|
43
37
|
| CoValueErrorState;
|
|
44
38
|
|
|
45
|
-
export type SubscriptionValue<D
|
|
39
|
+
export type SubscriptionValue<D> =
|
|
46
40
|
| {
|
|
47
41
|
type: typeof CoValueLoadingState.LOADED;
|
|
48
|
-
value:
|
|
42
|
+
value: D;
|
|
49
43
|
id: string;
|
|
50
44
|
}
|
|
51
45
|
| JazzError;
|
|
@@ -55,3 +49,62 @@ export type SubscriptionValueLoading = {
|
|
|
55
49
|
};
|
|
56
50
|
|
|
57
51
|
export type BranchDefinition = { name: string; owner?: Group | Account };
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Detail structure for subscription performance marks and measures.
|
|
55
|
+
* Used by SubscriptionScope.trackLoadingPerformance() to emit performance data.
|
|
56
|
+
*/
|
|
57
|
+
export interface SubscriptionPerformanceDetail {
|
|
58
|
+
/** Type of performance entry */
|
|
59
|
+
type: "jazz-subscription";
|
|
60
|
+
/** Unique identifier for this subscription instance */
|
|
61
|
+
uuid: string;
|
|
62
|
+
/** CoValue ID (e.g., "co_z1234...") */
|
|
63
|
+
id: string;
|
|
64
|
+
/** Source identifier (hook name or API) */
|
|
65
|
+
source: string;
|
|
66
|
+
/** The resolve query object */
|
|
67
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
68
|
+
resolve: any;
|
|
69
|
+
/** Current status of the subscription */
|
|
70
|
+
status: "pending" | "loaded" | "error";
|
|
71
|
+
/** When the subscription started loading (DOMHighResTimeStamp) */
|
|
72
|
+
startTime: number;
|
|
73
|
+
/** When loading completed (if completed) */
|
|
74
|
+
endTime?: number;
|
|
75
|
+
/** Total load time in ms (if completed) */
|
|
76
|
+
duration?: number;
|
|
77
|
+
/** Error type if status is "error" */
|
|
78
|
+
errorType?: "unavailable" | "unauthorized" | "deleted";
|
|
79
|
+
/** Stack trace captured at subscription creation time */
|
|
80
|
+
callerStack?: string;
|
|
81
|
+
devtools?: ExtensionTrackEntryPayload | ExtensionMarkerPayload;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
type DevToolsColor =
|
|
85
|
+
| "primary"
|
|
86
|
+
| "primary-light"
|
|
87
|
+
| "primary-dark"
|
|
88
|
+
| "secondary"
|
|
89
|
+
| "secondary-light"
|
|
90
|
+
| "secondary-dark"
|
|
91
|
+
| "tertiary"
|
|
92
|
+
| "tertiary-light"
|
|
93
|
+
| "tertiary-dark"
|
|
94
|
+
| "error";
|
|
95
|
+
|
|
96
|
+
interface ExtensionTrackEntryPayload {
|
|
97
|
+
dataType?: "track-entry"; // Defaults to "track-entry"
|
|
98
|
+
color?: DevToolsColor; // Defaults to "primary"
|
|
99
|
+
track: string; // Required: Name of the custom track
|
|
100
|
+
trackGroup?: string; // Optional: Group for organizing tracks
|
|
101
|
+
properties?: [string, string][]; // Key-value pairs for detailed view
|
|
102
|
+
tooltipText?: string; // Short description for tooltip
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface ExtensionMarkerPayload {
|
|
106
|
+
dataType: "marker"; // Required: Identifies as a marker
|
|
107
|
+
color?: DevToolsColor; // Defaults to "primary"
|
|
108
|
+
properties?: [string, string][]; // Key-value pairs for detailed view
|
|
109
|
+
tooltipText?: string; // Short description for tooltip
|
|
110
|
+
}
|
|
@@ -24,7 +24,7 @@ export function myRoleForRawValue(raw: RawCoValue): Role | undefined {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export function createCoValue<D extends CoValue>(
|
|
27
|
-
ref: RefEncoded<
|
|
27
|
+
ref: RefEncoded<CoValue>,
|
|
28
28
|
raw: RawCoValue,
|
|
29
29
|
subscriptionScope: SubscriptionScope<D>,
|
|
30
30
|
) {
|
|
@@ -39,7 +39,7 @@ export function createCoValue<D extends CoValue>(
|
|
|
39
39
|
|
|
40
40
|
return {
|
|
41
41
|
type: CoValueLoadingState.LOADED,
|
|
42
|
-
value: freshValueInstance,
|
|
42
|
+
value: freshValueInstance as unknown as D,
|
|
43
43
|
id: subscriptionScope.id,
|
|
44
44
|
};
|
|
45
45
|
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { SubscriptionScope } from "../subscribe/SubscriptionScope.js";
|
|
3
|
+
import { setupJazzTestSync } from "../testing.js";
|
|
4
|
+
import { setupAccount } from "./utils.js";
|
|
5
|
+
import { z } from "../index.js";
|
|
6
|
+
import { co } from "../internal.js";
|
|
7
|
+
import { CoID, RawCoValue, cojsonInternals } from "cojson";
|
|
8
|
+
|
|
9
|
+
describe("SubscriptionScope performance profiling", () => {
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
await setupJazzTestSync();
|
|
12
|
+
SubscriptionScope.setProfilingEnabled(true);
|
|
13
|
+
performance.clearMarks();
|
|
14
|
+
performance.clearMeasures();
|
|
15
|
+
cojsonInternals.setCoValueLoadingRetryDelay(100);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
SubscriptionScope.setProfilingEnabled(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("emits performance entries on successful load", async () => {
|
|
23
|
+
const { me, meOnSecondPeer } = await setupAccount();
|
|
24
|
+
|
|
25
|
+
const TestMap = co.map({ name: z.string() });
|
|
26
|
+
const value = TestMap.create({ name: "test" }, { owner: me });
|
|
27
|
+
|
|
28
|
+
const loaded = await TestMap.load(value.$jazz.id, {
|
|
29
|
+
loadAs: meOnSecondPeer,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(loaded).toBeDefined();
|
|
33
|
+
|
|
34
|
+
const measures = performance.getEntriesByType(
|
|
35
|
+
"measure",
|
|
36
|
+
) as PerformanceMeasure[];
|
|
37
|
+
const measure = measures.find(
|
|
38
|
+
(m) =>
|
|
39
|
+
(m as PerformanceMeasure & { detail?: { id?: string } }).detail?.id ===
|
|
40
|
+
value.$jazz.id,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
expect(measure).toBeDefined();
|
|
44
|
+
const detail = (
|
|
45
|
+
measure as PerformanceMeasure & { detail: Record<string, unknown> }
|
|
46
|
+
).detail;
|
|
47
|
+
expect(detail.type).toBe("jazz-subscription");
|
|
48
|
+
expect(detail.status).toBe("loaded");
|
|
49
|
+
expect(detail.uuid).toBeDefined();
|
|
50
|
+
expect(detail.source).toBeDefined();
|
|
51
|
+
expect(detail.resolve).toBeDefined();
|
|
52
|
+
expect(measure!.duration).toBeGreaterThan(0);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("does not emit performance entries when profiling is disabled", async () => {
|
|
56
|
+
SubscriptionScope.setProfilingEnabled(false);
|
|
57
|
+
|
|
58
|
+
const { me, meOnSecondPeer } = await setupAccount();
|
|
59
|
+
|
|
60
|
+
const TestMap = co.map({ name: z.string() });
|
|
61
|
+
const value = TestMap.create({ name: "test" }, { owner: me });
|
|
62
|
+
|
|
63
|
+
const loaded = await TestMap.load(value.$jazz.id, {
|
|
64
|
+
loadAs: meOnSecondPeer,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(loaded).toBeDefined();
|
|
68
|
+
|
|
69
|
+
// Verify no jazz-subscription entries were created
|
|
70
|
+
const measures = performance.getEntriesByType(
|
|
71
|
+
"measure",
|
|
72
|
+
) as PerformanceMeasure[];
|
|
73
|
+
const jazzMeasures = measures.filter(
|
|
74
|
+
(m) =>
|
|
75
|
+
(m as PerformanceMeasure & { detail?: { type?: string } }).detail
|
|
76
|
+
?.type === "jazz-subscription",
|
|
77
|
+
);
|
|
78
|
+
expect(jazzMeasures.length).toBe(0);
|
|
79
|
+
|
|
80
|
+
const marks = performance.getEntriesByType("mark") as PerformanceMark[];
|
|
81
|
+
const jazzMarks = marks.filter(
|
|
82
|
+
(m) =>
|
|
83
|
+
(m as PerformanceMark & { detail?: { type?: string } }).detail?.type ===
|
|
84
|
+
"jazz-subscription",
|
|
85
|
+
);
|
|
86
|
+
expect(jazzMarks.length).toBe(0);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("emits performance entries with error status on unavailable", async () => {
|
|
90
|
+
const { meOnSecondPeer } = await setupAccount();
|
|
91
|
+
|
|
92
|
+
const TestMap = co.map({ name: z.string() });
|
|
93
|
+
const fakeId = "co_zFAKEIDTHATDOESNOTEXIST123" as CoID<RawCoValue>;
|
|
94
|
+
|
|
95
|
+
const loaded = await TestMap.load(fakeId, { loadAs: meOnSecondPeer });
|
|
96
|
+
|
|
97
|
+
expect(loaded.$isLoaded).toBe(false);
|
|
98
|
+
|
|
99
|
+
const measures = performance.getEntriesByType(
|
|
100
|
+
"measure",
|
|
101
|
+
) as PerformanceMeasure[];
|
|
102
|
+
const measure = measures.find(
|
|
103
|
+
(m) =>
|
|
104
|
+
(m as PerformanceMeasure & { detail?: { id?: string } }).detail?.id ===
|
|
105
|
+
fakeId,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
expect(measure).toBeDefined();
|
|
109
|
+
const detail = (
|
|
110
|
+
measure as PerformanceMeasure & { detail: Record<string, unknown> }
|
|
111
|
+
).detail;
|
|
112
|
+
expect(detail.status).toBe("error");
|
|
113
|
+
expect(detail.errorType).toBe("unavailable");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("emits start and end marks with correct detail", async () => {
|
|
117
|
+
const { me, meOnSecondPeer } = await setupAccount();
|
|
118
|
+
|
|
119
|
+
const TestMap = co.map({ name: z.string() });
|
|
120
|
+
const value = TestMap.create({ name: "test" }, { owner: me });
|
|
121
|
+
|
|
122
|
+
await TestMap.load(value.$jazz.id, { loadAs: meOnSecondPeer });
|
|
123
|
+
|
|
124
|
+
const marks = performance.getEntriesByType("mark") as PerformanceMark[];
|
|
125
|
+
const startMark = marks.find(
|
|
126
|
+
(m) =>
|
|
127
|
+
m.name.startsWith("jazz.subscription.start:") &&
|
|
128
|
+
(m as PerformanceMark & { detail?: { id?: string } }).detail?.id ===
|
|
129
|
+
value.$jazz.id,
|
|
130
|
+
);
|
|
131
|
+
const endMark = marks.find(
|
|
132
|
+
(m) =>
|
|
133
|
+
m.name.startsWith("jazz.subscription.end:") &&
|
|
134
|
+
(m as PerformanceMark & { detail?: { id?: string } }).detail?.id ===
|
|
135
|
+
value.$jazz.id,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
expect(startMark).toBeDefined();
|
|
139
|
+
expect(endMark).toBeDefined();
|
|
140
|
+
expect(
|
|
141
|
+
(startMark as PerformanceMark & { detail: { status: string } }).detail
|
|
142
|
+
.status,
|
|
143
|
+
).toBe("pending");
|
|
144
|
+
expect(
|
|
145
|
+
(endMark as PerformanceMark & { detail: { status: string } }).detail
|
|
146
|
+
.status,
|
|
147
|
+
).toBe("loaded");
|
|
148
|
+
});
|
|
149
|
+
});
|