jazz-tools 0.19.2 → 0.19.4

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 (89) hide show
  1. package/.svelte-kit/__package__/jazz.class.svelte.d.ts +2 -2
  2. package/.svelte-kit/__package__/jazz.class.svelte.d.ts.map +1 -1
  3. package/.svelte-kit/__package__/jazz.class.svelte.js +15 -17
  4. package/.turbo/turbo-build.log +54 -54
  5. package/CHANGELOG.md +24 -0
  6. package/dist/{chunk-NCNM6UDZ.js → chunk-PT7FCV26.js} +148 -78
  7. package/dist/chunk-PT7FCV26.js.map +1 -0
  8. package/dist/index.js +14 -7
  9. package/dist/index.js.map +1 -1
  10. package/dist/inspector/{custom-element-ABVPHX53.js → custom-element-P76EIWEV.js} +322 -146
  11. package/dist/inspector/custom-element-P76EIWEV.js.map +1 -0
  12. package/dist/inspector/index.js +302 -126
  13. package/dist/inspector/index.js.map +1 -1
  14. package/dist/inspector/register-custom-element.js +1 -1
  15. package/dist/inspector/tests/viewer/co-plain-text-view.test.d.ts +2 -0
  16. package/dist/inspector/tests/viewer/co-plain-text-view.test.d.ts.map +1 -0
  17. package/dist/inspector/utils/history.d.ts +5 -1
  18. package/dist/inspector/utils/history.d.ts.map +1 -1
  19. package/dist/inspector/utils/permissions.d.ts +3 -0
  20. package/dist/inspector/utils/permissions.d.ts.map +1 -0
  21. package/dist/inspector/viewer/co-map-view.d.ts.map +1 -1
  22. package/dist/inspector/viewer/co-plain-text-view.d.ts +4 -2
  23. package/dist/inspector/viewer/co-plain-text-view.d.ts.map +1 -1
  24. package/dist/inspector/viewer/grid-view.d.ts.map +1 -1
  25. package/dist/inspector/viewer/page.d.ts.map +1 -1
  26. package/dist/inspector/viewer/use-resolve-covalue.d.ts +0 -1
  27. package/dist/inspector/viewer/use-resolve-covalue.d.ts.map +1 -1
  28. package/dist/react-core/hooks.d.ts.map +1 -1
  29. package/dist/react-core/index.js +4 -17
  30. package/dist/react-core/index.js.map +1 -1
  31. package/dist/svelte/jazz.class.svelte.d.ts +2 -2
  32. package/dist/svelte/jazz.class.svelte.d.ts.map +1 -1
  33. package/dist/svelte/jazz.class.svelte.js +15 -17
  34. package/dist/testing.js +1 -1
  35. package/dist/tools/coValues/coFeed.d.ts.map +1 -1
  36. package/dist/tools/coValues/group.d.ts.map +1 -1
  37. package/dist/tools/coValues/interfaces.d.ts +7 -6
  38. package/dist/tools/coValues/interfaces.d.ts.map +1 -1
  39. package/dist/tools/coValues/promise.d.ts +9 -0
  40. package/dist/tools/coValues/promise.d.ts.map +1 -0
  41. package/dist/tools/coValues/request.d.ts.map +1 -1
  42. package/dist/tools/exports.d.ts +1 -1
  43. package/dist/tools/exports.d.ts.map +1 -1
  44. package/dist/tools/implementation/refs.d.ts +1 -1
  45. package/dist/tools/implementation/refs.d.ts.map +1 -1
  46. package/dist/tools/implementation/zodSchema/runtimeConverters/schemaFieldToCoFieldDef.d.ts +3 -1
  47. package/dist/tools/implementation/zodSchema/runtimeConverters/schemaFieldToCoFieldDef.d.ts.map +1 -1
  48. package/dist/tools/implementation/zodSchema/unionUtils.d.ts.map +1 -1
  49. package/dist/tools/subscribe/SubscriptionScope.d.ts +5 -2
  50. package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
  51. package/dist/tools/subscribe/index.d.ts +1 -1
  52. package/dist/tools/subscribe/index.d.ts.map +1 -1
  53. package/dist/tools/subscribe/types.d.ts +2 -1
  54. package/dist/tools/subscribe/types.d.ts.map +1 -1
  55. package/dist/tools/tests/SubscriptionScope.test.d.ts +2 -0
  56. package/dist/tools/tests/SubscriptionScope.test.d.ts.map +1 -0
  57. package/package.json +4 -4
  58. package/src/inspector/tests/utils/history.test.ts +233 -2
  59. package/src/inspector/tests/viewer/co-plain-text-view.test.tsx +125 -0
  60. package/src/inspector/tests/viewer/comap-view.test.tsx +309 -1
  61. package/src/inspector/tests/viewer/history-view.test.tsx +134 -2
  62. package/src/inspector/utils/history.ts +168 -1
  63. package/src/inspector/utils/permissions.ts +10 -0
  64. package/src/inspector/viewer/co-map-view.tsx +27 -15
  65. package/src/inspector/viewer/co-plain-text-view.tsx +102 -3
  66. package/src/inspector/viewer/grid-view.tsx +2 -1
  67. package/src/inspector/viewer/history-view.tsx +5 -23
  68. package/src/inspector/viewer/page.tsx +8 -1
  69. package/src/inspector/viewer/use-resolve-covalue.ts +2 -6
  70. package/src/react-core/hooks.ts +5 -29
  71. package/src/svelte/jazz.class.svelte.ts +16 -34
  72. package/src/tools/coValues/coFeed.ts +10 -7
  73. package/src/tools/coValues/coMap.ts +10 -7
  74. package/src/tools/coValues/group.ts +6 -2
  75. package/src/tools/coValues/interfaces.ts +48 -28
  76. package/src/tools/coValues/promise.ts +34 -0
  77. package/src/tools/coValues/request.ts +12 -8
  78. package/src/tools/exports.ts +1 -0
  79. package/src/tools/implementation/refs.ts +9 -17
  80. package/src/tools/implementation/zodSchema/runtimeConverters/schemaFieldToCoFieldDef.ts +62 -30
  81. package/src/tools/implementation/zodSchema/unionUtils.ts +3 -4
  82. package/src/tools/subscribe/SubscriptionScope.ts +45 -2
  83. package/src/tools/subscribe/index.ts +28 -13
  84. package/src/tools/subscribe/types.ts +5 -2
  85. package/src/tools/tests/SubscriptionScope.test.ts +397 -0
  86. package/src/tools/tests/deepLoading.test.ts +22 -0
  87. package/src/tools/tests/subscribe.test.ts +69 -0
  88. package/dist/chunk-NCNM6UDZ.js.map +0 -1
  89. package/dist/inspector/custom-element-ABVPHX53.js.map +0 -1
@@ -107,6 +107,23 @@ export function isCoValueClass<V extends CoValue>(
107
107
  */
108
108
  export type ID<T> = string;
109
109
 
110
+ const unloadedCoValueStates = new Map<
111
+ NotLoadedCoValueState,
112
+ NotLoaded<CoValue>
113
+ >();
114
+
115
+ export function getUnloadedCoValueWithoutId<T extends CoValue>(
116
+ loadingState: NotLoadedCoValueState,
117
+ ): NotLoaded<T> {
118
+ const value = unloadedCoValueStates.get(loadingState);
119
+ if (value) {
120
+ return value;
121
+ }
122
+ const newValue = createUnloadedCoValue("", loadingState);
123
+ unloadedCoValueStates.set(loadingState, newValue);
124
+ return newValue;
125
+ }
126
+
110
127
  export function createUnloadedCoValue<T extends CoValue>(
111
128
  id: ID<T>,
112
129
  loadingState: NotLoadedCoValueState,
@@ -159,12 +176,8 @@ export function loadCoValue<
159
176
  loadAs: options.loadAs,
160
177
  syncResolution: true,
161
178
  skipRetry: options.skipRetry,
162
- onUnavailable: () => {
163
- resolve(createUnloadedCoValue(id, CoValueLoadingState.UNAVAILABLE));
164
- },
165
- onUnauthorized: () => {
166
- resolve(createUnloadedCoValue(id, CoValueLoadingState.UNAUTHORIZED));
167
- },
179
+ onUnavailable: resolve,
180
+ onUnauthorized: resolve,
168
181
  unstable_branch: options.unstable_branch,
169
182
  },
170
183
  (value, unsubscribe) => {
@@ -215,8 +228,8 @@ export type SubscribeListenerOptions<
215
228
  > = {
216
229
  resolve?: RefsToResolveStrict<V, R>;
217
230
  loadAs?: Account | AnonymousJazzAgent;
218
- onUnauthorized?: () => void;
219
- onUnavailable?: () => void;
231
+ onUnauthorized?: (value: NotLoaded<V>) => void;
232
+ onUnavailable?: (value: NotLoaded<V>) => void;
220
233
  unstable_branch?: BranchDefinition;
221
234
  };
222
235
 
@@ -290,8 +303,8 @@ export function subscribeToCoValue<
290
303
  options: {
291
304
  resolve?: RefsToResolveStrict<V, R>;
292
305
  loadAs: Account | AnonymousJazzAgent;
293
- onUnavailable?: () => void;
294
- onUnauthorized?: () => void;
306
+ onUnavailable?: (value: NotLoaded<V>) => void;
307
+ onUnauthorized?: (value: NotLoaded<V>) => void;
295
308
  syncResolution?: boolean;
296
309
  skipRetry?: boolean;
297
310
  unstable_branch?: BranchDefinition;
@@ -318,35 +331,42 @@ export function subscribeToCoValue<
318
331
  options.unstable_branch,
319
332
  );
320
333
 
321
- const handleUpdate = (value: SubscriptionValue<V, any>) => {
334
+ const handleUpdate = () => {
322
335
  if (unsubscribed) return;
323
336
 
324
- if (value.type === CoValueLoadingState.UNAVAILABLE) {
325
- options.onUnavailable?.();
337
+ const value = rootNode.getCurrentValue();
326
338
 
327
- // Don't log unavailable errors when `loadUnique` or `upsertUnique` are used
328
- if (!options.skipRetry) {
329
- console.error(value.toString());
330
- }
331
- } else if (value.type === CoValueLoadingState.UNAUTHORIZED) {
332
- options.onUnauthorized?.();
339
+ if (value.$isLoaded) {
340
+ listener(value as Resolved<V, R>, unsubscribe);
341
+ return;
342
+ }
333
343
 
334
- console.error(value.toString());
335
- } else if (value.type === CoValueLoadingState.LOADED) {
336
- listener(value.value as Resolved<V, R>, unsubscribe);
344
+ switch (value.$jazz.loadingState) {
345
+ case CoValueLoadingState.UNAVAILABLE:
346
+ options.onUnavailable?.(value);
347
+
348
+ // Don't log unavailable errors when `loadUnique` or `upsertUnique` are used
349
+ if (!options.skipRetry) {
350
+ console.error(value.toString());
351
+ }
352
+ break;
353
+ case CoValueLoadingState.UNAUTHORIZED:
354
+ options.onUnauthorized?.(value);
355
+ console.error(value.toString());
356
+ break;
337
357
  }
338
358
  };
339
359
 
340
360
  let shouldDefer = !options.syncResolution;
341
361
 
342
- rootNode.setListener((value) => {
362
+ rootNode.setListener(() => {
343
363
  if (shouldDefer) {
344
364
  shouldDefer = false;
345
365
  Promise.resolve().then(() => {
346
- handleUpdate(value);
366
+ handleUpdate();
347
367
  });
348
368
  } else {
349
- handleUpdate(value);
369
+ handleUpdate();
350
370
  }
351
371
  });
352
372
 
@@ -366,8 +386,8 @@ export function subscribeToExistingCoValue<
366
386
  options:
367
387
  | {
368
388
  resolve?: RefsToResolveStrict<V, R>;
369
- onUnavailable?: () => void;
370
- onUnauthorized?: () => void;
389
+ onUnavailable?: (value: NotLoaded<V>) => void;
390
+ onUnauthorized?: (value: NotLoaded<V>) => void;
371
391
  unstable_branch?: BranchDefinition;
372
392
  }
373
393
  | undefined,
@@ -703,7 +723,7 @@ function loadContentPiecesFromSubscription(
703
723
 
704
724
  const currentValue = subscription.getCurrentValue();
705
725
 
706
- if (typeof currentValue !== "string") {
726
+ if (currentValue.$isLoaded) {
707
727
  const core = currentValue.$jazz.raw.core as AvailableCoValueCore;
708
728
  loadContentPiecesFromCoValue(core, valuesExported, contentPieces);
709
729
  }
@@ -0,0 +1,34 @@
1
+ export class CoValuePromise<T> extends Promise<T> {
2
+ status: "pending" | "fulfilled" | "rejected" = "pending";
3
+ value: T | undefined;
4
+ reason: unknown | undefined;
5
+
6
+ static getRejected<T = never>(reason?: unknown): CoValuePromise<T> {
7
+ return new CoValuePromise<T>((resolve, reject) => {
8
+ reject(reason);
9
+ });
10
+ }
11
+
12
+ static getFulfilled<T>(value: T): CoValuePromise<T> {
13
+ return new CoValuePromise<T>((resolve) => {
14
+ resolve(value);
15
+ });
16
+ }
17
+
18
+ constructor(
19
+ executor: (
20
+ resolve: (value: T) => void,
21
+ reject: (reason?: unknown) => void,
22
+ ) => void,
23
+ ) {
24
+ super((resolve, reject) => {
25
+ const _resolve = (value: T) => {
26
+ resolve(value);
27
+ };
28
+ const _reject = (reason?: unknown) => {
29
+ reject(reason);
30
+ };
31
+ executor(_resolve, _reject);
32
+ });
33
+ }
34
+ }
@@ -14,8 +14,6 @@ import {
14
14
  CoMapSchemaInit,
15
15
  CoValueClass,
16
16
  CoreCoMapSchema,
17
- CoValueLoadingState,
18
- createUnloadedCoValue,
19
17
  Group,
20
18
  Loaded,
21
19
  ResolveQuery,
@@ -377,9 +375,6 @@ export class HttpRoute<
377
375
  const as = options?.owner ?? Account.getMe();
378
376
 
379
377
  const target = await loadWorkerAccountOrGroup(this.workerId, as);
380
- if (!target.$isLoaded) {
381
- throw new JazzRequestError("Worker account not found", 400);
382
- }
383
378
 
384
379
  const response = await fetch(this.url, {
385
380
  method: "POST",
@@ -621,20 +616,29 @@ async function loadWorkerAccountOrGroup(id: string, loadAs: Account) {
621
616
  const coValue = await node.loadCoValueCore(id as `co_z${string}`);
622
617
 
623
618
  if (!coValue.isAvailable()) {
624
- return createUnloadedCoValue(id, CoValueLoadingState.UNAVAILABLE);
619
+ throw new JazzRequestError("Worker account not found", 400);
625
620
  }
626
621
 
627
622
  const content = coValue.getCurrentContent();
628
623
 
629
624
  if (content instanceof RawAccount) {
630
- return Account.load(content.id, {
625
+ const account = await Account.load(content.id, {
631
626
  loadAs,
632
627
  });
628
+ if (!account.$isLoaded) {
629
+ throw new JazzRequestError("Worker account not found", 400);
630
+ }
631
+ return account;
633
632
  }
634
633
 
635
- return Group.load(content.id, {
634
+ const group = await Group.load(content.id, {
636
635
  loadAs,
637
636
  });
637
+
638
+ if (!group.$isLoaded) {
639
+ throw new JazzRequestError("Worker group not found", 400);
640
+ }
641
+ return group;
638
642
  }
639
643
 
640
644
  function defaultGetToken(request: Request) {
@@ -64,6 +64,7 @@ export {
64
64
  Ref,
65
65
  createUnloadedCoValue,
66
66
  unstable_loadUnique,
67
+ getUnloadedCoValueWithoutId,
67
68
  } from "./internal.js";
68
69
 
69
70
  export {
@@ -53,29 +53,21 @@ export class Ref<out V extends CoValue> {
53
53
  }
54
54
 
55
55
  if (!node) {
56
- return createUnloadedCoValue(this.id, CoValueLoadingState.LOADING);
56
+ return createUnloadedCoValue(this.id, CoValueLoadingState.UNAVAILABLE);
57
57
  }
58
58
 
59
- const value = node.value;
59
+ const value = node.getCurrentValue();
60
60
 
61
- if (value?.type === CoValueLoadingState.LOADED) {
62
- return value.value as V;
61
+ if (value.$isLoaded) {
62
+ return value as V;
63
63
  } else {
64
64
  return new Promise((resolve) => {
65
65
  const unsubscribe = node.subscribe((value) => {
66
- if (value?.type === CoValueLoadingState.LOADED) {
67
- unsubscribe();
68
- resolve(value.value as V);
69
- } else if (value?.type === CoValueLoadingState.UNAVAILABLE) {
70
- unsubscribe();
71
- resolve(
72
- createUnloadedCoValue(this.id, CoValueLoadingState.UNAVAILABLE),
73
- );
74
- } else if (value?.type === CoValueLoadingState.UNAUTHORIZED) {
66
+ const currentValue = node.getCurrentValue();
67
+
68
+ if (currentValue.$jazz.loadingState !== CoValueLoadingState.LOADING) {
75
69
  unsubscribe();
76
- resolve(
77
- createUnloadedCoValue(this.id, CoValueLoadingState.UNAUTHORIZED),
78
- );
70
+ resolve(currentValue as V);
79
71
  }
80
72
 
81
73
  if (subscriptionScope.closed) {
@@ -86,7 +78,7 @@ export class Ref<out V extends CoValue> {
86
78
  }
87
79
  }
88
80
 
89
- get value(): V | null | undefined {
81
+ get value(): MaybeLoaded<V> {
90
82
  return accessChildById(this.parent, this.id, this.schema);
91
83
  }
92
84
  }
@@ -60,16 +60,33 @@ function makeCodecCoField(
60
60
  });
61
61
  }
62
62
 
63
- export function schemaFieldToCoFieldDef(schema: SchemaField) {
63
+ // CoFieldDefs are inherently type-unsafe. This type exists only for documentation purposes.
64
+ type CoFieldDef = any;
65
+ const schemaFieldCache = new WeakMap<SchemaField, CoFieldDef>();
66
+
67
+ function cacheSchemaField(schema: SchemaField, value: CoFieldDef): CoFieldDef {
68
+ schemaFieldCache.set(schema, value);
69
+ return value;
70
+ }
71
+
72
+ export function schemaFieldToCoFieldDef(schema: SchemaField): CoFieldDef {
73
+ const cachedCoFieldDef = schemaFieldCache.get(schema);
74
+ if (cachedCoFieldDef !== undefined) {
75
+ return cachedCoFieldDef;
76
+ }
77
+
64
78
  if (isCoValueClass(schema)) {
65
- return coField.ref(schema);
79
+ return cacheSchemaField(schema, coField.ref(schema));
66
80
  } else if (isCoValueSchema(schema)) {
67
81
  if (schema.builtin === "CoOptional") {
68
- return coField.ref(schema.getCoValueClass(), {
69
- optional: true,
70
- });
82
+ return cacheSchemaField(
83
+ schema,
84
+ coField.ref(schema.getCoValueClass(), {
85
+ optional: true,
86
+ }),
87
+ );
71
88
  }
72
- return coField.ref(schema.getCoValueClass());
89
+ return cacheSchemaField(schema, coField.ref(schema.getCoValueClass()));
73
90
  } else {
74
91
  if ("_zod" in schema) {
75
92
  const zodSchemaDef = schema._zod.def;
@@ -90,29 +107,35 @@ export function schemaFieldToCoFieldDef(schema: SchemaField) {
90
107
  // Primitive coField types support null and undefined as values,
91
108
  // so we can just return the inner type here and rely on support
92
109
  // for null/undefined at the type level
93
- return coFieldDef;
110
+ return cacheSchemaField(schema, coFieldDef);
94
111
  } else if (zodSchemaDef.type === "string") {
95
- return coField.string;
112
+ return cacheSchemaField(schema, coField.string);
96
113
  } else if (zodSchemaDef.type === "number") {
97
- return coField.number;
114
+ return cacheSchemaField(schema, coField.number);
98
115
  } else if (zodSchemaDef.type === "boolean") {
99
- return coField.boolean;
116
+ return cacheSchemaField(schema, coField.boolean);
100
117
  } else if (zodSchemaDef.type === "null") {
101
- return coField.null;
118
+ return cacheSchemaField(schema, coField.null);
102
119
  } else if (zodSchemaDef.type === "enum") {
103
- return coField.string;
120
+ return cacheSchemaField(schema, coField.string);
104
121
  } else if (zodSchemaDef.type === "readonly") {
105
- return schemaFieldToCoFieldDef(
106
- (schema as unknown as ZodReadonly).def.innerType as SchemaField,
122
+ return cacheSchemaField(
123
+ schema,
124
+ schemaFieldToCoFieldDef(
125
+ (schema as unknown as ZodReadonly).def.innerType as SchemaField,
126
+ ),
107
127
  );
108
128
  } else if (zodSchemaDef.type === "date") {
109
- return coField.optional.Date;
129
+ return cacheSchemaField(schema, coField.optional.Date);
110
130
  } else if (zodSchemaDef.type === "template_literal") {
111
- return coField.string;
131
+ return cacheSchemaField(schema, coField.string);
112
132
  } else if (zodSchemaDef.type === "lazy") {
113
133
  // Mostly to support z.json()
114
- return schemaFieldToCoFieldDef(
115
- (schema as unknown as ZodLazy).unwrap() as SchemaField,
134
+ return cacheSchemaField(
135
+ schema,
136
+ schemaFieldToCoFieldDef(
137
+ (schema as unknown as ZodLazy).unwrap() as SchemaField,
138
+ ),
116
139
  );
117
140
  } else if (
118
141
  zodSchemaDef.type === "default" ||
@@ -122,9 +145,12 @@ export function schemaFieldToCoFieldDef(schema: SchemaField) {
122
145
  "z.default()/z.catch() are not supported in collaborative schemas. They will be ignored.",
123
146
  );
124
147
 
125
- return schemaFieldToCoFieldDef(
126
- (schema as unknown as ZodDefault | ZodCatch).def
127
- .innerType as SchemaField,
148
+ return cacheSchemaField(
149
+ schema,
150
+ schemaFieldToCoFieldDef(
151
+ (schema as unknown as ZodDefault | ZodCatch).def
152
+ .innerType as SchemaField,
153
+ ),
128
154
  );
129
155
  } else if (zodSchemaDef.type === "literal") {
130
156
  if (
@@ -140,11 +166,14 @@ export function schemaFieldToCoFieldDef(schema: SchemaField) {
140
166
  ) {
141
167
  throw new Error("z.literal() with bigint is not supported");
142
168
  }
143
- return coField.literal(
144
- ...(zodSchemaDef.values as Exclude<
145
- (typeof zodSchemaDef.values)[number],
146
- undefined | null | bigint
147
- >[]),
169
+ return cacheSchemaField(
170
+ schema,
171
+ coField.literal(
172
+ ...(zodSchemaDef.values as Exclude<
173
+ (typeof zodSchemaDef.values)[number],
174
+ undefined | null | bigint
175
+ >[]),
176
+ ),
148
177
  );
149
178
  } else if (
150
179
  zodSchemaDef.type === "object" ||
@@ -153,10 +182,10 @@ export function schemaFieldToCoFieldDef(schema: SchemaField) {
153
182
  zodSchemaDef.type === "tuple" ||
154
183
  zodSchemaDef.type === "intersection"
155
184
  ) {
156
- return coField.json();
185
+ return cacheSchemaField(schema, coField.json());
157
186
  } else if (zodSchemaDef.type === "union") {
158
187
  if (isUnionOfPrimitivesDeeply(schema)) {
159
- return coField.json();
188
+ return cacheSchemaField(schema, coField.json());
160
189
  } else {
161
190
  throw new Error(
162
191
  "z.union()/z.discriminatedUnion() of collaborative types is not supported. Use co.discriminatedUnion() instead.",
@@ -183,8 +212,11 @@ export function schemaFieldToCoFieldDef(schema: SchemaField) {
183
212
  throw error;
184
213
  }
185
214
 
186
- return makeCodecCoField(
187
- schema as z.core.$ZodCodec<z.core.$ZodType, z.core.$ZodType>,
215
+ return cacheSchemaField(
216
+ schema,
217
+ makeCodecCoField(
218
+ schema as z.core.$ZodCodec<z.core.$ZodType, z.core.$ZodType>,
219
+ ),
188
220
  );
189
221
  } else {
190
222
  throw new Error(
@@ -108,10 +108,9 @@ export function schemaUnionDiscriminatorFor(
108
108
  const coValueSchema = hydrateCoreCoValueSchema(option as any);
109
109
  const coValueClass = coValueSchema.getCoValueClass() as typeof CoMap;
110
110
 
111
- const dummyFieldNames = allNestedRefKeys
112
- .keys()
113
- .filter((key) => !optionDef.shape[key])
114
- .toArray();
111
+ const dummyFieldNames = Array.from(allNestedRefKeys).filter(
112
+ (key) => !optionDef.shape[key],
113
+ );
115
114
 
116
115
  if (dummyFieldNames.length === 0) {
117
116
  return coValueClass;
@@ -6,6 +6,7 @@ import {
6
6
  type CoValue,
7
7
  type ID,
8
8
  MaybeLoaded,
9
+ NotLoaded,
9
10
  type RefEncoded,
10
11
  type RefsToResolve,
11
12
  TypeSym,
@@ -283,7 +284,43 @@ export class SubscriptionScope<D extends CoValue> {
283
284
  return this.pendingLoadedChildren.size === 0;
284
285
  }
285
286
 
286
- getCurrentValue(): D | NotLoadedCoValueState {
287
+ unloadedValue: NotLoaded<D> | undefined;
288
+
289
+ private getUnloadedValue(reason: NotLoadedCoValueState): NotLoaded<D> {
290
+ if (this.unloadedValue?.$jazz.loadingState === reason) {
291
+ return this.unloadedValue;
292
+ }
293
+
294
+ const unloadedValue: NotLoaded<D> = {
295
+ $jazz: {
296
+ id: this.id,
297
+ loadingState: reason,
298
+ // @ts-expect-error - This is a private property
299
+ _subscriptionScope: this,
300
+ },
301
+ $isLoaded: false,
302
+ };
303
+
304
+ this.unloadedValue = unloadedValue;
305
+
306
+ return unloadedValue;
307
+ }
308
+
309
+ getCurrentValue(): MaybeLoaded<D> {
310
+ const rawValue = this.getCurrentRawValue();
311
+
312
+ if (
313
+ rawValue === CoValueLoadingState.UNAUTHORIZED ||
314
+ rawValue === CoValueLoadingState.UNAVAILABLE ||
315
+ rawValue === CoValueLoadingState.LOADING
316
+ ) {
317
+ return this.getUnloadedValue(rawValue);
318
+ }
319
+
320
+ return rawValue;
321
+ }
322
+
323
+ getCurrentRawValue(): D | NotLoadedCoValueState {
287
324
  if (
288
325
  this.value.type === CoValueLoadingState.UNAUTHORIZED ||
289
326
  this.value.type === CoValueLoadingState.UNAVAILABLE
@@ -406,7 +443,7 @@ export class SubscriptionScope<D extends CoValue> {
406
443
  this.subscription.pullValue();
407
444
 
408
445
  // Check if the value is now available
409
- const value = this.getCurrentValue();
446
+ const value = this.getCurrentRawValue();
410
447
 
411
448
  // If the value is available, trigger the listener
412
449
  if (typeof value !== "string") {
@@ -590,6 +627,12 @@ export class SubscriptionScope<D extends CoValue> {
590
627
  return undefined;
591
628
  }
592
629
 
630
+ // Check if $onError: "catch" is specified for this key
631
+ const skipInvalid = typeof depth === "object" && depth.$onError === "catch";
632
+ if (skipInvalid) {
633
+ this.skipInvalidKeys.add(key);
634
+ }
635
+
593
636
  const id = map.$jazz.raw.get(key) as string | undefined;
594
637
  const descriptor = map.$jazz.getDescriptor(key);
595
638
 
@@ -1,4 +1,9 @@
1
- import type { CoValue, CoValueClass, RefEncoded } from "../internal.js";
1
+ import type {
2
+ CoValue,
3
+ CoValueClass,
4
+ MaybeLoaded,
5
+ RefEncoded,
6
+ } from "../internal.js";
2
7
  import { createUnloadedCoValue } from "../internal.js";
3
8
  import { SubscriptionScope } from "./SubscriptionScope.js";
4
9
  import { CoValueLoadingState } from "./types.js";
@@ -56,16 +61,22 @@ export function accessChildByKey<D extends CoValue>(
56
61
  );
57
62
  }
58
63
 
64
+ // TODO: this doesn't check the subscription tree loading state
65
+ // so if one of the children is loading, it will return the loading state
66
+ // instead of the latest loaded state
59
67
  const value = subscriptionScope.childValues.get(childId);
60
68
 
61
69
  if (value?.type === CoValueLoadingState.LOADED) {
62
70
  return value.value;
63
- } else {
64
- return createUnloadedCoValue(
65
- childId,
66
- value?.type ?? CoValueLoadingState.LOADING,
67
- );
68
71
  }
72
+
73
+ const childNode = subscriptionScope.childNodes.get(childId);
74
+
75
+ if (!childNode) {
76
+ return createUnloadedCoValue(childId, CoValueLoadingState.UNAVAILABLE);
77
+ }
78
+
79
+ return childNode.getCurrentValue();
69
80
  }
70
81
 
71
82
  /**
@@ -77,9 +88,9 @@ export function accessChildByKey<D extends CoValue>(
77
88
  * Used for refs that never change (e.g. CoFeed entries, CoMap edits)
78
89
  */
79
90
  export function accessChildById<D extends CoValue>(
80
- parent: D,
91
+ parent: CoValue,
81
92
  childId: string,
82
- schema: RefEncoded<CoValue>,
93
+ schema: RefEncoded<D>,
83
94
  ) {
84
95
  const subscriptionScope = getSubscriptionScope(parent);
85
96
 
@@ -87,12 +98,16 @@ export function accessChildById<D extends CoValue>(
87
98
 
88
99
  const value = subscriptionScope.childValues.get(childId);
89
100
 
101
+ // TODO: this doesn't check the subscription tree loading state
90
102
  if (value?.type === CoValueLoadingState.LOADED) {
91
103
  return value.value;
92
- } else {
93
- return createUnloadedCoValue(
94
- childId,
95
- value?.type ?? CoValueLoadingState.LOADING,
96
- );
97
104
  }
105
+
106
+ const childNode = subscriptionScope.childNodes.get(childId);
107
+
108
+ if (!childNode) {
109
+ return createUnloadedCoValue<D>(childId, CoValueLoadingState.UNAVAILABLE);
110
+ }
111
+
112
+ return childNode.getCurrentValue() as MaybeLoaded<D>;
98
113
  }
@@ -29,10 +29,13 @@ export const CoValueLoadingState = {
29
29
  export type CoValueLoadingState =
30
30
  (typeof CoValueLoadingState)[keyof typeof CoValueLoadingState];
31
31
 
32
+ export type CoValueErrorState =
33
+ | typeof CoValueLoadingState.UNAVAILABLE
34
+ | typeof CoValueLoadingState.UNAUTHORIZED;
35
+
32
36
  export type NotLoadedCoValueState =
33
37
  | typeof CoValueLoadingState.LOADING
34
- | typeof CoValueLoadingState.UNAUTHORIZED
35
- | typeof CoValueLoadingState.UNAVAILABLE;
38
+ | CoValueErrorState;
36
39
 
37
40
  export type SubscriptionValue<D extends CoValue, R extends RefsToResolve<D>> =
38
41
  | {