jazz-tools 0.20.1 → 0.20.3

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 (115) hide show
  1. package/.turbo/turbo-build.log +49 -49
  2. package/CHANGELOG.md +19 -6
  3. package/dist/{chunk-2OPP7KWV.js → chunk-Q5RNSSUM.js} +121 -22
  4. package/dist/chunk-Q5RNSSUM.js.map +1 -0
  5. package/dist/index.js +1 -1
  6. package/dist/inspector/{chunk-MCTB5ZJC.js → chunk-6JPVMI3V.js} +302 -182
  7. package/dist/inspector/chunk-6JPVMI3V.js.map +1 -0
  8. package/dist/inspector/{custom-element-5YWVZBWA.js → custom-element-WOQY2M4W.js} +1337 -206
  9. package/dist/inspector/custom-element-WOQY2M4W.js.map +1 -0
  10. package/dist/inspector/in-app.d.ts +1 -0
  11. package/dist/inspector/in-app.d.ts.map +1 -1
  12. package/dist/inspector/index.d.ts +1 -0
  13. package/dist/inspector/index.d.ts.map +1 -1
  14. package/dist/inspector/index.js +1044 -17
  15. package/dist/inspector/index.js.map +1 -1
  16. package/dist/inspector/pages/home.d.ts +4 -1
  17. package/dist/inspector/pages/home.d.ts.map +1 -1
  18. package/dist/inspector/pages/performance/PerformancePage.d.ts +7 -0
  19. package/dist/inspector/pages/performance/PerformancePage.d.ts.map +1 -0
  20. package/dist/inspector/pages/performance/SubscriptionDetailPanel.d.ts +8 -0
  21. package/dist/inspector/pages/performance/SubscriptionDetailPanel.d.ts.map +1 -0
  22. package/dist/inspector/pages/performance/SubscriptionRow.d.ts +11 -0
  23. package/dist/inspector/pages/performance/SubscriptionRow.d.ts.map +1 -0
  24. package/dist/inspector/pages/performance/Timeline.d.ts +12 -0
  25. package/dist/inspector/pages/performance/Timeline.d.ts.map +1 -0
  26. package/dist/inspector/pages/performance/helpers.d.ts +5 -0
  27. package/dist/inspector/pages/performance/helpers.d.ts.map +1 -0
  28. package/dist/inspector/pages/performance/index.d.ts +3 -0
  29. package/dist/inspector/pages/performance/index.d.ts.map +1 -0
  30. package/dist/inspector/pages/performance/types.d.ts +13 -0
  31. package/dist/inspector/pages/performance/types.d.ts.map +1 -0
  32. package/dist/inspector/pages/performance/usePerformanceEntries.d.ts +3 -0
  33. package/dist/inspector/pages/performance/usePerformanceEntries.d.ts.map +1 -0
  34. package/dist/inspector/register-custom-element.js +3 -1
  35. package/dist/inspector/register-custom-element.js.map +1 -1
  36. package/dist/inspector/standalone.js +1 -1
  37. package/dist/inspector/tests/pages/performance/PerformancePage.test.d.ts +2 -0
  38. package/dist/inspector/tests/pages/performance/PerformancePage.test.d.ts.map +1 -0
  39. package/dist/inspector/tests/pages/performance/SubscriptionDetailPanel.test.d.ts +2 -0
  40. package/dist/inspector/tests/pages/performance/SubscriptionDetailPanel.test.d.ts.map +1 -0
  41. package/dist/inspector/tests/pages/performance/SubscriptionRow.test.d.ts +2 -0
  42. package/dist/inspector/tests/pages/performance/SubscriptionRow.test.d.ts.map +1 -0
  43. package/dist/inspector/tests/pages/performance/Timeline.test.d.ts +2 -0
  44. package/dist/inspector/tests/pages/performance/Timeline.test.d.ts.map +1 -0
  45. package/dist/inspector/tests/pages/performance/helpers.test.d.ts +2 -0
  46. package/dist/inspector/tests/pages/performance/helpers.test.d.ts.map +1 -0
  47. package/dist/inspector/viewer/delete-local-data.d.ts.map +1 -1
  48. package/dist/inspector/viewer/header.d.ts +4 -2
  49. package/dist/inspector/viewer/header.d.ts.map +1 -1
  50. package/dist/inspector/viewer/page-stack.d.ts +3 -1
  51. package/dist/inspector/viewer/page-stack.d.ts.map +1 -1
  52. package/dist/react-core/hooks.d.ts +2 -2
  53. package/dist/react-core/hooks.d.ts.map +1 -1
  54. package/dist/react-core/index.js +50 -18
  55. package/dist/react-core/index.js.map +1 -1
  56. package/dist/react-core/subscription-provider.d.ts.map +1 -1
  57. package/dist/react-native-core/media/image.d.ts +1 -1
  58. package/dist/svelte/jazz.class.svelte.d.ts.map +1 -1
  59. package/dist/svelte/jazz.class.svelte.js +27 -22
  60. package/dist/testing.js +1 -1
  61. package/dist/tools/coValues/interfaces.d.ts.map +1 -1
  62. package/dist/tools/exports.d.ts +1 -1
  63. package/dist/tools/exports.d.ts.map +1 -1
  64. package/dist/tools/implementation/zodSchema/runtimeConverters/coValueSchemaTransformation.d.ts.map +1 -1
  65. package/dist/tools/subscribe/SubscriptionCache.d.ts +2 -2
  66. package/dist/tools/subscribe/SubscriptionCache.d.ts.map +1 -1
  67. package/dist/tools/subscribe/SubscriptionScope.d.ts +19 -12
  68. package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
  69. package/dist/tools/subscribe/errorReporting.d.ts +6 -0
  70. package/dist/tools/subscribe/errorReporting.d.ts.map +1 -1
  71. package/dist/tools/subscribe/index.d.ts +4 -4
  72. package/dist/tools/subscribe/index.d.ts.map +1 -1
  73. package/dist/tools/subscribe/types.d.ts +48 -3
  74. package/dist/tools/subscribe/types.d.ts.map +1 -1
  75. package/dist/tools/subscribe/utils.d.ts +1 -1
  76. package/dist/tools/subscribe/utils.d.ts.map +1 -1
  77. package/dist/tools/tests/SubscriptionScope.performance.test.d.ts +2 -0
  78. package/dist/tools/tests/SubscriptionScope.performance.test.d.ts.map +1 -0
  79. package/package.json +4 -4
  80. package/src/inspector/in-app.tsx +41 -3
  81. package/src/inspector/index.tsx +5 -1
  82. package/src/inspector/pages/home.tsx +26 -3
  83. package/src/inspector/pages/performance/PerformancePage.tsx +215 -0
  84. package/src/inspector/pages/performance/SubscriptionDetailPanel.tsx +182 -0
  85. package/src/inspector/pages/performance/SubscriptionRow.tsx +242 -0
  86. package/src/inspector/pages/performance/Timeline.tsx +513 -0
  87. package/src/inspector/pages/performance/helpers.ts +70 -0
  88. package/src/inspector/pages/performance/index.ts +2 -0
  89. package/src/inspector/pages/performance/types.ts +12 -0
  90. package/src/inspector/pages/performance/usePerformanceEntries.ts +53 -0
  91. package/src/inspector/register-custom-element.ts +3 -0
  92. package/src/inspector/tests/pages/performance/PerformancePage.test.tsx +83 -0
  93. package/src/inspector/tests/pages/performance/SubscriptionDetailPanel.test.tsx +68 -0
  94. package/src/inspector/tests/pages/performance/SubscriptionRow.test.tsx +93 -0
  95. package/src/inspector/tests/pages/performance/Timeline.test.tsx +57 -0
  96. package/src/inspector/tests/pages/performance/helpers.test.ts +91 -0
  97. package/src/inspector/viewer/delete-local-data.tsx +24 -5
  98. package/src/inspector/viewer/header.tsx +96 -17
  99. package/src/inspector/viewer/page-stack.tsx +22 -18
  100. package/src/react-core/hooks.ts +34 -4
  101. package/src/react-core/subscription-provider.tsx +17 -8
  102. package/src/svelte/jazz.class.svelte.ts +51 -33
  103. package/src/tools/coValues/interfaces.ts +3 -0
  104. package/src/tools/exports.ts +1 -0
  105. package/src/tools/implementation/zodSchema/runtimeConverters/coValueSchemaTransformation.ts +13 -0
  106. package/src/tools/subscribe/SubscriptionCache.ts +6 -4
  107. package/src/tools/subscribe/SubscriptionScope.ts +141 -23
  108. package/src/tools/subscribe/errorReporting.ts +1 -1
  109. package/src/tools/subscribe/index.ts +1 -1
  110. package/src/tools/subscribe/types.ts +62 -9
  111. package/src/tools/subscribe/utils.ts +2 -2
  112. package/src/tools/tests/SubscriptionScope.performance.test.ts +149 -0
  113. package/dist/chunk-2OPP7KWV.js.map +0 -1
  114. package/dist/inspector/chunk-MCTB5ZJC.js.map +0 -1
  115. package/dist/inspector/custom-element-5YWVZBWA.js.map +0 -1
@@ -101,6 +101,7 @@ export function useCoValueSubscription<
101
101
  resolve?: ResolveQueryStrict<S, R>;
102
102
  unstable_branch?: BranchDefinition;
103
103
  },
104
+ source?: string,
104
105
  ): CoValueSubscription<S, R> | null {
105
106
  const resolve = getResolveQuery(Schema, options?.resolve);
106
107
  const subscriptions = useCoValueSubscriptions(
@@ -108,6 +109,7 @@ export function useCoValueSubscription<
108
109
  [id],
109
110
  resolve,
110
111
  options?.unstable_branch,
112
+ source,
111
113
  );
112
114
  return (subscriptions[0] ?? null) as CoValueSubscription<S, R> | null;
113
115
  }
@@ -140,6 +142,7 @@ function useCoValueSubscriptions(
140
142
  ids: readonly (string | undefined | null)[],
141
143
  resolve: ResolveQuery<any>,
142
144
  branch?: BranchDefinition,
145
+ source?: string,
143
146
  ): (SubscriptionScope<CoValue> | null)[] {
144
147
  const contextManager = useJazzContext();
145
148
  const agent = useAgent();
@@ -169,6 +172,9 @@ function useCoValueSubscriptions(
169
172
  subscription.callerStack = callerStack;
170
173
  }
171
174
 
175
+ // Track performance for root subscriptions
176
+ subscription.trackLoadingPerformance(source ?? "unknown");
177
+
172
178
  return subscription;
173
179
  });
174
180
 
@@ -428,7 +434,12 @@ export function useCoState<
428
434
  },
429
435
  ): TSelectorReturn {
430
436
  useImportCoValueContent(id, options?.preloaded);
431
- const subscription = useCoValueSubscription(Schema, id, options);
437
+ const subscription = useCoValueSubscription(
438
+ Schema,
439
+ id,
440
+ options,
441
+ `useCoState`,
442
+ );
432
443
  return useSubscriptionSelector(subscription, options);
433
444
  }
434
445
 
@@ -471,7 +482,12 @@ export function useSuspenseCoState<
471
482
  ): TSelectorReturn {
472
483
  useImportCoValueContent(id, options?.preloaded);
473
484
 
474
- const subscription = useCoValueSubscription(Schema, id, options);
485
+ const subscription = useCoValueSubscription(
486
+ Schema,
487
+ id,
488
+ options,
489
+ "useSuspenseCoState",
490
+ );
475
491
 
476
492
  if (!subscription) {
477
493
  throw new Error("Subscription not found");
@@ -534,6 +550,7 @@ export function useAccountSubscription<
534
550
  resolve?: ResolveQueryStrict<S, R>;
535
551
  unstable_branch?: BranchDefinition;
536
552
  },
553
+ source?: string,
537
554
  ) {
538
555
  const contextManager = useJazzContext();
539
556
 
@@ -570,6 +587,9 @@ export function useAccountSubscription<
570
587
  subscription.callerStack = callerStack;
571
588
  }
572
589
 
590
+ // Track performance for root subscriptions
591
+ subscription.trackLoadingPerformance(source ?? "unknown");
592
+
573
593
  return {
574
594
  subscription,
575
595
  contextManager,
@@ -727,7 +747,11 @@ export function useAccount<
727
747
  unstable_branch?: BranchDefinition;
728
748
  },
729
749
  ): TSelectorReturn {
730
- const subscription = useAccountSubscription(AccountSchema, options);
750
+ const subscription = useAccountSubscription(
751
+ AccountSchema,
752
+ options,
753
+ "useAccount",
754
+ );
731
755
  return useSubscriptionSelector(subscription, options);
732
756
  }
733
757
 
@@ -765,7 +789,11 @@ export function useSuspenseAccount<
765
789
  unstable_branch?: BranchDefinition;
766
790
  },
767
791
  ): TSelectorReturn {
768
- const subscription = useAccountSubscription(AccountSchema, options);
792
+ const subscription = useAccountSubscription(
793
+ AccountSchema,
794
+ options,
795
+ "useSuspenseAccount",
796
+ );
769
797
 
770
798
  if (!subscription) {
771
799
  throw new Error(
@@ -1054,6 +1082,7 @@ export function useSuspenseCoStates<
1054
1082
  ids,
1055
1083
  resolve,
1056
1084
  options?.unstable_branch,
1085
+ "useSuspenseCoStates",
1057
1086
  ) as SubscriptionScope<CoValue>[];
1058
1087
  useSuspendUntilLoaded(subscriptionScopes);
1059
1088
  return useSubscriptionsSelector(subscriptionScopes, options);
@@ -1123,6 +1152,7 @@ export function useCoStates<
1123
1152
  ids,
1124
1153
  resolve,
1125
1154
  options?.unstable_branch,
1155
+ "useCoStates",
1126
1156
  ) as SubscriptionScope<CoValue>[];
1127
1157
  return useSubscriptionsSelector(subscriptionScopes, options);
1128
1158
  }
@@ -39,10 +39,15 @@ export function createCoValueSubscriptionContext<
39
39
  loadingFallback?: React.ReactNode;
40
40
  unavailableFallback?: React.ReactNode;
41
41
  }>) => {
42
- const subscription = useCoValueSubscription(schema, id, {
43
- ...options,
44
- resolve: resolve,
45
- });
42
+ const subscription = useCoValueSubscription(
43
+ schema,
44
+ id,
45
+ {
46
+ ...options,
47
+ resolve: resolve,
48
+ },
49
+ "CoValueSubscriptionProvider",
50
+ );
46
51
 
47
52
  const loadState = useSubscriptionSelector(subscription, {
48
53
  select: (value) => value.$jazz.loadingState,
@@ -104,10 +109,14 @@ export function createAccountSubscriptionContext<
104
109
  loadingFallback?: React.ReactNode;
105
110
  unavailableFallback?: React.ReactNode;
106
111
  }>) => {
107
- const subscription = useAccountSubscription(schema, {
108
- ...options,
109
- resolve: resolve,
110
- });
112
+ const subscription = useAccountSubscription(
113
+ schema,
114
+ {
115
+ ...options,
116
+ resolve: resolve,
117
+ },
118
+ "AccountSubscriptionProvider",
119
+ );
111
120
 
112
121
  const loadState = useSubscriptionSelector(subscription, {
113
122
  select: (value) => value.$jazz.loadingState,
@@ -4,6 +4,7 @@ import type {
4
4
  AnyAccountSchema,
5
5
  BranchDefinition,
6
6
  CoValue,
7
+ CoValueClass,
7
8
  CoValueClassOrSchema,
8
9
  CoValueFromRaw,
9
10
  SchemaResolveQuery,
@@ -15,10 +16,11 @@ import type {
15
16
  ResolveQueryStrict,
16
17
  } from "jazz-tools";
17
18
  import {
19
+ captureStack,
18
20
  coValueClassFromCoValueClassOrSchema,
19
- subscribeToCoValue,
20
21
  CoValueLoadingState,
21
22
  getUnloadedCoValueWithoutId,
23
+ SubscriptionScope,
22
24
  } from "jazz-tools";
23
25
  import { untrack } from "svelte";
24
26
  import { createSubscriber } from "svelte/reactivity";
@@ -69,6 +71,7 @@ export class CoState<
69
71
  id: CoStateId | (() => CoStateId),
70
72
  options?: CoStateOptions<V, R> | (() => CoStateOptions<V, R>),
71
73
  ) {
74
+ const callerStack = captureStack();
72
75
  this.#id = $derived.by(typeof id === "function" ? id : () => id);
73
76
  this.#options = $derived.by(
74
77
  typeof options === "function" ? options : () => options,
@@ -89,29 +92,36 @@ export class CoState<
89
92
  getUnloadedCoValueWithoutId(CoValueLoadingState.UNAVAILABLE),
90
93
  );
91
94
  }
95
+
92
96
  const agent = "me" in ctx ? ctx.me : ctx.guest;
97
+ const node = "node" in agent ? agent.node : agent.$jazz.localNode;
93
98
  const resolve = getResolveQuery(Schema, options?.resolve);
99
+ const cls = coValueClassFromCoValueClassOrSchema(Schema) as CoValueClass<Loaded<V, R>>;
94
100
 
95
- const unsubscribe = subscribeToCoValue(
96
- coValueClassFromCoValueClassOrSchema(Schema),
101
+ const subscriptionScope = new SubscriptionScope<Loaded<V, R>>(
102
+ node,
103
+ resolve,
97
104
  id,
98
- {
99
- // @ts-expect-error The resolve query type isn't compatible with the coValueClassFromCoValueClassOrSchema conversion
100
- resolve,
101
- loadAs: agent,
102
- onError: (value) => {
103
- this.update(value);
104
- },
105
- syncResolution: true,
106
- unstable_branch: options?.unstable_branch,
107
- },
108
- (value) => {
109
- this.update(value as Loaded<V, R>);
110
- },
105
+ { ref: cls, optional: false },
106
+ false, // skipRetry
107
+ false, // bestEffortResolution
108
+ options?.unstable_branch,
111
109
  );
112
110
 
111
+ subscriptionScope.callerStack = callerStack;
112
+
113
+ // Track performance for Svelte subscriptions
114
+ subscriptionScope.trackLoadingPerformance("CoState");
115
+
116
+ subscriptionScope.subscribe(() => {
117
+ const value = subscriptionScope.getCurrentValue();
118
+ this.update(value);
119
+ });
120
+
121
+ this.update(subscriptionScope.getCurrentValue());
122
+
113
123
  return () => {
114
- unsubscribe();
124
+ subscriptionScope.destroy();
115
125
  };
116
126
  });
117
127
  });
@@ -150,6 +160,7 @@ export class AccountCoState<
150
160
  Schema: A,
151
161
  options?: CoStateOptions<A, R> | (() => CoStateOptions<A, R>),
152
162
  ) {
163
+ const callerStack = captureStack();
153
164
  this.#options = $derived.by(
154
165
  typeof options === "function" ? options : () => options,
155
166
  );
@@ -170,27 +181,34 @@ export class AccountCoState<
170
181
  }
171
182
 
172
183
  const me = ctx.me;
184
+ const node = me.$jazz.localNode;
173
185
  const resolve = getResolveQuery(Schema, options?.resolve);
174
-
175
- const unsubscribe = subscribeToCoValue(
176
- coValueClassFromCoValueClassOrSchema(Schema),
186
+ const cls = coValueClassFromCoValueClassOrSchema(Schema) as CoValueClass<Loaded<A, R>>;
187
+
188
+ const subscriptionScope = new SubscriptionScope<Loaded<A, R>>(
189
+ node,
190
+ resolve,
177
191
  me.$jazz.id,
178
- {
179
- resolve,
180
- loadAs: me,
181
- onError: (value) => {
182
- this.update(value);
183
- },
184
- syncResolution: true,
185
- unstable_branch: options?.unstable_branch,
186
- },
187
- (value) => {
188
- this.update(value as Loaded<A, R>);
189
- },
192
+ { ref: cls, optional: false },
193
+ false, // skipRetry
194
+ false, // bestEffortResolution
195
+ options?.unstable_branch,
190
196
  );
191
197
 
198
+ subscriptionScope.callerStack = callerStack;
199
+
200
+ // Track performance for Svelte subscriptions
201
+ subscriptionScope.trackLoadingPerformance("AccountCoState");
202
+
203
+ subscriptionScope.subscribe(() => {
204
+ const value = subscriptionScope.getCurrentValue();
205
+ this.update(value);
206
+ });
207
+
208
+ this.update(subscriptionScope.getCurrentValue());
209
+
192
210
  return () => {
193
- unsubscribe();
211
+ subscriptionScope.destroy();
194
212
  };
195
213
  });
196
214
  });
@@ -349,6 +349,9 @@ export function subscribeToCoValue<
349
349
  options.unstable_branch,
350
350
  );
351
351
 
352
+ // Track performance for API subscriptions
353
+ rootNode.trackLoadingPerformance("subscribe");
354
+
352
355
  const handleUpdate = () => {
353
356
  if (unsubscribed) return;
354
357
 
@@ -41,6 +41,7 @@ export type {
41
41
  AccountCreationProps,
42
42
  BaseProfileShape,
43
43
  ExportedCoValue,
44
+ SubscriptionPerformanceDetail,
44
45
  } from "./internal.js";
45
46
 
46
47
  export {
@@ -39,6 +39,19 @@ import {
39
39
  schemaFieldToCoFieldDef,
40
40
  } from "./schemaFieldToCoFieldDef.js";
41
41
 
42
+ /**
43
+ * A platform agnostic way to check if we're in development mode
44
+ *
45
+ * Works in Node.js and bundled code, falls back to false if process is not available
46
+ */
47
+ const isDev = (function () {
48
+ try {
49
+ return process.env.NODE_ENV === "development";
50
+ } catch {
51
+ return false;
52
+ }
53
+ })();
54
+
42
55
  // Note: if you're editing this function, edit the `isAnyCoValueSchema`
43
56
  // function in `zodReExport.ts` as well
44
57
  export function isAnyCoValueSchema(
@@ -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);