jazz-tools 0.13.17 → 0.13.19

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 (82) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/CHANGELOG.md +17 -0
  3. package/dist/{chunk-PYBQOYML.js → chunk-4HBHY4I7.js} +735 -595
  4. package/dist/chunk-4HBHY4I7.js.map +1 -0
  5. package/dist/coValues/account.d.ts +5 -4
  6. package/dist/coValues/account.d.ts.map +1 -1
  7. package/dist/coValues/coFeed.d.ts +1 -0
  8. package/dist/coValues/coFeed.d.ts.map +1 -1
  9. package/dist/coValues/coList.d.ts +1 -0
  10. package/dist/coValues/coList.d.ts.map +1 -1
  11. package/dist/coValues/coMap.d.ts +4 -2
  12. package/dist/coValues/coMap.d.ts.map +1 -1
  13. package/dist/coValues/coPlainText.d.ts +2 -2
  14. package/dist/coValues/coPlainText.d.ts.map +1 -1
  15. package/dist/coValues/deepLoading.d.ts +1 -9
  16. package/dist/coValues/deepLoading.d.ts.map +1 -1
  17. package/dist/coValues/extensions/imageDef.d.ts.map +1 -1
  18. package/dist/coValues/group.d.ts.map +1 -1
  19. package/dist/coValues/inbox.d.ts.map +1 -1
  20. package/dist/coValues/interfaces.d.ts +4 -1
  21. package/dist/coValues/interfaces.d.ts.map +1 -1
  22. package/dist/implementation/createContext.d.ts.map +1 -1
  23. package/dist/implementation/refs.d.ts +5 -10
  24. package/dist/implementation/refs.d.ts.map +1 -1
  25. package/dist/index.js +1 -1
  26. package/dist/internal.d.ts +1 -1
  27. package/dist/internal.d.ts.map +1 -1
  28. package/dist/subscribe/CoValueCoreSubscription.d.ts +14 -0
  29. package/dist/subscribe/CoValueCoreSubscription.d.ts.map +1 -0
  30. package/dist/subscribe/JazzError.d.ts +16 -0
  31. package/dist/subscribe/JazzError.d.ts.map +1 -0
  32. package/dist/subscribe/SubscriptionScope.d.ts +43 -0
  33. package/dist/subscribe/SubscriptionScope.d.ts.map +1 -0
  34. package/dist/subscribe/index.d.ts +21 -0
  35. package/dist/subscribe/index.d.ts.map +1 -0
  36. package/dist/subscribe/types.d.ts +12 -0
  37. package/dist/subscribe/types.d.ts.map +1 -0
  38. package/dist/subscribe/utils.d.ts +10 -0
  39. package/dist/subscribe/utils.d.ts.map +1 -0
  40. package/dist/testing.js +2 -2
  41. package/dist/testing.js.map +1 -1
  42. package/dist/tests/coMap.record.test.d.ts +2 -0
  43. package/dist/tests/coMap.record.test.d.ts.map +1 -0
  44. package/dist/tests/utils.d.ts +2 -2
  45. package/dist/tests/utils.d.ts.map +1 -1
  46. package/package.json +2 -2
  47. package/src/coValues/account.ts +43 -31
  48. package/src/coValues/coFeed.ts +11 -7
  49. package/src/coValues/coList.ts +13 -17
  50. package/src/coValues/coMap.ts +72 -80
  51. package/src/coValues/coPlainText.ts +13 -2
  52. package/src/coValues/deepLoading.ts +4 -277
  53. package/src/coValues/extensions/imageDef.ts +1 -7
  54. package/src/coValues/group.ts +7 -6
  55. package/src/coValues/inbox.ts +4 -11
  56. package/src/coValues/interfaces.ts +54 -111
  57. package/src/implementation/createContext.ts +3 -4
  58. package/src/implementation/invites.ts +2 -2
  59. package/src/implementation/refs.ts +30 -121
  60. package/src/internal.ts +1 -2
  61. package/src/subscribe/CoValueCoreSubscription.ts +71 -0
  62. package/src/subscribe/JazzError.ts +48 -0
  63. package/src/subscribe/SubscriptionScope.ts +536 -0
  64. package/src/subscribe/index.ts +82 -0
  65. package/src/subscribe/types.ts +7 -0
  66. package/src/subscribe/utils.ts +36 -0
  67. package/src/testing.ts +1 -1
  68. package/src/tests/ContextManager.test.ts +13 -9
  69. package/src/tests/coFeed.test.ts +6 -6
  70. package/src/tests/coList.test.ts +304 -115
  71. package/src/tests/coMap.record.test.ts +325 -0
  72. package/src/tests/coMap.test.ts +718 -645
  73. package/src/tests/coPlainText.test.ts +2 -2
  74. package/src/tests/createContext.test.ts +8 -8
  75. package/src/tests/deepLoading.test.ts +8 -34
  76. package/src/tests/groupsAndAccounts.test.ts +6 -4
  77. package/src/tests/subscribe.test.ts +614 -42
  78. package/src/tests/utils.ts +8 -6
  79. package/dist/chunk-PYBQOYML.js.map +0 -1
  80. package/dist/implementation/subscriptionScope.d.ts +0 -34
  81. package/dist/implementation/subscriptionScope.d.ts.map +0 -1
  82. package/src/implementation/subscriptionScope.ts +0 -165
@@ -0,0 +1,536 @@
1
+ import type { LocalNode, RawCoValue } from "cojson";
2
+ import type { CoFeed, CoList, CoMap } from "../exports.js";
3
+ import {
4
+ type CoValue,
5
+ type ID,
6
+ type RefEncoded,
7
+ type RefsToResolve,
8
+ isRefEncoded,
9
+ } from "../internal.js";
10
+ import { CoValueCoreSubscription } from "./CoValueCoreSubscription.js";
11
+ import { JazzError, type JazzErrorIssue } from "./JazzError.js";
12
+ import type { SubscriptionValue, Unloaded } from "./types.js";
13
+ import { createCoValue, getOwnerFromRawValue } from "./utils.js";
14
+
15
+ export class SubscriptionScope<D extends CoValue> {
16
+ childNodes = new Map<string, SubscriptionScope<CoValue>>();
17
+ childValues: Map<string, SubscriptionValue<any, any> | Unloaded> = new Map<
18
+ string,
19
+ SubscriptionValue<D, any>
20
+ >();
21
+ value: SubscriptionValue<D, any> | Unloaded;
22
+ childErrors: Map<string, JazzError> = new Map();
23
+ validationErrors: Map<string, JazzError> = new Map();
24
+ errorFromChildren: JazzError | undefined;
25
+ subscription: CoValueCoreSubscription;
26
+ dirty = false;
27
+ resolve: RefsToResolve<any>;
28
+ idsSubscribed = new Set<string>();
29
+ autoloaded = new Set<string>();
30
+ autoloadedKeys = new Set<string>();
31
+ totalValidTransactions = 0;
32
+
33
+ silenceUpdates = false;
34
+
35
+ constructor(
36
+ public node: LocalNode,
37
+ resolve: RefsToResolve<D>,
38
+ public id: ID<D>,
39
+ public schema: RefEncoded<D>,
40
+ ) {
41
+ this.resolve = resolve;
42
+ this.value = { type: "unloaded", id };
43
+ this.subscription = new CoValueCoreSubscription(node, id, (value) => {
44
+ this.handleUpdate(value);
45
+ });
46
+ }
47
+
48
+ updateValue(value: SubscriptionValue<D, any>) {
49
+ this.value = value;
50
+
51
+ // Flags that the value has changed and we need to trigger an update
52
+ this.dirty = true;
53
+ }
54
+
55
+ handleUpdate(update: RawCoValue | "unavailable") {
56
+ if (update === "unavailable") {
57
+ if (this.value.type === "unloaded") {
58
+ this.updateValue(
59
+ new JazzError(this.id, "unavailable", [
60
+ {
61
+ code: "unavailable",
62
+ message: "The value is unavailable",
63
+ params: {
64
+ id: this.id,
65
+ },
66
+ path: [],
67
+ },
68
+ ]),
69
+ );
70
+ }
71
+ this.triggerUpdate();
72
+ return;
73
+ }
74
+
75
+ const owner = getOwnerFromRawValue(update);
76
+
77
+ const ruleset = update.core.verified.header.ruleset;
78
+
79
+ // Groups and accounts are accessible by everyone, for the other coValues we use the role to check access
80
+ const hasAccess = ruleset.type === "group" || owner.myRole() !== undefined;
81
+
82
+ if (!hasAccess) {
83
+ if (this.value.type !== "unauthorized") {
84
+ this.updateValue(
85
+ new JazzError(this.id, "unauthorized", [
86
+ {
87
+ code: "unauthorized",
88
+ message:
89
+ "The current user is not authorized to access this value",
90
+ params: {
91
+ id: this.id,
92
+ },
93
+ path: [],
94
+ },
95
+ ]),
96
+ );
97
+ this.triggerUpdate();
98
+ }
99
+ return;
100
+ }
101
+
102
+ // When resolving a CoValue with available children, we want to trigger a single update
103
+ // after loading all the children, not one per children
104
+ this.silenceUpdates = true;
105
+
106
+ if (this.value.type !== "loaded") {
107
+ this.updateValue(createCoValue(this.schema, update, this));
108
+ this.loadChildren();
109
+ } else {
110
+ const hasChanged =
111
+ update.totalValidTransactions !== this.totalValidTransactions ||
112
+ // Checking the identity of the _raw value makes us cover the cases where the group
113
+ // has been updated and the coValues that don't update the totalValidTransactions value (e.g. FileStream)
114
+ this.value.value._raw !== update;
115
+
116
+ if (this.loadChildren()) {
117
+ this.updateValue(createCoValue(this.schema, update, this));
118
+ } else if (hasChanged) {
119
+ this.updateValue(createCoValue(this.schema, update, this));
120
+ }
121
+ }
122
+
123
+ this.totalValidTransactions = update.totalValidTransactions;
124
+
125
+ this.silenceUpdates = false;
126
+ this.triggerUpdate();
127
+ }
128
+
129
+ computeChildErrors() {
130
+ let issues: JazzErrorIssue[] = [];
131
+ let errorType: JazzError["type"] = "unavailable";
132
+
133
+ if (this.childErrors.size === 0 && this.validationErrors.size === 0) {
134
+ return undefined;
135
+ }
136
+
137
+ for (const [key, value] of this.childErrors.entries()) {
138
+ // We don't want to block updates if the error is on an autoloaded value
139
+ if (this.autoloaded.has(key)) {
140
+ continue;
141
+ }
142
+
143
+ errorType = value.type;
144
+ if (value.issues) {
145
+ issues.push(...value.issues);
146
+ }
147
+ }
148
+
149
+ for (const value of this.validationErrors.values()) {
150
+ errorType = value.type;
151
+ if (value.issues) {
152
+ issues.push(...value.issues);
153
+ }
154
+ }
155
+
156
+ if (issues.length) {
157
+ return new JazzError(this.id, errorType, issues);
158
+ }
159
+
160
+ return undefined;
161
+ }
162
+
163
+ handleChildUpdate = (
164
+ id: string,
165
+ value: SubscriptionValue<any, any> | Unloaded,
166
+ key?: string,
167
+ ) => {
168
+ if (value.type === "unloaded") {
169
+ return;
170
+ }
171
+
172
+ this.childValues.set(id, value);
173
+
174
+ if (value.type === "unavailable" || value.type === "unauthorized") {
175
+ this.childErrors.set(id, value.prependPath(key ?? id));
176
+
177
+ this.errorFromChildren = this.computeChildErrors();
178
+ } else if (this.errorFromChildren && this.childErrors.has(id)) {
179
+ this.childErrors.delete(id);
180
+
181
+ this.errorFromChildren = this.computeChildErrors();
182
+ }
183
+
184
+ if (this.shouldSendUpdates()) {
185
+ if (this.value.type === "loaded") {
186
+ // On child updates, we re-create the value instance to make the updates
187
+ // seamless-immutable and so be compatible with React and the React compiler
188
+ this.updateValue(
189
+ createCoValue(this.schema, this.value.value._raw, this),
190
+ );
191
+ }
192
+ }
193
+
194
+ this.triggerUpdate();
195
+ };
196
+
197
+ shouldSendUpdates() {
198
+ if (this.value.type === "unloaded") return false;
199
+
200
+ // If the value is in error, we send the update regardless of the children statuses
201
+ if (this.value.type !== "loaded") return true;
202
+
203
+ for (const value of this.childValues.values()) {
204
+ // We don't wait for autoloaded values to be loaded, in order to stream updates
205
+ // on autoloaded lists or records
206
+ if (value.type === "unloaded" && !this.autoloaded.has(value.id)) {
207
+ return false;
208
+ }
209
+ }
210
+
211
+ return true;
212
+ }
213
+
214
+ triggerUpdate() {
215
+ if (!this.shouldSendUpdates()) return;
216
+ if (!this.dirty) return;
217
+ if (this.subscribers.size === 0) return;
218
+ if (this.silenceUpdates) return;
219
+
220
+ const error = this.errorFromChildren;
221
+ const value = this.value;
222
+
223
+ if (error) {
224
+ this.subscribers.forEach((listener) => listener(error));
225
+ } else if (value.type !== "unloaded") {
226
+ this.subscribers.forEach((listener) => listener(value));
227
+ }
228
+
229
+ this.dirty = false;
230
+ }
231
+
232
+ subscribers = new Set<(value: SubscriptionValue<D, any>) => void>();
233
+ subscribe(listener: (value: SubscriptionValue<D, any>) => void) {
234
+ this.subscribers.add(listener);
235
+
236
+ return () => {
237
+ this.subscribers.delete(listener);
238
+ };
239
+ }
240
+
241
+ setListener(listener: (value: SubscriptionValue<D, any>) => void) {
242
+ this.subscribers.add(listener);
243
+ this.triggerUpdate();
244
+ }
245
+
246
+ subscribeToKey(key: string) {
247
+ if (this.resolve === true || !this.resolve) {
248
+ this.resolve = {};
249
+ }
250
+
251
+ if (this.resolve.$each || key in this.resolve) {
252
+ return;
253
+ }
254
+
255
+ // Adding the key to the resolve object to resolve the key when calling loadChildren
256
+ this.resolve[key as keyof typeof this.resolve] = true;
257
+ // Track the keys that are autoloaded to flag any id on that key as autoloaded
258
+ this.autoloadedKeys.add(key);
259
+
260
+ if (this.value.type !== "loaded") {
261
+ return;
262
+ }
263
+
264
+ const value = this.value.value;
265
+
266
+ // We don't want to trigger an update when autoloading available children
267
+ // because on userland it looks like nothing has changed since the value
268
+ // is available on the first access
269
+ // This helps alot with correctness when triggering the autoloading while rendering components (on React and Svelte)
270
+ this.silenceUpdates = true;
271
+
272
+ if (value._type === "CoMap" || value._type === "Account") {
273
+ const map = value as CoMap;
274
+
275
+ this.loadCoMapKey(map, key, true);
276
+ } else if (value._type === "CoList") {
277
+ const list = value as CoList;
278
+
279
+ this.loadCoListKey(list, key, true);
280
+ }
281
+
282
+ this.silenceUpdates = false;
283
+ }
284
+
285
+ subscribeToId(id: string, descriptor: RefEncoded<any>) {
286
+ if (this.idsSubscribed.has(id) || this.childValues.has(id)) {
287
+ return;
288
+ }
289
+
290
+ this.idsSubscribed.add(id);
291
+ this.autoloaded.add(id);
292
+
293
+ // We don't want to trigger an update when autoloading available children
294
+ // because on userland it looks like nothing has changed since the value
295
+ // is available on the first access
296
+ // This helps alot with correctness when triggering the autoloading while rendering components (on React and Svelte)
297
+ this.silenceUpdates = true;
298
+
299
+ this.childValues.set(id, { type: "unloaded", id });
300
+ const child = new SubscriptionScope(
301
+ this.node,
302
+ true,
303
+ id as ID<any>,
304
+ descriptor,
305
+ );
306
+ this.childNodes.set(id, child);
307
+ child.setListener((value) => this.handleChildUpdate(id, value));
308
+
309
+ this.silenceUpdates = false;
310
+ }
311
+
312
+ loadChildren() {
313
+ const { resolve } = this;
314
+
315
+ if (this.value.type !== "loaded") {
316
+ return false;
317
+ }
318
+
319
+ const value = this.value.value;
320
+
321
+ const depth =
322
+ typeof resolve !== "object" || resolve === null ? {} : (resolve as any);
323
+
324
+ let hasChanged = false;
325
+
326
+ const idsToLoad = new Set<string>(this.idsSubscribed);
327
+
328
+ const coValueType = value._type;
329
+
330
+ if (Object.keys(depth).length > 0) {
331
+ if (coValueType === "CoMap" || coValueType === "Account") {
332
+ const map = value as CoMap;
333
+ const keys = "$each" in depth ? map._raw.keys() : Object.keys(depth);
334
+
335
+ for (const key of keys) {
336
+ const id = this.loadCoMapKey(map, key, depth[key] ?? depth.$each);
337
+
338
+ if (id) {
339
+ idsToLoad.add(id);
340
+ }
341
+ }
342
+ } else if (value._type === "CoList") {
343
+ const list = value as CoList;
344
+
345
+ const descriptor = list.getItemsDescriptor();
346
+
347
+ if (descriptor && isRefEncoded(descriptor)) {
348
+ list._raw.processNewTransactions();
349
+ const entries = list._raw.entries();
350
+ const keys =
351
+ "$each" in depth ? Object.keys(entries) : Object.keys(depth);
352
+
353
+ for (const key of keys) {
354
+ const id = this.loadCoListKey(list, key, depth[key] ?? depth.$each);
355
+
356
+ if (id) {
357
+ idsToLoad.add(id);
358
+ }
359
+ }
360
+ }
361
+ } else if (value._type === "CoStream") {
362
+ const stream = value as CoFeed;
363
+ const descriptor = stream.getItemsDescriptor();
364
+
365
+ if (descriptor && isRefEncoded(descriptor)) {
366
+ for (const session of stream._raw.sessions()) {
367
+ const values = stream._raw.items[session] ?? [];
368
+
369
+ for (const [i, item] of values.entries()) {
370
+ const key = `${session}/${i}`;
371
+
372
+ if (!depth.$each && !depth[key]) {
373
+ continue;
374
+ }
375
+
376
+ const id = item.value as string | undefined;
377
+
378
+ if (id) {
379
+ idsToLoad.add(id);
380
+ this.loadChildNode(id, depth[key] ?? depth.$each, descriptor);
381
+ this.validationErrors.delete(key);
382
+ } else if (!descriptor.optional) {
383
+ this.validationErrors.set(
384
+ key,
385
+ new JazzError(undefined, "unavailable", [
386
+ {
387
+ code: "validationError",
388
+ message: `The ref on position ${key} requested on ${stream.constructor.name} is missing`,
389
+ params: {},
390
+ path: [key],
391
+ },
392
+ ]),
393
+ );
394
+ }
395
+ }
396
+ }
397
+ }
398
+ }
399
+ }
400
+
401
+ this.errorFromChildren = this.computeChildErrors();
402
+
403
+ // Collect all the deleted ids
404
+ for (const id of this.childNodes.keys()) {
405
+ if (!idsToLoad.has(id)) {
406
+ hasChanged = true;
407
+ const childNode = this.childNodes.get(id);
408
+
409
+ if (childNode) {
410
+ childNode.destroy();
411
+ }
412
+
413
+ this.childNodes.delete(id);
414
+ this.childValues.delete(id);
415
+ }
416
+ }
417
+
418
+ return hasChanged;
419
+ }
420
+
421
+ loadCoMapKey(map: CoMap, key: string, depth: Record<string, any> | true) {
422
+ const id = map._raw.get(key) as string | undefined;
423
+ const descriptor = map.getDescriptor(key);
424
+
425
+ if (!descriptor) {
426
+ this.childErrors.set(
427
+ key,
428
+ new JazzError(undefined, "unavailable", [
429
+ {
430
+ code: "validationError",
431
+ message: `The ref ${key} requested on ${map.constructor.name} is not defined in the schema`,
432
+ params: {},
433
+ path: [key],
434
+ },
435
+ ]),
436
+ );
437
+ return undefined;
438
+ }
439
+
440
+ if (isRefEncoded(descriptor)) {
441
+ if (id) {
442
+ this.loadChildNode(id, depth, descriptor, key);
443
+ this.validationErrors.delete(key);
444
+
445
+ return id;
446
+ } else if (!descriptor.optional) {
447
+ this.validationErrors.set(
448
+ key,
449
+ new JazzError(undefined, "unavailable", [
450
+ {
451
+ code: "validationError",
452
+ message: `The ref ${key} requested on ${map.constructor.name} is missing`,
453
+ params: {},
454
+ path: [key],
455
+ },
456
+ ]),
457
+ );
458
+ }
459
+ }
460
+
461
+ return undefined;
462
+ }
463
+
464
+ loadCoListKey(list: CoList, key: string, depth: Record<string, any> | true) {
465
+ const descriptor = list.getItemsDescriptor();
466
+
467
+ if (!descriptor || !isRefEncoded(descriptor)) {
468
+ return undefined;
469
+ }
470
+
471
+ const entries = list._raw.entries();
472
+ const entry = entries[Number(key)];
473
+
474
+ if (!entry) {
475
+ return undefined;
476
+ }
477
+
478
+ const id = entry.value as string | undefined;
479
+
480
+ if (id) {
481
+ this.loadChildNode(id, depth, descriptor, key);
482
+ this.validationErrors.delete(key);
483
+
484
+ return id;
485
+ } else if (!descriptor.optional) {
486
+ this.validationErrors.set(
487
+ key,
488
+ new JazzError(undefined, "unavailable", [
489
+ {
490
+ code: "validationError",
491
+ message: `The ref on position ${key} requested on ${list.constructor.name} is missing`,
492
+ params: {},
493
+ path: [key],
494
+ },
495
+ ]),
496
+ );
497
+ }
498
+
499
+ return undefined;
500
+ }
501
+
502
+ loadChildNode(
503
+ id: string,
504
+ query: RefsToResolve<any>,
505
+ descriptor: RefEncoded<any>,
506
+ key?: string,
507
+ ) {
508
+ if (this.childValues.has(id)) {
509
+ return;
510
+ }
511
+
512
+ if (key && this.autoloadedKeys.has(key)) {
513
+ this.autoloaded.add(id);
514
+ }
515
+
516
+ // Cloning the resolve objects to avoid mutating the original object when tracking autoloaded values
517
+ const resolve =
518
+ typeof query === "object" && query !== null ? { ...query } : query;
519
+
520
+ this.childValues.set(id, { type: "unloaded", id });
521
+ const child = new SubscriptionScope(
522
+ this.node,
523
+ resolve,
524
+ id as ID<any>,
525
+ descriptor,
526
+ );
527
+ this.childNodes.set(id, child);
528
+ child.setListener((value) => this.handleChildUpdate(id, value, key));
529
+ }
530
+
531
+ destroy() {
532
+ this.subscription.unsubscribe();
533
+ this.subscribers.clear();
534
+ this.childNodes.forEach((child) => child.destroy());
535
+ }
536
+ }
@@ -0,0 +1,82 @@
1
+ import type { CoValue, CoValueClass, RefEncoded } from "../internal.js";
2
+ import { SubscriptionScope } from "./SubscriptionScope.js";
3
+
4
+ export function getSubscriptionScope<D extends CoValue>(value: D) {
5
+ const subscriptionScope = value._subscriptionScope;
6
+
7
+ if (subscriptionScope) {
8
+ return subscriptionScope;
9
+ }
10
+
11
+ const node = value._raw.core.node;
12
+ const resolve = true;
13
+ const id = value.id;
14
+
15
+ const newSubscriptionScope = new SubscriptionScope(node, resolve, id, {
16
+ ref: value.constructor as CoValueClass<D>,
17
+ optional: false,
18
+ });
19
+
20
+ Object.defineProperty(value, "_subscriptionScope", {
21
+ value: subscriptionScope,
22
+ writable: false,
23
+ enumerable: false,
24
+ configurable: false,
25
+ });
26
+
27
+ return newSubscriptionScope;
28
+ }
29
+
30
+ /** Autoload internals */
31
+
32
+ /**
33
+ * Given a coValue, access a child coValue by key
34
+ *
35
+ * By subscribing to a given key, the subscription will automatically react to the id changes
36
+ * on that key (e.g. deleting the key value will result on unsubscribing from the id)
37
+ */
38
+ export function accessChildByKey<D extends CoValue>(
39
+ parent: D,
40
+ childId: string,
41
+ key: string,
42
+ ) {
43
+ const subscriptionScope = getSubscriptionScope(parent);
44
+
45
+ if (!subscriptionScope.childValues.has(childId)) {
46
+ subscriptionScope.subscribeToKey(key);
47
+ }
48
+
49
+ const value = subscriptionScope.childValues.get(childId);
50
+
51
+ if (value?.type === "loaded") {
52
+ return value.value;
53
+ } else {
54
+ return null;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Given a coValue, access a child coValue by id
60
+ *
61
+ * By subscribing to a given id, the subscription becomes permanent and will unsubscribe
62
+ * only when the root subscription scope is destroyed.
63
+ *
64
+ * Used for refs that never change (e.g. CoFeed entries, CoMap edits)
65
+ */
66
+ export function accessChildById<D extends CoValue>(
67
+ parent: D,
68
+ childId: string,
69
+ schema: RefEncoded<CoValue>,
70
+ ) {
71
+ const subscriptionScope = getSubscriptionScope(parent);
72
+
73
+ subscriptionScope.subscribeToId(childId, schema);
74
+
75
+ const value = subscriptionScope.childValues.get(childId);
76
+
77
+ if (value?.type === "loaded") {
78
+ return value.value;
79
+ } else {
80
+ return null;
81
+ }
82
+ }
@@ -0,0 +1,7 @@
1
+ import type { CoValue, RefsToResolve, Resolved } from "../internal.js";
2
+ import type { JazzError } from "./JazzError.js";
3
+
4
+ export type SubscriptionValue<D extends CoValue, R extends RefsToResolve<D>> =
5
+ | { type: "loaded"; value: Resolved<D, R>; id: string }
6
+ | JazzError;
7
+ export type Unloaded = { type: "unloaded"; id: string };
@@ -0,0 +1,36 @@
1
+ import { RawAccount, RawCoValue, RawGroup } from "cojson";
2
+ import { RegisteredSchemas } from "../coValues/registeredSchemas.js";
3
+ import { CoValue, RefEncoded, instantiateRefEncoded } from "../internal.js";
4
+ import { coValuesCache } from "../lib/cache.js";
5
+ import { SubscriptionScope } from "./SubscriptionScope.js";
6
+
7
+ export function getOwnerFromRawValue(raw: RawCoValue) {
8
+ let owner = raw instanceof RawGroup ? raw : raw.group;
9
+
10
+ return coValuesCache.get(owner as any, () =>
11
+ owner instanceof RawAccount
12
+ ? RegisteredSchemas["Account"].fromRaw(owner)
13
+ : RegisteredSchemas["Group"].fromRaw(owner as any),
14
+ );
15
+ }
16
+
17
+ export function createCoValue<D extends CoValue>(
18
+ ref: RefEncoded<D>,
19
+ raw: RawCoValue,
20
+ subscriptionScope: SubscriptionScope<D>,
21
+ ) {
22
+ const freshValueInstance = instantiateRefEncoded(ref, raw);
23
+
24
+ Object.defineProperty(freshValueInstance, "_subscriptionScope", {
25
+ value: subscriptionScope,
26
+ writable: false,
27
+ enumerable: false,
28
+ configurable: false,
29
+ });
30
+
31
+ return {
32
+ type: "loaded" as const,
33
+ value: freshValueInstance,
34
+ id: subscriptionScope.id,
35
+ };
36
+ }
package/src/testing.ts CHANGED
@@ -185,7 +185,7 @@ export class TestJazzContextManager<
185
185
 
186
186
  const credentials = {
187
187
  accountID: account.id,
188
- accountSecret: node.account.agentSecret,
188
+ accountSecret: node.getCurrentAgent().agentSecret,
189
189
  secretSeed: SecretSeedMap.get(account.id),
190
190
  provider,
191
191
  } satisfies AuthCredentials;