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
@@ -1,11 +1,4 @@
1
- import {
2
- CoID,
3
- InviteSecret,
4
- RawAccount,
5
- RawCoMap,
6
- RawControlledAccount,
7
- SessionID,
8
- } from "cojson";
1
+ import { CoID, InviteSecret, RawAccount, RawCoMap, SessionID } from "cojson";
9
2
  import { CoStreamItem, RawCoStream } from "cojson";
10
3
  import { activeAccountContext } from "../implementation/activeAccountContext.js";
11
4
  import { type Account } from "./account.js";
@@ -32,9 +25,9 @@ export function createInboxRoot(account: Account) {
32
25
  throw new Error("Account is not controlled");
33
26
  }
34
27
 
35
- const rawAccount = account._raw as RawControlledAccount;
28
+ const rawAccount = account._raw;
36
29
 
37
- const group = rawAccount.createGroup();
30
+ const group = rawAccount.core.node.createGroup();
38
31
  const messagesFeed = group.createStream<MessagesStream>();
39
32
 
40
33
  const inboxRoot = rawAccount.createMap<InboxRoot>();
@@ -386,7 +379,7 @@ async function acceptInvite(invite: string, account?: Account) {
386
379
  throw new Error("Account is not controlled");
387
380
  }
388
381
 
389
- await (account._raw as RawControlledAccount).acceptInvite(id, inviteSecret);
382
+ await account._raw.core.node.acceptInvite(id, inviteSecret);
390
383
 
391
384
  return id;
392
385
  }
@@ -3,23 +3,15 @@ import type {
3
3
  CojsonInternalTypes,
4
4
  RawCoValue,
5
5
  } from "cojson";
6
- import { RawAccount } from "cojson";
6
+ import { ControlledAccount, RawAccount } from "cojson";
7
7
  import { activeAccountContext } from "../implementation/activeAccountContext.js";
8
8
  import { AnonymousJazzAgent } from "../implementation/anonymousJazzAgent.js";
9
- import {
10
- Ref,
11
- SubscriptionScope,
12
- inspect,
13
- subscriptionsScopes,
14
- } from "../internal.js";
9
+ import { inspect } from "../internal.js";
15
10
  import { coValuesCache } from "../lib/cache.js";
11
+ import { SubscriptionScope } from "../subscribe/SubscriptionScope.js";
12
+ import { SubscriptionValue } from "../subscribe/types.js";
16
13
  import { type Account } from "./account.js";
17
- import {
18
- RefsToResolve,
19
- RefsToResolveStrict,
20
- Resolved,
21
- fulfillsDepth,
22
- } from "./deepLoading.js";
14
+ import { RefsToResolve, RefsToResolveStrict, Resolved } from "./deepLoading.js";
23
15
  import { type Group } from "./group.js";
24
16
  import { RegisteredSchemas } from "./registeredSchemas.js";
25
17
 
@@ -46,6 +38,10 @@ export interface CoValue {
46
38
  _owner: Account | Group;
47
39
  /** @category Internals */
48
40
  _raw: RawCoValue;
41
+
42
+ /** @internal */
43
+ _subscriptionScope?: SubscriptionScope<this>;
44
+
49
45
  /** @internal */
50
46
  readonly _loadedAs: Account | AnonymousJazzAgent;
51
47
  /** @category Stringifying & Inspection */
@@ -93,27 +89,22 @@ export class CoValueBase implements CoValue {
93
89
  declare _instanceID: string;
94
90
 
95
91
  get _owner(): Account | Group {
96
- const owner =
92
+ const owner = coValuesCache.get(this._raw.group, () =>
97
93
  this._raw.group instanceof RawAccount
98
94
  ? RegisteredSchemas["Account"].fromRaw(this._raw.group)
99
- : RegisteredSchemas["Group"].fromRaw(this._raw.group);
100
-
101
- const subScope = subscriptionsScopes.get(this);
102
- if (subScope) {
103
- subScope.onRefAccessedOrSet(this.id, owner.id);
104
- subscriptionsScopes.set(owner, subScope);
105
- }
95
+ : RegisteredSchemas["Group"].fromRaw(this._raw.group),
96
+ );
106
97
 
107
98
  return owner;
108
99
  }
109
100
 
110
101
  /** @private */
111
102
  get _loadedAs() {
112
- const rawAccount = this._raw.core.node.account;
103
+ const agent = this._raw.core.node.getCurrentAgent();
113
104
 
114
- if (rawAccount instanceof RawAccount) {
115
- return coValuesCache.get(rawAccount, () =>
116
- RegisteredSchemas["Account"].fromRaw(rawAccount),
105
+ if (agent instanceof ControlledAccount) {
106
+ return coValuesCache.get(agent.account, () =>
107
+ RegisteredSchemas["Account"].fromRaw(agent.account),
117
108
  );
118
109
  }
119
110
 
@@ -150,12 +141,7 @@ export class CoValueBase implements CoValue {
150
141
  castAs<Cl extends CoValueClass & CoValueFromRaw<CoValue>>(
151
142
  cl: Cl,
152
143
  ): InstanceType<Cl> {
153
- const casted = cl.fromRaw(this._raw) as InstanceType<Cl>;
154
- const subscriptionScope = subscriptionsScopes.get(this);
155
- if (subscriptionScope) {
156
- subscriptionsScopes.set(casted, subscriptionScope);
157
- }
158
- return casted;
144
+ return cl.fromRaw(this._raw) as InstanceType<Cl>;
159
145
  }
160
146
  }
161
147
 
@@ -194,6 +180,7 @@ export function loadCoValue<
194
180
  {
195
181
  resolve: options.resolve,
196
182
  loadAs: options.loadAs,
183
+ syncResolution: true,
197
184
  onUnavailable: () => {
198
185
  resolve(null);
199
186
  },
@@ -317,102 +304,58 @@ export function subscribeToCoValue<
317
304
  resolve?: RefsToResolveStrict<V, R>;
318
305
  loadAs: Account | AnonymousJazzAgent;
319
306
  onUnavailable?: () => void;
320
- onUnauthorized?: (errorPath: string[]) => void;
307
+ onUnauthorized?: () => void;
321
308
  syncResolution?: boolean;
322
309
  },
323
310
  listener: SubscribeListener<V, R>,
324
311
  ): () => void {
325
- const ref = new Ref(id, options.loadAs, { ref: cls, optional: false });
312
+ const loadAs = options.loadAs ?? activeAccountContext.get();
313
+ const node = "node" in loadAs ? loadAs.node : loadAs._raw.core.node;
326
314
 
327
- let unsubscribed = false;
328
- let unsubscribe: (() => void) | undefined;
315
+ const resolve = options.resolve ?? true;
329
316
 
330
- function subscribe() {
331
- const value = ref.getValueWithoutAccessCheck();
317
+ let unsubscribed = false;
332
318
 
333
- if (!value) {
334
- options.onUnavailable?.();
335
- return;
336
- }
319
+ const rootNode = new SubscriptionScope<V>(node, resolve, id as ID<V>, {
320
+ ref: cls,
321
+ optional: false,
322
+ });
337
323
 
324
+ const handleUpdate = (value: SubscriptionValue<V, any>) => {
338
325
  if (unsubscribed) return;
339
326
 
340
- const subscription = new SubscriptionScope(
341
- value,
342
- cls as CoValueClass<V> & CoValueFromRaw<V>,
343
- (update, subscription) => {
344
- if (subscription.syncResolution) return;
345
-
346
- if (!ref.hasReadAccess()) {
347
- console.error(
348
- "Not enough permissions to load / subscribe to CoValue",
349
- id,
350
- );
351
- options.onUnauthorized?.([]);
352
- return;
353
- }
354
-
355
- let result;
356
-
357
- try {
358
- subscription.syncResolution = true;
359
- result = fulfillsDepth(options.resolve, update);
360
- } catch (e) {
361
- console.error(
362
- "Failed to load / subscribe to CoValue",
363
- e,
364
- e instanceof Error ? e.stack : undefined,
365
- );
366
- options.onUnavailable?.();
367
- return;
368
- } finally {
369
- subscription.syncResolution = false;
370
- }
371
-
372
- if (result.status === "unauthorized") {
373
- console.error(
374
- "Not enough permissions to load / subscribe to CoValue",
375
- id,
376
- "on path",
377
- result.path.join("."),
378
- "unaccessible value:",
379
- result.id,
380
- );
381
- options.onUnauthorized?.(result.path);
382
- return;
383
- }
384
-
385
- if (result.status === "fulfilled") {
386
- listener(update as Resolved<V, R>, subscription.unsubscribeAll);
387
- }
388
- },
389
- );
327
+ if (value.type === "unavailable") {
328
+ options.onUnavailable?.();
390
329
 
391
- unsubscribe = subscription.unsubscribeAll;
392
- }
330
+ console.error(value.toString());
331
+ } else if (value.type === "unauthorized") {
332
+ options.onUnauthorized?.();
393
333
 
394
- const sync = options.syncResolution ? ref.syncLoad() : undefined;
334
+ console.error(value.toString());
335
+ } else if (value.type === "loaded") {
336
+ listener(value.value as Resolved<V, R>, unsubscribe);
337
+ }
338
+ };
395
339
 
396
- if (sync) {
397
- subscribe();
398
- } else {
399
- ref
400
- .load()
401
- .then(() => subscribe())
402
- .catch((e) => {
403
- console.error(
404
- "Failed to load / subscribe to CoValue",
405
- e,
406
- e instanceof Error ? e.stack : undefined,
407
- );
408
- options.onUnavailable?.();
340
+ let shouldDefer = !options.syncResolution;
341
+
342
+ rootNode.setListener((value) => {
343
+ if (shouldDefer) {
344
+ shouldDefer = false;
345
+ Promise.resolve().then(() => {
346
+ handleUpdate(value);
409
347
  });
410
- }
348
+ } else {
349
+ handleUpdate(value);
350
+ }
351
+ });
411
352
 
412
- return function unsubscribeAtAnyPoint() {
353
+ function unsubscribe() {
413
354
  unsubscribed = true;
414
- unsubscribe && unsubscribe();
415
- };
355
+ rootNode.destroy();
356
+ }
357
+
358
+ return unsubscribe;
416
359
  }
417
360
 
418
361
  export function createCoValueObservable<
@@ -248,7 +248,7 @@ export async function createJazzContext<Acc extends Account>(options: {
248
248
  await authSecretStorage.setWithoutNotify({
249
249
  accountID: context.account.id,
250
250
  secretSeed,
251
- accountSecret: context.node.account.agentSecret,
251
+ accountSecret: context.node.getCurrentAgent().agentSecret,
252
252
  provider: "anonymous",
253
253
  });
254
254
  }
@@ -268,11 +268,10 @@ export async function createAnonymousJazzContext({
268
268
  crypto: CryptoProvider;
269
269
  }): Promise<JazzContextWithAgent> {
270
270
  const agentSecret = crypto.newRandomAgentSecret();
271
- const rawAgent = new ControlledAgent(agentSecret, crypto);
272
271
 
273
272
  const node = new LocalNode(
274
- rawAgent,
275
- crypto.newRandomSessionID(rawAgent.id),
273
+ agentSecret,
274
+ crypto.newRandomSessionID(crypto.getAgentID(agentSecret)),
276
275
  crypto,
277
276
  );
278
277
 
@@ -12,11 +12,11 @@ export function createInviteLink<C extends CoValue>(
12
12
  const coValueCore = value._raw.core;
13
13
  let currentCoValue = coValueCore;
14
14
 
15
- while (currentCoValue.header.ruleset.type === "ownedByGroup") {
15
+ while (currentCoValue.verified.header.ruleset.type === "ownedByGroup") {
16
16
  currentCoValue = currentCoValue.getGroup().core;
17
17
  }
18
18
 
19
- const { ruleset, meta } = currentCoValue.header;
19
+ const { ruleset, meta } = currentCoValue.verified.header;
20
20
 
21
21
  if (ruleset.type !== "group" || meta?.type === "account") {
22
22
  throw new Error("Can't create invite link for object without group");
@@ -1,4 +1,3 @@
1
- import { type CoID, RawAccount, type RawCoValue, RawGroup } from "cojson";
2
1
  import { type Account } from "../coValues/account.js";
3
2
  import type {
4
3
  AnonymousJazzAgent,
@@ -7,153 +6,61 @@ import type {
7
6
  RefEncoded,
8
7
  UnCo,
9
8
  } from "../internal.js";
10
- import {
11
- instantiateRefEncoded,
12
- isRefEncoded,
13
- subscriptionsScopes,
14
- } from "../internal.js";
15
- import { coValuesCache } from "../lib/cache.js";
16
-
17
- const TRACE_ACCESSES = false;
9
+ import { isRefEncoded } from "../internal.js";
10
+ import { accessChildById, getSubscriptionScope } from "../subscribe/index.js";
18
11
 
19
12
  export class Ref<out V extends CoValue> {
20
13
  constructor(
21
14
  readonly id: ID<V>,
22
15
  readonly controlledAccount: Account | AnonymousJazzAgent,
23
16
  readonly schema: RefEncoded<V>,
17
+ readonly parent: CoValue,
24
18
  ) {
25
19
  if (!isRefEncoded(schema)) {
26
20
  throw new Error("Ref must be constructed with a ref schema");
27
21
  }
28
22
  }
29
23
 
30
- private getNode() {
31
- return "node" in this.controlledAccount
32
- ? this.controlledAccount.node
33
- : this.controlledAccount._raw.core.node;
34
- }
35
-
36
- hasReadAccess() {
37
- const node = this.getNode();
38
-
39
- const raw = node.getLoaded(this.id as unknown as CoID<RawCoValue>);
40
-
41
- if (!raw) {
42
- return true;
43
- }
44
-
45
- if (raw instanceof RawAccount || raw instanceof RawGroup) {
46
- return true;
47
- }
48
-
49
- const group = raw.core.getGroup();
24
+ async load(): Promise<V | null> {
25
+ const subscriptionScope = getSubscriptionScope(this.parent);
50
26
 
51
- if (group instanceof RawAccount) {
52
- if (node.account.id !== group.id) {
53
- return false;
54
- }
55
- } else if (group.myRole() === undefined) {
56
- return false;
57
- }
58
-
59
- return true;
60
- }
61
-
62
- getValueWithoutAccessCheck() {
63
- const node = this.getNode();
64
- const raw = node.getLoaded(this.id as unknown as CoID<RawCoValue>);
27
+ subscriptionScope.subscribeToId(this.id, this.schema);
65
28
 
66
- if (raw) {
67
- return coValuesCache.get(raw, () =>
68
- instantiateRefEncoded(this.schema, raw),
69
- );
70
- } else {
71
- return null;
72
- }
73
- }
29
+ const node = subscriptionScope.childNodes.get(this.id);
74
30
 
75
- get value() {
76
- if (!this.hasReadAccess()) {
31
+ if (!node) {
77
32
  return null;
78
33
  }
79
34
 
80
- return this.getValueWithoutAccessCheck();
81
- }
82
-
83
- private async loadHelper(): Promise<V | "unavailable"> {
84
- const node =
85
- "node" in this.controlledAccount
86
- ? this.controlledAccount.node
87
- : this.controlledAccount._raw.core.node;
88
- const raw = await node.load(this.id as unknown as CoID<RawCoValue>);
89
- if (raw === "unavailable") {
90
- return "unavailable";
91
- } else {
92
- return new Ref(this.id, this.controlledAccount, this.schema).value!;
93
- }
94
- }
95
-
96
- syncLoad(): V | undefined {
97
- const node =
98
- "node" in this.controlledAccount
99
- ? this.controlledAccount.node
100
- : this.controlledAccount._raw.core.node;
101
-
102
- const entry = node.coValuesStore.get(
103
- this.id as unknown as CoID<RawCoValue>,
104
- );
105
-
106
- if (entry.highLevelState === "available") {
107
- return new Ref(this.id, this.controlledAccount, this.schema).value!;
108
- }
109
-
110
- return undefined;
111
- }
35
+ const value = node.value;
112
36
 
113
- async load(): Promise<V | undefined> {
114
- const result = await this.loadHelper();
115
- if (result === "unavailable") {
116
- return undefined;
37
+ if (value?.type === "loaded") {
38
+ return value.value as V;
117
39
  } else {
118
- return result;
40
+ return new Promise((resolve) => {
41
+ const unsubscribe = node.subscribe((value) => {
42
+ if (value?.type === "loaded") {
43
+ unsubscribe();
44
+ resolve(value.value as V);
45
+ } else if (value?.type === "unavailable") {
46
+ unsubscribe();
47
+ resolve(null);
48
+ } else if (value?.type === "unauthorized") {
49
+ unsubscribe();
50
+ resolve(null);
51
+ }
52
+ });
53
+ });
119
54
  }
120
55
  }
121
56
 
122
- accessFrom(fromScopeValue: CoValue, key: string | number | symbol): V | null {
123
- const subScope = subscriptionsScopes.get(fromScopeValue);
124
-
125
- subScope?.onRefAccessedOrSet(fromScopeValue.id, this.id);
126
- TRACE_ACCESSES &&
127
- console.log(subScope?.scopeID, "accessing", fromScopeValue, key, this.id);
128
-
129
- if (this.value && subScope) {
130
- subscriptionsScopes.set(this.value, subScope);
131
- }
132
-
133
- if (subScope) {
134
- const cached = subScope.cachedValues[this.id];
135
- if (cached) {
136
- TRACE_ACCESSES && console.log("cached", cached);
137
- return cached as V;
138
- } else if (this.value !== null) {
139
- const freshValueInstance = instantiateRefEncoded(
140
- this.schema,
141
- this.value._raw,
142
- );
143
- TRACE_ACCESSES && console.log("freshValueInstance", freshValueInstance);
144
- subScope.cachedValues[this.id] = freshValueInstance;
145
- subscriptionsScopes.set(freshValueInstance, subScope);
146
- return freshValueInstance as V;
147
- } else {
148
- return null;
149
- }
150
- } else {
151
- return this.value;
152
- }
57
+ get value(): V | null | undefined {
58
+ return accessChildById(this.parent, this.id, this.schema);
153
59
  }
154
60
  }
155
61
 
156
62
  export function makeRefs<Keys extends string | number>(
63
+ parent: CoValue,
157
64
  getIdForKey: (key: Keys) => ID<CoValue> | undefined,
158
65
  getKeysWithIds: () => Keys[],
159
66
  controlledAccount: Account | AnonymousJazzAgent,
@@ -175,6 +82,7 @@ export function makeRefs<Keys extends string | number>(
175
82
  getIdForKey(key)!,
176
83
  controlledAccount,
177
84
  refSchemaForKey(key),
85
+ parent,
178
86
  );
179
87
  }
180
88
  };
@@ -189,6 +97,7 @@ export function makeRefs<Keys extends string | number>(
189
97
  id as ID<CoValue>,
190
98
  controlledAccount,
191
99
  refSchemaForKey(key as Keys),
100
+ parent,
192
101
  );
193
102
  },
194
103
  ownKeys() {
package/src/internal.ts CHANGED
@@ -7,8 +7,7 @@ export * from "./implementation/anonymousJazzAgent.js";
7
7
  export * from "./implementation/errors.js";
8
8
  export * from "./implementation/refs.js";
9
9
  export * from "./implementation/schema.js";
10
- export * from "./implementation/subscriptionScope.js";
11
-
10
+ export * from "./subscribe/index.js";
12
11
  export * from "./implementation/createContext.js";
13
12
 
14
13
  import "./implementation/devtoolsFormatters.js";
@@ -0,0 +1,71 @@
1
+ import { CoValueCore, LocalNode, RawCoMap, RawCoValue } from "cojson";
2
+
3
+ export class CoValueCoreSubscription {
4
+ _unsubscribe: () => void = () => {};
5
+ unsubscribed = false;
6
+
7
+ value: RawCoMap | undefined;
8
+
9
+ constructor(
10
+ public node: LocalNode,
11
+ public id: string,
12
+ public listener: (value: RawCoValue | "unavailable") => void,
13
+ ) {
14
+ const entry = this.node.getCoValue(this.id as any);
15
+
16
+ if (entry?.isAvailable()) {
17
+ this.subscribe(entry.getCurrentContent());
18
+ } else {
19
+ this.node.loadCoValueCore(this.id as any).then((value) => {
20
+ if (this.unsubscribed) return;
21
+
22
+ if (value.isAvailable()) {
23
+ this.subscribe(value.getCurrentContent());
24
+ } else {
25
+ this.listener("unavailable");
26
+ this.subscribeToState();
27
+ }
28
+ });
29
+ }
30
+ }
31
+
32
+ subscribeToState() {
33
+ const entry = this.node.getCoValue(this.id as any);
34
+ const handleStateChange = (
35
+ core: CoValueCore,
36
+ unsubFromStateChange: () => void,
37
+ ) => {
38
+ if (this.unsubscribed) {
39
+ unsubFromStateChange();
40
+ return;
41
+ }
42
+
43
+ if (core.isAvailable()) {
44
+ this.subscribe(core.getCurrentContent());
45
+ unsubFromStateChange();
46
+ }
47
+ };
48
+
49
+ const unsubFromStateChange = entry.subscribe(handleStateChange);
50
+
51
+ this._unsubscribe = () => {
52
+ unsubFromStateChange();
53
+ };
54
+ }
55
+
56
+ subscribe(value: RawCoValue) {
57
+ if (this.unsubscribed) return;
58
+
59
+ this._unsubscribe = value.subscribe((value) => {
60
+ this.listener(value);
61
+ });
62
+
63
+ this.listener(value);
64
+ }
65
+
66
+ unsubscribe() {
67
+ if (this.unsubscribed) return;
68
+ this.unsubscribed = true;
69
+ this._unsubscribe();
70
+ }
71
+ }
@@ -0,0 +1,48 @@
1
+ import type { CoValue, ID } from "../internal.js";
2
+
3
+ export class JazzError {
4
+ constructor(
5
+ public id: ID<CoValue> | undefined,
6
+ public type: "unavailable" | "unauthorized",
7
+ public issues: JazzErrorIssue[],
8
+ ) {}
9
+
10
+ toString() {
11
+ return this.issues
12
+ .map((issue) => {
13
+ let message = `${issue.message}`;
14
+
15
+ if (this.id) {
16
+ message += ` from ${this.id}`;
17
+ }
18
+
19
+ if (issue.path.length > 0) {
20
+ message += ` on path ${issue.path.join(".")}`;
21
+ }
22
+
23
+ return message;
24
+ })
25
+ .join("\n");
26
+ }
27
+
28
+ prependPath(item: string) {
29
+ if (this.issues.length === 0) {
30
+ return this;
31
+ }
32
+
33
+ const issues = this.issues.map((issue) => {
34
+ return {
35
+ ...issue,
36
+ path: [item].concat(issue.path),
37
+ };
38
+ });
39
+
40
+ return new JazzError(this.id, this.type, issues);
41
+ }
42
+ }
43
+ export type JazzErrorIssue = {
44
+ code: "unavailable" | "unauthorized" | "validationError";
45
+ message: string;
46
+ params: Record<string, any>;
47
+ path: string[];
48
+ };