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.
Files changed (146) hide show
  1. package/.turbo/turbo-build.log +56 -56
  2. package/CHANGELOG.md +21 -0
  3. package/dist/{chunk-3CAPPS2F.js → chunk-Q5RNSSUM.js} +269 -95
  4. package/dist/chunk-Q5RNSSUM.js.map +1 -0
  5. package/dist/chunk-ZQWSQH6L.js +20 -0
  6. package/dist/index.js +2 -2
  7. package/dist/inspector/{chunk-MCTB5ZJC.js → chunk-6JPVMI3V.js} +302 -182
  8. package/dist/inspector/chunk-6JPVMI3V.js.map +1 -0
  9. package/dist/inspector/{custom-element-5YWVZBWA.js → custom-element-PWRX4VCA.js} +1337 -206
  10. package/dist/inspector/custom-element-PWRX4VCA.js.map +1 -0
  11. package/dist/inspector/in-app.d.ts +1 -0
  12. package/dist/inspector/in-app.d.ts.map +1 -1
  13. package/dist/inspector/index.d.ts +1 -0
  14. package/dist/inspector/index.d.ts.map +1 -1
  15. package/dist/inspector/index.js +1044 -17
  16. package/dist/inspector/index.js.map +1 -1
  17. package/dist/inspector/pages/home.d.ts +4 -1
  18. package/dist/inspector/pages/home.d.ts.map +1 -1
  19. package/dist/inspector/pages/performance/PerformancePage.d.ts +7 -0
  20. package/dist/inspector/pages/performance/PerformancePage.d.ts.map +1 -0
  21. package/dist/inspector/pages/performance/SubscriptionDetailPanel.d.ts +8 -0
  22. package/dist/inspector/pages/performance/SubscriptionDetailPanel.d.ts.map +1 -0
  23. package/dist/inspector/pages/performance/SubscriptionRow.d.ts +11 -0
  24. package/dist/inspector/pages/performance/SubscriptionRow.d.ts.map +1 -0
  25. package/dist/inspector/pages/performance/Timeline.d.ts +12 -0
  26. package/dist/inspector/pages/performance/Timeline.d.ts.map +1 -0
  27. package/dist/inspector/pages/performance/helpers.d.ts +5 -0
  28. package/dist/inspector/pages/performance/helpers.d.ts.map +1 -0
  29. package/dist/inspector/pages/performance/index.d.ts +3 -0
  30. package/dist/inspector/pages/performance/index.d.ts.map +1 -0
  31. package/dist/inspector/pages/performance/types.d.ts +13 -0
  32. package/dist/inspector/pages/performance/types.d.ts.map +1 -0
  33. package/dist/inspector/pages/performance/usePerformanceEntries.d.ts +3 -0
  34. package/dist/inspector/pages/performance/usePerformanceEntries.d.ts.map +1 -0
  35. package/dist/inspector/register-custom-element.js +3 -1
  36. package/dist/inspector/register-custom-element.js.map +1 -1
  37. package/dist/inspector/standalone.js +1 -1
  38. package/dist/inspector/tests/pages/performance/PerformancePage.test.d.ts +2 -0
  39. package/dist/inspector/tests/pages/performance/PerformancePage.test.d.ts.map +1 -0
  40. package/dist/inspector/tests/pages/performance/SubscriptionDetailPanel.test.d.ts +2 -0
  41. package/dist/inspector/tests/pages/performance/SubscriptionDetailPanel.test.d.ts.map +1 -0
  42. package/dist/inspector/tests/pages/performance/SubscriptionRow.test.d.ts +2 -0
  43. package/dist/inspector/tests/pages/performance/SubscriptionRow.test.d.ts.map +1 -0
  44. package/dist/inspector/tests/pages/performance/Timeline.test.d.ts +2 -0
  45. package/dist/inspector/tests/pages/performance/Timeline.test.d.ts.map +1 -0
  46. package/dist/inspector/tests/pages/performance/helpers.test.d.ts +2 -0
  47. package/dist/inspector/tests/pages/performance/helpers.test.d.ts.map +1 -0
  48. package/dist/inspector/viewer/delete-local-data.d.ts.map +1 -1
  49. package/dist/inspector/viewer/header.d.ts +4 -2
  50. package/dist/inspector/viewer/header.d.ts.map +1 -1
  51. package/dist/inspector/viewer/page-stack.d.ts +3 -1
  52. package/dist/inspector/viewer/page-stack.d.ts.map +1 -1
  53. package/dist/react-core/hooks.d.ts +2 -2
  54. package/dist/react-core/hooks.d.ts.map +1 -1
  55. package/dist/react-core/index.js +50 -18
  56. package/dist/react-core/index.js.map +1 -1
  57. package/dist/react-core/subscription-provider.d.ts.map +1 -1
  58. package/dist/react-native-core/media/image.d.ts +1 -1
  59. package/dist/svelte/jazz.class.svelte.d.ts.map +1 -1
  60. package/dist/svelte/jazz.class.svelte.js +27 -22
  61. package/dist/testing.js +2 -2
  62. package/dist/tools/coValues/interfaces.d.ts.map +1 -1
  63. package/dist/tools/exports.d.ts +1 -1
  64. package/dist/tools/exports.d.ts.map +1 -1
  65. package/dist/tools/implementation/zodSchema/runtimeConverters/coValueSchemaTransformation.d.ts.map +1 -1
  66. package/dist/tools/implementation/zodSchema/schemaTypes/AccountSchema.d.ts.map +1 -1
  67. package/dist/tools/implementation/zodSchema/schemaTypes/CoFeedSchema.d.ts +2 -1
  68. package/dist/tools/implementation/zodSchema/schemaTypes/CoFeedSchema.d.ts.map +1 -1
  69. package/dist/tools/implementation/zodSchema/schemaTypes/CoListSchema.d.ts +2 -1
  70. package/dist/tools/implementation/zodSchema/schemaTypes/CoListSchema.d.ts.map +1 -1
  71. package/dist/tools/implementation/zodSchema/schemaTypes/CoMapSchema.d.ts +2 -1
  72. package/dist/tools/implementation/zodSchema/schemaTypes/CoMapSchema.d.ts.map +1 -1
  73. package/dist/tools/implementation/zodSchema/schemaTypes/CoVectorSchema.d.ts +3 -1
  74. package/dist/tools/implementation/zodSchema/schemaTypes/CoVectorSchema.d.ts.map +1 -1
  75. package/dist/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.d.ts +3 -1
  76. package/dist/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.d.ts.map +1 -1
  77. package/dist/tools/implementation/zodSchema/schemaTypes/PlainTextSchema.d.ts +2 -1
  78. package/dist/tools/implementation/zodSchema/schemaTypes/PlainTextSchema.d.ts.map +1 -1
  79. package/dist/tools/implementation/zodSchema/schemaTypes/RichTextSchema.d.ts +6 -1
  80. package/dist/tools/implementation/zodSchema/schemaTypes/RichTextSchema.d.ts.map +1 -1
  81. package/dist/tools/implementation/zodSchema/zodCo.d.ts.map +1 -1
  82. package/dist/tools/ssr.js +1 -1
  83. package/dist/tools/subscribe/SubscriptionCache.d.ts +2 -2
  84. package/dist/tools/subscribe/SubscriptionCache.d.ts.map +1 -1
  85. package/dist/tools/subscribe/SubscriptionScope.d.ts +19 -12
  86. package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
  87. package/dist/tools/subscribe/errorReporting.d.ts +6 -0
  88. package/dist/tools/subscribe/errorReporting.d.ts.map +1 -1
  89. package/dist/tools/subscribe/index.d.ts +4 -4
  90. package/dist/tools/subscribe/index.d.ts.map +1 -1
  91. package/dist/tools/subscribe/types.d.ts +48 -3
  92. package/dist/tools/subscribe/types.d.ts.map +1 -1
  93. package/dist/tools/subscribe/utils.d.ts +1 -1
  94. package/dist/tools/subscribe/utils.d.ts.map +1 -1
  95. package/dist/tools/tests/SubscriptionScope.performance.test.d.ts +2 -0
  96. package/dist/tools/tests/SubscriptionScope.performance.test.d.ts.map +1 -0
  97. package/package.json +4 -4
  98. package/src/inspector/in-app.tsx +41 -3
  99. package/src/inspector/index.tsx +5 -1
  100. package/src/inspector/pages/home.tsx +26 -3
  101. package/src/inspector/pages/performance/PerformancePage.tsx +215 -0
  102. package/src/inspector/pages/performance/SubscriptionDetailPanel.tsx +182 -0
  103. package/src/inspector/pages/performance/SubscriptionRow.tsx +242 -0
  104. package/src/inspector/pages/performance/Timeline.tsx +513 -0
  105. package/src/inspector/pages/performance/helpers.ts +70 -0
  106. package/src/inspector/pages/performance/index.ts +2 -0
  107. package/src/inspector/pages/performance/types.ts +12 -0
  108. package/src/inspector/pages/performance/usePerformanceEntries.ts +53 -0
  109. package/src/inspector/register-custom-element.ts +3 -0
  110. package/src/inspector/tests/pages/performance/PerformancePage.test.tsx +83 -0
  111. package/src/inspector/tests/pages/performance/SubscriptionDetailPanel.test.tsx +68 -0
  112. package/src/inspector/tests/pages/performance/SubscriptionRow.test.tsx +93 -0
  113. package/src/inspector/tests/pages/performance/Timeline.test.tsx +57 -0
  114. package/src/inspector/tests/pages/performance/helpers.test.ts +91 -0
  115. package/src/inspector/viewer/delete-local-data.tsx +24 -5
  116. package/src/inspector/viewer/header.tsx +96 -17
  117. package/src/inspector/viewer/page-stack.tsx +22 -18
  118. package/src/react-core/hooks.ts +34 -4
  119. package/src/react-core/subscription-provider.tsx +17 -8
  120. package/src/svelte/jazz.class.svelte.ts +51 -33
  121. package/src/tools/coValues/coList.ts +73 -37
  122. package/src/tools/coValues/interfaces.ts +3 -0
  123. package/src/tools/exports.ts +1 -0
  124. package/src/tools/implementation/zodSchema/runtimeConverters/coValueSchemaTransformation.ts +13 -0
  125. package/src/tools/implementation/zodSchema/schemaTypes/AccountSchema.ts +0 -2
  126. package/src/tools/implementation/zodSchema/schemaTypes/CoFeedSchema.ts +5 -2
  127. package/src/tools/implementation/zodSchema/schemaTypes/CoListSchema.ts +5 -2
  128. package/src/tools/implementation/zodSchema/schemaTypes/CoMapSchema.ts +5 -2
  129. package/src/tools/implementation/zodSchema/schemaTypes/CoVectorSchema.ts +6 -2
  130. package/src/tools/implementation/zodSchema/schemaTypes/FileStreamSchema.ts +6 -2
  131. package/src/tools/implementation/zodSchema/schemaTypes/PlainTextSchema.ts +5 -2
  132. package/src/tools/implementation/zodSchema/schemaTypes/RichTextSchema.ts +9 -2
  133. package/src/tools/subscribe/SubscriptionCache.ts +6 -4
  134. package/src/tools/subscribe/SubscriptionScope.ts +141 -23
  135. package/src/tools/subscribe/errorReporting.ts +1 -1
  136. package/src/tools/subscribe/index.ts +1 -1
  137. package/src/tools/subscribe/types.ts +62 -9
  138. package/src/tools/subscribe/utils.ts +2 -2
  139. package/src/tools/tests/SubscriptionScope.performance.test.ts +149 -0
  140. package/src/tools/tests/coList.test.ts +262 -0
  141. package/src/tools/tests/schema.withPermissions.test.ts +27 -4
  142. package/dist/chunk-3CAPPS2F.js.map +0 -1
  143. package/dist/chunk-PZ5AY32C.js +0 -10
  144. package/dist/inspector/chunk-MCTB5ZJC.js.map +0 -1
  145. package/dist/inspector/custom-element-5YWVZBWA.js.map +0 -1
  146. /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 = DEFAULT_SCHEMA_PERMISSIONS;
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.permissions = permissions;
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<CoValue> {
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<CoValue>;
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<CoValue>(
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<any, any>> = new Map<
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, any> | SubscriptionValueLoading;
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<D>,
85
- public id: ID<D>,
86
- public schema: RefEncoded<D>,
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
- updateValue(value: SubscriptionValue<D, any>) {
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<any, any> | SubscriptionValueLoading,
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, any>) => void>();
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, any>) => void) {
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, any>) => void) {
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, any>) => void) {
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 as ID<any>,
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 as ID<any>,
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 extends CoValue, R extends RefsToResolve<D>> =
39
+ export type SubscriptionValue<D> =
46
40
  | {
47
41
  type: typeof CoValueLoadingState.LOADED;
48
- value: Resolved<D, R>;
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<D>,
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
+ });