jazz-tools 0.19.8 → 0.19.10

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 (80) hide show
  1. package/.turbo/turbo-build.log +44 -42
  2. package/CHANGELOG.md +19 -3
  3. package/dist/{chunk-2S3Z2CN6.js → chunk-FFEEPZEG.js} +367 -102
  4. package/dist/chunk-FFEEPZEG.js.map +1 -0
  5. package/dist/index.js +1 -1
  6. package/dist/react/hooks.d.ts +1 -1
  7. package/dist/react/hooks.d.ts.map +1 -1
  8. package/dist/react/index.d.ts +1 -1
  9. package/dist/react/index.d.ts.map +1 -1
  10. package/dist/react/index.js +5 -1
  11. package/dist/react/index.js.map +1 -1
  12. package/dist/react-core/hooks.d.ts +59 -0
  13. package/dist/react-core/hooks.d.ts.map +1 -1
  14. package/dist/react-core/index.js +124 -36
  15. package/dist/react-core/index.js.map +1 -1
  16. package/dist/react-core/tests/testUtils.d.ts +1 -0
  17. package/dist/react-core/tests/testUtils.d.ts.map +1 -1
  18. package/dist/react-core/tests/useSuspenseAccount.test.d.ts +2 -0
  19. package/dist/react-core/tests/useSuspenseAccount.test.d.ts.map +1 -0
  20. package/dist/react-core/tests/useSuspenseCoState.test.d.ts +2 -0
  21. package/dist/react-core/tests/useSuspenseCoState.test.d.ts.map +1 -0
  22. package/dist/react-core/use.d.ts +3 -0
  23. package/dist/react-core/use.d.ts.map +1 -0
  24. package/dist/react-native/index.js +5 -1
  25. package/dist/react-native/index.js.map +1 -1
  26. package/dist/react-native-core/crypto/RNCrypto.d.ts +2 -0
  27. package/dist/react-native-core/crypto/RNCrypto.d.ts.map +1 -0
  28. package/dist/react-native-core/crypto/RNCrypto.js +3 -0
  29. package/dist/react-native-core/crypto/RNCrypto.js.map +1 -0
  30. package/dist/react-native-core/hooks.d.ts +1 -1
  31. package/dist/react-native-core/hooks.d.ts.map +1 -1
  32. package/dist/react-native-core/index.js +5 -1
  33. package/dist/react-native-core/index.js.map +1 -1
  34. package/dist/react-native-core/platform.d.ts +2 -1
  35. package/dist/react-native-core/platform.d.ts.map +1 -1
  36. package/dist/testing.js +1 -1
  37. package/dist/testing.js.map +1 -1
  38. package/dist/tools/coValues/interfaces.d.ts +1 -1
  39. package/dist/tools/coValues/interfaces.d.ts.map +1 -1
  40. package/dist/tools/implementation/ContextManager.d.ts +3 -0
  41. package/dist/tools/implementation/ContextManager.d.ts.map +1 -1
  42. package/dist/tools/subscribe/CoValueCoreSubscription.d.ts +8 -22
  43. package/dist/tools/subscribe/CoValueCoreSubscription.d.ts.map +1 -1
  44. package/dist/tools/subscribe/SubscriptionCache.d.ts +51 -0
  45. package/dist/tools/subscribe/SubscriptionCache.d.ts.map +1 -0
  46. package/dist/tools/subscribe/SubscriptionScope.d.ts +17 -1
  47. package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
  48. package/dist/tools/subscribe/utils.d.ts +9 -1
  49. package/dist/tools/subscribe/utils.d.ts.map +1 -1
  50. package/dist/tools/testing.d.ts +2 -2
  51. package/dist/tools/testing.d.ts.map +1 -1
  52. package/dist/tools/tests/SubscriptionCache.test.d.ts +2 -0
  53. package/dist/tools/tests/SubscriptionCache.test.d.ts.map +1 -0
  54. package/package.json +13 -6
  55. package/src/react/hooks.tsx +2 -0
  56. package/src/react/index.ts +1 -14
  57. package/src/react-core/hooks.ts +167 -18
  58. package/src/react-core/tests/createCoValueSubscriptionContext.test.tsx +18 -8
  59. package/src/react-core/tests/testUtils.tsx +67 -5
  60. package/src/react-core/tests/useCoState.test.ts +3 -7
  61. package/src/react-core/tests/useSubscriptionSelector.test.ts +3 -7
  62. package/src/react-core/tests/useSuspenseAccount.test.tsx +343 -0
  63. package/src/react-core/tests/useSuspenseCoState.test.tsx +1182 -0
  64. package/src/react-core/use.ts +46 -0
  65. package/src/react-native-core/crypto/RNCrypto.ts +1 -0
  66. package/src/react-native-core/hooks.tsx +2 -0
  67. package/src/react-native-core/platform.ts +2 -1
  68. package/src/tools/coValues/interfaces.ts +2 -3
  69. package/src/tools/implementation/ContextManager.ts +13 -0
  70. package/src/tools/subscribe/CoValueCoreSubscription.ts +71 -100
  71. package/src/tools/subscribe/SubscriptionCache.ts +272 -0
  72. package/src/tools/subscribe/SubscriptionScope.ts +113 -7
  73. package/src/tools/subscribe/utils.ts +77 -0
  74. package/src/tools/testing.ts +0 -3
  75. package/src/tools/tests/CoValueCoreSubscription.test.ts +46 -12
  76. package/src/tools/tests/ContextManager.test.ts +85 -0
  77. package/src/tools/tests/SubscriptionCache.test.ts +237 -0
  78. package/src/tools/tests/coMap.test.ts +5 -7
  79. package/tsup.config.ts +1 -0
  80. package/dist/chunk-2S3Z2CN6.js.map +0 -1
@@ -3930,7 +3930,8 @@ function applyCoValueMigrations(instance) {
3930
3930
 
3931
3931
  // src/tools/subscribe/CoValueCoreSubscription.ts
3932
3932
  import {
3933
- cojsonInternals as cojsonInternals4
3933
+ cojsonInternals as cojsonInternals4,
3934
+ isRawCoID
3934
3935
  } from "cojson";
3935
3936
 
3936
3937
  // src/tools/subscribe/types.ts
@@ -3984,101 +3985,62 @@ var CoValueCoreSubscription = class {
3984
3985
  */
3985
3986
  initializeSubscription() {
3986
3987
  const source = this.source;
3987
- if (source.isAvailable()) {
3988
- this.handleAvailableSource();
3988
+ if (!isRawCoID(source.id)) {
3989
+ this.emit(CoValueLoadingState.UNAVAILABLE);
3989
3990
  return;
3990
3991
  }
3991
3992
  if (this.branchName) {
3992
- this.handleBranchCheckout();
3993
+ this.handleBranching(this.branchName, this.branchOwnerId);
3993
3994
  return;
3994
3995
  }
3995
- this.loadCoValue();
3996
+ this.subscribe(this.source);
3996
3997
  }
3997
- /**
3998
- * Handles the case where the CoValue source is immediately available.
3999
- * Either subscribes directly or attempts to get the requested branch.
4000
- */
4001
- handleAvailableSource() {
4002
- if (!this.branchName || !cojsonInternals4.canBeBranched(this.source)) {
4003
- this.subscribe(this.source.getCurrentContent());
3998
+ handleBranching(branchName, branchOwnerId) {
3999
+ const source = this.source;
4000
+ if (!source.isAvailable()) {
4001
+ this.waitForSourceToBecomeAvailable(branchName, branchOwnerId);
4004
4002
  return;
4005
4003
  }
4006
- const branch = this.source.getBranch(this.branchName, this.branchOwnerId);
4007
- if (branch.isAvailable()) {
4008
- this.subscribe(branch.getCurrentContent());
4004
+ if (!cojsonInternals4.canBeBranched(source)) {
4005
+ this.subscribe(source);
4009
4006
  return;
4010
- } else if (!this.source.hasBranch(this.branchName, this.branchOwnerId)) {
4011
- this.source.createBranch(this.branchName, this.branchOwnerId);
4012
- this.subscribe(branch.getCurrentContent());
4013
- } else {
4014
- this.handleBranchCheckout();
4015
4007
  }
4016
- }
4017
- /**
4018
- * Attempts to checkout a specific branch of the CoValue.
4019
- * This is called when the source isn't available but a branch is requested.
4020
- */
4021
- handleBranchCheckout() {
4022
- this.localNode.checkoutBranch(this.source.id, this.branchName, this.branchOwnerId).then((value) => {
4023
- if (this.unsubscribed) return;
4024
- if (value !== CoValueLoadingState.UNAVAILABLE) {
4025
- this.subscribe(value);
4026
- } else {
4027
- this.handleUnavailableBranch();
4008
+ const branch = source.getBranch(branchName, branchOwnerId);
4009
+ if (!branch.isAvailable() && !source.hasBranch(branchName, branchOwnerId)) {
4010
+ try {
4011
+ source.createBranch(branchName, branchOwnerId);
4012
+ } catch (error) {
4013
+ console.error("error creating branch", error);
4014
+ this.emit(CoValueLoadingState.UNAVAILABLE);
4015
+ return;
4028
4016
  }
4029
- }).catch((error) => {
4030
- console.error(error);
4031
- this.emit(CoValueLoadingState.UNAVAILABLE);
4032
- });
4033
- }
4034
- /**
4035
- * Handles the case where a branch checkout fails.
4036
- * Determines whether to retry or report unavailability.
4037
- */
4038
- handleUnavailableBranch() {
4039
- const source = this.source;
4040
- if (source.isAvailable()) {
4041
- throw new Error("Branch is unavailable");
4042
4017
  }
4043
- this.subscribeToUnavailableSource();
4044
- this.emit(CoValueLoadingState.UNAVAILABLE);
4018
+ this.subscribe(branch);
4045
4019
  }
4046
4020
  /**
4047
- * Loads the CoValue core from the network/storage.
4048
- * This is the fallback strategy when immediate availability fails.
4021
+ * Loads a CoValue core and emits an unavailable event if it is still unavailable after the retries.
4049
4022
  */
4050
- loadCoValue() {
4051
- this.localNode.loadCoValueCore(this.source.id, void 0, this.skipRetry).then((value) => {
4052
- if (this.unsubscribed) return;
4053
- if (value.isAvailable()) {
4054
- this.subscribe(value.getCurrentContent());
4055
- } else {
4056
- this.subscribeToUnavailableSource();
4023
+ load(value) {
4024
+ this.localNode.loadCoValueCore(value.id, void 0, this.skipRetry).then(() => {
4025
+ if (!value.isAvailable()) {
4057
4026
  this.emit(CoValueLoadingState.UNAVAILABLE);
4058
4027
  }
4059
- }).catch((error) => {
4060
- console.error(error);
4061
- this.emit(CoValueLoadingState.UNAVAILABLE);
4062
4028
  });
4063
4029
  }
4064
4030
  /**
4065
- * Subscribes to state changes of an unavailable CoValue source.
4066
- * This allows the subscription to become active when the source becomes available after a first loading attempt.
4031
+ * Waits for the source to become available and then tries to branch.
4067
4032
  */
4068
- subscribeToUnavailableSource() {
4033
+ waitForSourceToBecomeAvailable(branchName, branchOwnerId) {
4069
4034
  const source = this.source;
4070
4035
  const handleStateChange = (_, unsubFromStateChange) => {
4071
4036
  if (!source.isAvailable()) {
4072
4037
  return;
4073
4038
  }
4074
4039
  unsubFromStateChange();
4075
- if (this.branchName) {
4076
- this.handleBranchCheckout();
4077
- } else {
4078
- this.subscribe(source.getCurrentContent());
4079
- }
4040
+ this.handleBranching(branchName, branchOwnerId);
4080
4041
  };
4081
4042
  this._unsubscribe = source.subscribe(handleStateChange);
4043
+ this.load(source);
4082
4044
  }
4083
4045
  /**
4084
4046
  * Subscribes to a specific CoValue and notifies the listener.
@@ -4087,16 +4049,30 @@ var CoValueCoreSubscription = class {
4087
4049
  subscribe(value) {
4088
4050
  if (this.unsubscribed) return;
4089
4051
  this._unsubscribe = value.subscribe((value2) => {
4090
- this.emit(value2);
4052
+ if (value2.isAvailable()) {
4053
+ this.emit(value2.getCurrentContent());
4054
+ }
4091
4055
  });
4056
+ if (!value.isAvailable()) {
4057
+ this.load(value);
4058
+ }
4092
4059
  }
4093
4060
  emit(value) {
4094
4061
  if (this.unsubscribed) return;
4095
- if (!isReadyForEmit(value)) {
4062
+ if (!this.isReadyForEmit(value)) {
4096
4063
  return;
4097
4064
  }
4098
4065
  this.listener(value);
4099
4066
  }
4067
+ isReadyForEmit(value) {
4068
+ if (value === CoValueLoadingState.UNAVAILABLE) {
4069
+ return true;
4070
+ }
4071
+ if (!isCompletelyDownloaded(value)) {
4072
+ return false;
4073
+ }
4074
+ return true;
4075
+ }
4100
4076
  /**
4101
4077
  * Unsubscribes from all active subscriptions and marks the instance as unsubscribed.
4102
4078
  * This prevents any further operations and ensures proper cleanup.
@@ -4107,10 +4083,7 @@ var CoValueCoreSubscription = class {
4107
4083
  this._unsubscribe();
4108
4084
  }
4109
4085
  };
4110
- function isReadyForEmit(value) {
4111
- if (value === "unavailable") {
4112
- return true;
4113
- }
4086
+ function isCompletelyDownloaded(value) {
4114
4087
  return value.core.verified?.header.meta?.type === "binary" || value.core.isCompletelyDownloaded();
4115
4088
  }
4116
4089
 
@@ -4148,6 +4121,34 @@ var JazzError = class _JazzError {
4148
4121
  }
4149
4122
  };
4150
4123
 
4124
+ // src/tools/subscribe/errorReporting.ts
4125
+ var isDev = function() {
4126
+ try {
4127
+ return process.env.NODE_ENV === "development";
4128
+ } catch {
4129
+ return false;
4130
+ }
4131
+ }();
4132
+ var customErrorReporter;
4133
+ var captureErrorCause = isDev;
4134
+ function enableCaptureErrorCause(capture) {
4135
+ captureErrorCause = capture;
4136
+ }
4137
+ function setCustomErrorReporter(reporter) {
4138
+ customErrorReporter = reporter;
4139
+ }
4140
+ function isCustomErrorReportingEnabled() {
4141
+ return customErrorReporter !== void 0;
4142
+ }
4143
+ function captureStack() {
4144
+ return captureErrorCause ? new Error() : void 0;
4145
+ }
4146
+ function captureError(error, props) {
4147
+ if (customErrorReporter) {
4148
+ customErrorReporter(error, props);
4149
+ }
4150
+ }
4151
+
4151
4152
  // src/tools/subscribe/utils.ts
4152
4153
  import { RawAccount as RawAccount4 } from "cojson";
4153
4154
 
@@ -4188,33 +4189,47 @@ function createCoValue(ref2, raw, subscriptionScope) {
4188
4189
  id: subscriptionScope.id
4189
4190
  };
4190
4191
  }
4191
-
4192
- // src/tools/subscribe/errorReporting.ts
4193
- var isDev = function() {
4194
- try {
4195
- return process.env.NODE_ENV === "development";
4196
- } catch {
4197
- return false;
4198
- }
4199
- }();
4200
- var customErrorReporter;
4201
- var captureErrorCause = isDev;
4202
- function enableCaptureErrorCause(capture) {
4203
- captureErrorCause = capture;
4204
- }
4205
- function setCustomErrorReporter(reporter) {
4206
- customErrorReporter = reporter;
4207
- }
4208
- function isCustomErrorReportingEnabled() {
4209
- return customErrorReporter !== void 0;
4192
+ function resolvedPromise(value) {
4193
+ const promise = Promise.resolve(value);
4194
+ promise.status = "fulfilled";
4195
+ promise.value = value;
4196
+ return promise;
4210
4197
  }
4211
- function captureStack() {
4212
- return captureErrorCause ? new Error() : void 0;
4198
+ function rejectedPromise(reason) {
4199
+ const promise = Promise.reject(reason);
4200
+ promise.status = "rejected";
4201
+ promise.reason = reason;
4202
+ return promise;
4213
4203
  }
4214
- function captureError(error, props) {
4215
- if (customErrorReporter) {
4216
- customErrorReporter(error, props);
4204
+ function isEqualRefsToResolve(a, b) {
4205
+ if (a === b) {
4206
+ return true;
4207
+ }
4208
+ if (typeof a === "boolean" && typeof b === "boolean") {
4209
+ return a === b;
4210
+ }
4211
+ if (typeof a === "boolean" || typeof b === "boolean") {
4212
+ return false;
4213
+ }
4214
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
4215
+ return false;
4217
4216
  }
4217
+ const keysA = Object.keys(a);
4218
+ const keysB = Object.keys(b);
4219
+ if (keysA.length !== keysB.length) {
4220
+ return false;
4221
+ }
4222
+ for (const key of keysA) {
4223
+ if (!(key in b)) {
4224
+ return false;
4225
+ }
4226
+ const valueA = a[key];
4227
+ const valueB = b[key];
4228
+ if (!isEqualRefsToResolve(valueA, valueB)) {
4229
+ return false;
4230
+ }
4231
+ }
4232
+ return true;
4218
4233
  }
4219
4234
 
4220
4235
  // src/tools/subscribe/SubscriptionScope.ts
@@ -4273,6 +4288,7 @@ var SubscriptionScope = class _SubscriptionScope {
4273
4288
  this.triggerUpdate();
4274
4289
  };
4275
4290
  this.subscribers = /* @__PURE__ */ new Set();
4291
+ this.subscriberChangeCallbacks = /* @__PURE__ */ new Set();
4276
4292
  this.callerStack = callerStack;
4277
4293
  this.resolve = resolve;
4278
4294
  this.value = { type: CoValueLoadingState.LOADING, id };
@@ -4401,6 +4417,48 @@ var SubscriptionScope = class _SubscriptionScope {
4401
4417
  if (this.value.type !== CoValueLoadingState.LOADED) return true;
4402
4418
  return this.pendingLoadedChildren.size === 0;
4403
4419
  }
4420
+ cachePromise(value, callback) {
4421
+ if (this.lastPromise?.value === value) {
4422
+ return this.lastPromise.promise;
4423
+ }
4424
+ const promise = callback();
4425
+ this.lastPromise = { value, promise };
4426
+ return promise;
4427
+ }
4428
+ getPromise() {
4429
+ const currentValue = this.getCurrentValue();
4430
+ if (currentValue.$isLoaded) {
4431
+ return resolvedPromise(currentValue);
4432
+ }
4433
+ if (currentValue.$jazz.loadingState !== CoValueLoadingState.LOADING) {
4434
+ const error = this.getError();
4435
+ return rejectedPromise(
4436
+ new Error(error?.toString() ?? "Unknown error", {
4437
+ cause: this.callerStack
4438
+ })
4439
+ );
4440
+ }
4441
+ return this.cachePromise(currentValue, () => {
4442
+ return new Promise((resolve, reject) => {
4443
+ const unsubscribe = this.subscribe(() => {
4444
+ const currentValue2 = this.getCurrentValue();
4445
+ if (currentValue2.$jazz.loadingState === CoValueLoadingState.LOADING) {
4446
+ return;
4447
+ }
4448
+ if (currentValue2.$isLoaded) {
4449
+ resolve(currentValue2);
4450
+ } else {
4451
+ reject(
4452
+ new Error(this.getError()?.toString() ?? "Unknown error", {
4453
+ cause: this.callerStack
4454
+ })
4455
+ );
4456
+ }
4457
+ unsubscribe();
4458
+ });
4459
+ });
4460
+ });
4461
+ }
4404
4462
  getUnloadedValue(reason) {
4405
4463
  if (this.unloadedValue?.$jazz.loadingState === reason) {
4406
4464
  return this.unloadedValue;
@@ -4451,14 +4509,16 @@ var SubscriptionScope = class _SubscriptionScope {
4451
4509
  }
4452
4510
  return result;
4453
4511
  }
4454
- logError() {
4455
- let error;
4512
+ getError() {
4456
4513
  if (this.value.type === CoValueLoadingState.UNAUTHORIZED || this.value.type === CoValueLoadingState.UNAVAILABLE) {
4457
- error = this.value;
4514
+ return this.value;
4458
4515
  }
4459
4516
  if (this.errorFromChildren) {
4460
- error = this.errorFromChildren;
4517
+ return this.errorFromChildren;
4461
4518
  }
4519
+ }
4520
+ logError() {
4521
+ const error = this.getError();
4462
4522
  if (!error || this.lastErrorLogged === error) {
4463
4523
  return;
4464
4524
  }
@@ -4489,14 +4549,37 @@ var SubscriptionScope = class _SubscriptionScope {
4489
4549
  }
4490
4550
  this.dirty = false;
4491
4551
  }
4552
+ /**
4553
+ * Subscribe to subscriber count changes
4554
+ * Callback receives the total number of subscribers
4555
+ * Returns an unsubscribe function
4556
+ */
4557
+ onSubscriberChange(callback) {
4558
+ this.subscriberChangeCallbacks.add(callback);
4559
+ return () => {
4560
+ this.subscriberChangeCallbacks.delete(callback);
4561
+ };
4562
+ }
4563
+ notifySubscriberChange() {
4564
+ const count = this.subscribers.size;
4565
+ this.subscriberChangeCallbacks.forEach((callback) => {
4566
+ callback(count);
4567
+ });
4568
+ }
4492
4569
  subscribe(listener) {
4493
4570
  this.subscribers.add(listener);
4571
+ this.notifySubscriberChange();
4494
4572
  return () => {
4495
4573
  this.subscribers.delete(listener);
4574
+ this.notifySubscriberChange();
4496
4575
  };
4497
4576
  }
4498
4577
  setListener(listener) {
4578
+ const hadListener = this.subscribers.has(listener);
4499
4579
  this.subscribers.add(listener);
4580
+ if (!hadListener) {
4581
+ this.notifySubscriberChange();
4582
+ }
4500
4583
  this.triggerUpdate();
4501
4584
  }
4502
4585
  subscribeToKey(key) {
@@ -4765,7 +4848,12 @@ var SubscriptionScope = class _SubscriptionScope {
4765
4848
  destroy() {
4766
4849
  this.closed = true;
4767
4850
  this.subscription.unsubscribe();
4851
+ const hadSubscribers = this.subscribers.size > 0;
4768
4852
  this.subscribers.clear();
4853
+ if (hadSubscribers) {
4854
+ this.notifySubscriberChange();
4855
+ }
4856
+ this.subscriberChangeCallbacks.clear();
4769
4857
  this.childNodes.forEach((child) => child.destroy());
4770
4858
  }
4771
4859
  };
@@ -6381,6 +6469,177 @@ var InMemoryKVStore = class {
6381
6469
  }
6382
6470
  };
6383
6471
 
6472
+ // src/tools/subscribe/SubscriptionCache.ts
6473
+ var SubscriptionCache = class {
6474
+ constructor(cleanupTimeout = 5e3) {
6475
+ this.cache = /* @__PURE__ */ new Map();
6476
+ this.cleanupTimeout = cleanupTimeout;
6477
+ }
6478
+ /**
6479
+ * Get the inner set for a given id (read-only access)
6480
+ */
6481
+ getIdSet(id) {
6482
+ return this.cache.get(id);
6483
+ }
6484
+ /**
6485
+ * Get the inner set for a given id, creating it if it doesn't exist
6486
+ */
6487
+ getIdSetOrCreate(id) {
6488
+ let idSet = this.cache.get(id);
6489
+ if (!idSet) {
6490
+ idSet = /* @__PURE__ */ new Set();
6491
+ this.cache.set(id, idSet);
6492
+ }
6493
+ return idSet;
6494
+ }
6495
+ /**
6496
+ * Check if an entry matches the provided parameters
6497
+ */
6498
+ matchesEntry(entry, schema, resolve, branch) {
6499
+ if (entry.schema !== schema) {
6500
+ return false;
6501
+ }
6502
+ if (!isEqualRefsToResolve(entry.resolve, resolve)) {
6503
+ return false;
6504
+ }
6505
+ const branchName = branch?.name;
6506
+ if (entry.branch?.name !== branchName) {
6507
+ return false;
6508
+ }
6509
+ const branchOwnerId = branch?.owner?.$jazz.id;
6510
+ if (entry.branch?.owner?.$jazz.id !== branchOwnerId) {
6511
+ return false;
6512
+ }
6513
+ return true;
6514
+ }
6515
+ /**
6516
+ * Find a matching cache entry by comparing against entry properties
6517
+ * Uses id-based nesting to quickly filter candidates
6518
+ */
6519
+ findMatchingEntry(schema, id, resolve, branch) {
6520
+ const idSet = this.getIdSet(id);
6521
+ if (!idSet) {
6522
+ return void 0;
6523
+ }
6524
+ for (const entry of idSet) {
6525
+ if (this.matchesEntry(entry, schema, resolve, branch)) {
6526
+ return entry;
6527
+ }
6528
+ }
6529
+ return void 0;
6530
+ }
6531
+ /**
6532
+ * Handle subscriber count changes from SubscriptionScope
6533
+ */
6534
+ handleSubscriberChange(entry, count) {
6535
+ entry.subscriberCount = count;
6536
+ if (count === 0) {
6537
+ this.scheduleCleanup(entry);
6538
+ } else {
6539
+ this.cancelCleanup(entry);
6540
+ }
6541
+ }
6542
+ /**
6543
+ * Schedule cleanup timeout for an entry
6544
+ */
6545
+ scheduleCleanup(entry) {
6546
+ this.cancelCleanup(entry);
6547
+ entry.cleanupTimeoutId = setTimeout(() => {
6548
+ this.destroyEntry(entry);
6549
+ }, this.cleanupTimeout);
6550
+ }
6551
+ /**
6552
+ * Cancel pending cleanup timeout for an entry
6553
+ */
6554
+ cancelCleanup(entry) {
6555
+ if (entry.cleanupTimeoutId !== void 0) {
6556
+ clearTimeout(entry.cleanupTimeoutId);
6557
+ entry.cleanupTimeoutId = void 0;
6558
+ }
6559
+ }
6560
+ /**
6561
+ * Destroy a cache entry and its SubscriptionScope
6562
+ */
6563
+ destroyEntry(entry) {
6564
+ this.cancelCleanup(entry);
6565
+ entry.unsubscribeFromScope();
6566
+ try {
6567
+ entry.subscriptionScope.destroy();
6568
+ } catch (error) {
6569
+ console.error("Error destroying SubscriptionScope:", error);
6570
+ }
6571
+ const id = entry.subscriptionScope.id;
6572
+ const idSet = this.getIdSet(id);
6573
+ if (idSet) {
6574
+ idSet.delete(entry);
6575
+ if (idSet.size === 0) {
6576
+ this.cache.delete(id);
6577
+ }
6578
+ }
6579
+ }
6580
+ /**
6581
+ * Get or create a SubscriptionScope from the cache
6582
+ */
6583
+ getOrCreate(node, schema, id, resolve, skipRetry, bestEffortResolution, branch) {
6584
+ if (!id) {
6585
+ throw new Error("Cannot create subscription with undefined or null id");
6586
+ }
6587
+ const matchingEntry = this.findMatchingEntry(schema, id, resolve, branch);
6588
+ if (matchingEntry) {
6589
+ this.cancelCleanup(matchingEntry);
6590
+ return matchingEntry.subscriptionScope;
6591
+ }
6592
+ const refEncoded = {
6593
+ ref: coValueClassFromCoValueClassOrSchema(schema),
6594
+ optional: true
6595
+ };
6596
+ const subscriptionScope = new SubscriptionScope(
6597
+ node,
6598
+ // @ts-expect-error the SubscriptionScope is too generic for TS to infer its instances are CoValues
6599
+ resolve,
6600
+ id,
6601
+ refEncoded,
6602
+ skipRetry ?? false,
6603
+ bestEffortResolution ?? false,
6604
+ branch
6605
+ );
6606
+ const handleSubscriberChange = (count) => {
6607
+ const idSet2 = this.getIdSet(id);
6608
+ if (idSet2 && idSet2.has(entry)) {
6609
+ this.handleSubscriberChange(entry, count);
6610
+ }
6611
+ };
6612
+ const entry = {
6613
+ subscriptionScope,
6614
+ schema,
6615
+ resolve,
6616
+ branch,
6617
+ subscriberCount: subscriptionScope.subscribers.size,
6618
+ unsubscribeFromScope: subscriptionScope.onSubscriberChange(
6619
+ handleSubscriberChange
6620
+ )
6621
+ };
6622
+ const idSet = this.getIdSetOrCreate(id);
6623
+ idSet.add(entry);
6624
+ return subscriptionScope;
6625
+ }
6626
+ /**
6627
+ * Clear all cache entries and destroy all SubscriptionScope instances
6628
+ */
6629
+ clear() {
6630
+ const entriesToDestroy = [];
6631
+ for (const idSet of this.cache.values()) {
6632
+ for (const entry of idSet) {
6633
+ entriesToDestroy.push(entry);
6634
+ }
6635
+ }
6636
+ for (const entry of entriesToDestroy) {
6637
+ this.destroyEntry(entry);
6638
+ }
6639
+ this.cache.clear();
6640
+ }
6641
+ };
6642
+
6384
6643
  // src/tools/implementation/ContextManager.ts
6385
6644
  function getAnonymousFallback() {
6386
6645
  const context = createAnonymousJazzContext({
@@ -6414,6 +6673,7 @@ var JazzContextManager = class {
6414
6673
  return;
6415
6674
  }
6416
6675
  this.authenticatingAccountID = null;
6676
+ this.subscriptionCache.clear();
6417
6677
  await this.props.onLogOut?.();
6418
6678
  if (this.props.logOutReplacement) {
6419
6679
  await this.props.logOutReplacement();
@@ -6511,6 +6771,7 @@ var JazzContextManager = class {
6511
6771
  };
6512
6772
  KvStoreContext.getInstance().initialize(this.getKvStore());
6513
6773
  this.authSecretStorage = new AuthSecretStorage(opts?.authSecretStorageKey);
6774
+ this.subscriptionCache = new SubscriptionCache();
6514
6775
  if (opts?.useAnonymousFallback) {
6515
6776
  this.value = getAnonymousFallback();
6516
6777
  }
@@ -6539,6 +6800,7 @@ var JazzContextManager = class {
6539
6800
  throw new Error("Not implemented");
6540
6801
  }
6541
6802
  async updateContext(props, context, authProps) {
6803
+ this.subscriptionCache.clear();
6542
6804
  if (!this.keepContextOpen) {
6543
6805
  this.context?.done();
6544
6806
  }
@@ -6576,6 +6838,9 @@ var JazzContextManager = class {
6576
6838
  getAuthenticatingAccountID() {
6577
6839
  return this.authenticatingAccountID;
6578
6840
  }
6841
+ getSubscriptionScopeCache() {
6842
+ return this.subscriptionCache;
6843
+ }
6579
6844
  async handleAnonymousAccountMigration(prevContext) {
6580
6845
  if (!this.props) {
6581
6846
  throw new Error("Props required");
@@ -7129,4 +7394,4 @@ export {
7129
7394
  JazzContextManager
7130
7395
  };
7131
7396
  /* istanbul ignore file -- @preserve */
7132
- //# sourceMappingURL=chunk-2S3Z2CN6.js.map
7397
+ //# sourceMappingURL=chunk-FFEEPZEG.js.map