@tinycloud/sdk-services 3.7.0-beta.2 → 3.7.0-beta.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.
package/dist/index.cjs CHANGED
@@ -5561,20 +5561,29 @@ var StorageFullMonitor = class {
5561
5561
  /**
5562
5562
  * @param ownerOf Owner key of a space id. Spaces with one key share one
5563
5563
  * storage budget. Defaults to the space id itself.
5564
+ * @param ownOwner Owner key of the signed-in account, or undefined when
5565
+ * there is none. Rejections on that owner's storage, or on a space that was
5566
+ * unknown, start the episode.
5564
5567
  */
5565
- constructor(ownerOf = (spaceId) => spaceId) {
5568
+ constructor(ownerOf = (spaceId) => spaceId, ownOwner = () => void 0) {
5566
5569
  this.ownerOf = ownerOf;
5567
- /** First rejection per owner key; {@link UNKNOWN_OWNER} when the space was unknown. */
5570
+ this.ownOwner = ownOwner;
5571
+ /**
5572
+ * First rejection per owner key; {@link UNKNOWN_OWNER} when the space was unknown.
5573
+ * A status read or a successful write removes the owner, so best-effort writes resume.
5574
+ */
5568
5575
  this.full = /* @__PURE__ */ new Map();
5569
5576
  this.handlers = /* @__PURE__ */ new Set();
5577
+ this.episodeListeners = /* @__PURE__ */ new Set();
5578
+ this.delegatedListeners = /* @__PURE__ */ new Set();
5570
5579
  }
5571
- /** True while any owner's storage is known to be full. */
5580
+ /** True while any owner's storage is known to be full for best-effort writes. */
5572
5581
  get isFull() {
5573
5582
  return this.full.size > 0;
5574
5583
  }
5575
- /** The rejection `storage.full` announced, until the episode ends. */
5584
+ /** The rejection that started the own-storage episode, until it ends. */
5576
5585
  get rejection() {
5577
- return this.event;
5586
+ return this.episode?.event;
5578
5587
  }
5579
5588
  /**
5580
5589
  * True when a write to `spaceId`'s owner was rejected, or a write whose
@@ -5583,13 +5592,31 @@ var StorageFullMonitor = class {
5583
5592
  isFullFor(spaceId) {
5584
5593
  return this.full.has(UNKNOWN_OWNER) || spaceId !== void 0 && this.full.has(this.ownerOf(spaceId));
5585
5594
  }
5586
- /** Watch a context's service errors. Returns the unsubscribe function. */
5595
+ /** Subscribe to the own-storage episode starting or ending. Returns the unsubscribe function. */
5596
+ onEpisode(listener) {
5597
+ this.episodeListeners.add(listener);
5598
+ return () => {
5599
+ this.episodeListeners.delete(listener);
5600
+ };
5601
+ }
5602
+ /**
5603
+ * Subscribe to a delegated owner's storage being found full: once per owner
5604
+ * until that owner is cleared. The own-storage episode does not fire it.
5605
+ * Returns the unsubscribe function.
5606
+ */
5607
+ onDelegated(listener) {
5608
+ this.delegatedListeners.add(listener);
5609
+ return () => {
5610
+ this.delegatedListeners.delete(listener);
5611
+ };
5612
+ }
5613
+ /** Watch a context's service errors and successful writes. Returns the unsubscribe function. */
5587
5614
  observe(context) {
5588
- return context.on(TelemetryEvents.SERVICE_ERROR, (data) => {
5615
+ const stopErrors = context.on(TelemetryEvents.SERVICE_ERROR, (data) => {
5589
5616
  const { service, error } = data;
5590
5617
  if (!error || !isStorageFullError(error)) return;
5591
5618
  const spaceId = context.session?.spaceId;
5592
- const owner = spaceId ? this.ownerOf(spaceId) : UNKNOWN_OWNER;
5619
+ const owner = this.ownerFor(spaceId);
5593
5620
  if (this.full.has(owner)) return;
5594
5621
  const meta = error.meta ?? {};
5595
5622
  const event = { service: service ?? error.service, error };
@@ -5600,8 +5627,13 @@ var StorageFullMonitor = class {
5600
5627
  }
5601
5628
  if (spaceId) event.spaceId = spaceId;
5602
5629
  this.full.set(owner, event);
5603
- if (this.event) return;
5604
- this.event = event;
5630
+ if (this.isOwn(owner)) {
5631
+ if (this.episode) return;
5632
+ this.episode = { owner, event };
5633
+ this.notifyEpisode();
5634
+ } else {
5635
+ this.notifyDelegated();
5636
+ }
5605
5637
  context.emit(TelemetryEvents.STORAGE_FULL, event);
5606
5638
  for (const handler of this.handlers) {
5607
5639
  try {
@@ -5611,6 +5643,19 @@ var StorageFullMonitor = class {
5611
5643
  }
5612
5644
  }
5613
5645
  });
5646
+ const stopWrites = context.on(TelemetryEvents.SERVICE_RESPONSE, (data) => {
5647
+ const { service, action, ok: ok2 } = data;
5648
+ if (ok2 !== true || !isCommittingWrite(service, action)) return;
5649
+ const owner = this.ownerFor(context.session?.spaceId);
5650
+ this.full.delete(owner);
5651
+ if (this.episode?.owner !== owner) return;
5652
+ this.episode = void 0;
5653
+ this.notifyEpisode();
5654
+ });
5655
+ return () => {
5656
+ stopErrors();
5657
+ stopWrites();
5658
+ };
5614
5659
  }
5615
5660
  /** Subscribe to the `storage.full` event. Returns the unsubscribe function. */
5616
5661
  on(handler) {
@@ -5619,22 +5664,56 @@ var StorageFullMonitor = class {
5619
5664
  this.handlers.delete(handler);
5620
5665
  };
5621
5666
  }
5622
- /** Forget that any owner's storage is full; the next rejection emits `storage.full` again. */
5667
+ /** Forget every owner's full state and end the episode; the next rejection starts a new one. */
5623
5668
  clear() {
5624
5669
  this.full.clear();
5625
- this.event = void 0;
5670
+ if (!this.episode) return;
5671
+ this.episode = void 0;
5672
+ this.notifyEpisode();
5626
5673
  }
5627
5674
  /**
5628
- * Forget that `spaceId`'s owner is full (`undefined`: the rejections whose
5629
- * space was unknown). Once no owner is full, the next rejection emits
5630
- * `storage.full` again.
5675
+ * Forget that `spaceId`'s owner is full for best-effort writes (`undefined`:
5676
+ * the rejections whose space was unknown). Does not end the episode.
5631
5677
  */
5632
5678
  clearFor(spaceId) {
5633
- this.full.delete(spaceId === void 0 ? UNKNOWN_OWNER : this.ownerOf(spaceId));
5634
- if (this.full.size === 0) this.event = void 0;
5679
+ this.full.delete(this.ownerFor(spaceId));
5680
+ }
5681
+ isOwn(owner) {
5682
+ return owner === UNKNOWN_OWNER || owner === this.ownOwner();
5683
+ }
5684
+ ownerFor(spaceId) {
5685
+ return spaceId ? this.ownerOf(spaceId) : UNKNOWN_OWNER;
5686
+ }
5687
+ notifyEpisode() {
5688
+ for (const listener of this.episodeListeners) {
5689
+ try {
5690
+ listener();
5691
+ } catch (listenerError) {
5692
+ console.error("Error in storage episode listener:", listenerError);
5693
+ }
5694
+ }
5695
+ }
5696
+ notifyDelegated() {
5697
+ for (const listener of this.delegatedListeners) {
5698
+ try {
5699
+ listener();
5700
+ } catch (listenerError) {
5701
+ console.error("Error in delegated storage listener:", listenerError);
5702
+ }
5703
+ }
5635
5704
  }
5636
5705
  };
5637
5706
  var UNKNOWN_OWNER = "\0unknown";
5707
+ var COMMITTING_ACTIONS = /* @__PURE__ */ new Map([
5708
+ ["kv", ["put", "batchPut"]],
5709
+ ["vault", ["put"]],
5710
+ ["sql", ["execute", "batch", "executeStatement", "migrations.apply"]],
5711
+ ["duckdb", ["execute", "batch", "executeStatement", "import"]]
5712
+ ]);
5713
+ function isCommittingWrite(service, action) {
5714
+ if (service === void 0 || action === void 0) return false;
5715
+ return COMMITTING_ACTIONS.get(service)?.includes(action) ?? false;
5716
+ }
5638
5717
 
5639
5718
  // src/storage/status.ts
5640
5719
  var STORAGE_MANAGE_URL = "https://account.tinycloud.xyz/storage";