@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.d.cts CHANGED
@@ -10,8 +10,10 @@ export { IPrefixedKVService, KVService, PrefixedKVService } from './kv/index.cjs
10
10
  export { A as AuthorityRefusal, C as CLEAR_PENDING_WARNING, aq as DEFAULT_SIGNED_READ_URL_EXPIRY_MS, ar as KVAction, as as KVActionType, at as KVBatchPutItem, au as KVBatchPutOptions, av as KVBatchPutResponse, aw as KVBatchReadResponse, ax as KVChange, ay as KVChangesAuthority, az as KVChangesOptions, aA as KVChangesResponse, aB as KVCreateSignedReadUrlOptions, aC as KVDeleteOptions, aD as KVGetOptions, aE as KVHeadOptions, aF as KVListOptions, b as KVListPage, aG as KVListResponse, aH as KVPutOptions, c as KVReadThrough, d as KVReplicaHandle, e as KVReplicaSpec, f as KVReplicaStorage, a as KVReplicationController, K as KVReplicationDeps, aI as KVResponse, aJ as KVResponseHeaders, aK as KVServiceConfig, aL as KVSignedReadUrlResponse, L as LocalGetResult, g as LocalListResult, h as LocalReadMeta, i as LocalReplicaStatus, j as LocalSyncResult, k as PendingWriteRecord, l as PendingWriteState, P as PendingWriteStore, m as PinnedKey, w as PurgeTarget, x as ReplicaAccountGate, y as ReplicaAdmitHook, z as ReplicaAuthorityState, B as ReplicaBrowserGateAccess, D as ReplicaBrowserGateSnapshot, E as ReplicaBrowserGateTx, F as ReplicaBrowserPageAdmission, G as ReplicaClock, H as ReplicaCoverage, I as ReplicaDeliveryDecision, J as ReplicaDenyInput, M as ReplicaDenyOutcome, N as ReplicaDevice, O as ReplicaEraseReport, Q as ReplicaGateAccess, S as ReplicaGateReason, T as ReplicaGateScope, U as ReplicaGateSnapshot, V as ReplicaGateTx, W as ReplicaGatedError, X as ReplicaGatedHandle, Y as ReplicaGatedSpec, Z as ReplicaGatedStorage, _ as ReplicaGrantInfo, $ as ReplicaGuardedStore, a0 as ReplicaInspectedStatus, a1 as ReplicaLifecycleGate, a2 as ReplicaPageAdmission, a3 as ReplicaPartitionEraser, a4 as ReplicaPartitionIndexEntry, a5 as ReplicaPeerCommit, a6 as ReplicaPrivateGateHash, a7 as ReplicaPurgeIntent, a8 as ReplicaQuotaLedger, a9 as ReplicaQuotaSpec, aa as ReplicaSessionStamp, ab as ReplicaStampedReply, ac as ReplicaState, ad as ReplicaStatusEntry, ae as ReplicationAuthority, af as ReplicationControl, ag as ReplicationEvent, R as ReplicationIdentity, ah as ReplicationOptions, ai as ReplicationPurgeReport, aj as ReplicationReason, ak as ReplicationScheduler, al as ResolvedReplicationOptions, am as SaltedHash, an as canonicalReplicationIdentity, ao as replicationIdentityKey, ap as requiresSecretsOptIn } from './scope-44m8GKdi.cjs';
11
11
  export { ClampConsentAnchor, ConsentDeadlineMs, EffectiveGrantExpiry, EffectiveGrantExpiryInput, MintRemainingMs, NeedsSilentRenewal, ReplicaConsentAnchor, ReplicaConsentSource, ReplicaGrantPolicy } from './kv/replication/grantPolicyTypes.cjs';
12
12
  export { BatchOptions, BatchResponse, DatabaseHandle, ExecuteOptions, ExecuteResponse, IDatabaseHandle, ISQLMigrations, ISQLService, QueryOptions, QueryResponse, SQLAction, SQLActionType, SQLMigrations, SQLService, SQLServiceConfig, SqlMigration, SqlMigrationApplyOptions, SqlMigrationApplyResponse, SqlStatement, SqlValue } from './sql/index.cjs';
13
- import { IEncryptionService, DecryptCapabilityProof } from './encryption/index.cjs';
14
- export { BuildCanonicalDecryptRequestInput, BuildDecryptFactsInput, BuildDecryptInvocationInput, BuiltDecryptInvocation, CanonicalDecryptRequest, CanonicalJson, DECRYPT_ACTION, DECRYPT_FACT_TYPE, DECRYPT_RESULT_TYPE, DEFAULT_ENCRYPTION_ALG, DEFAULT_KEY_VERSION, DecryptEnvelopeOptions, DecryptInvocationFact, DecryptInvocationSigner, DecryptRequestBody, DecryptResponseBody, DecryptTransport, DiscoverNetworkInput, DiscoveredNetwork, DiscoverySource, ENCRYPTION_NETWORK_URN_PREFIX, ENCRYPTION_SERVICE, ENCRYPTION_SERVICE_SHORT, ENVELOPE_VERSION, EncryptToNetworkInput, EncryptToNetworkOptions, EncryptToNetworkResult, EncryptionCrypto, EncryptionError, EncryptionErrorInput, EncryptionService, EncryptionServiceConfig, InlineEncryptedEnvelope, Json, NETWORK_NAME_PATTERN, NetworkDescriptor, NetworkIdError, NodeDescriptorFetcher, ParsedNetworkId, RandomReceiverKeyInput, ReceiverKeyPair, ReceiverKeySigner, SignedReceiverKeyInput, VerifyDecryptResponseInput, WellKnownDescriptorFetcher, base64Decode, base64Encode, buildCanonicalDecryptRequest, buildDecryptAttenuation, buildDecryptFacts, buildDecryptInvocation, buildNetworkId, canonicalHashHex, canonicalSignedResponse, canonicalize as canonicalizeEncryptionJson, checkDecryptInvocationInput, decryptEnvelopeWithKey, deriveSignedReceiverKey, discoverNetwork, encryptToNetwork, encryptionError, ensureNetworkUsableForDecrypt, generateRandomReceiverKey, hexDecode, hexEncode, isNetworkId, networkDiscoveryKey, openWrappedKey, parseNetworkId, utf8Decode, utf8Encode, validateEnvelope, verifyDecryptResponse } from './encryption/index.cjs';
13
+ import { D as DecryptCapabilityProof } from './types-B_rbS4XE.cjs';
14
+ export { B as BuildDecryptInvocationInput, a as BuiltDecryptInvocation, C as CanonicalJson, b as DECRYPT_ACTION, c as DECRYPT_FACT_TYPE, d as DECRYPT_RESULT_TYPE, e as DEFAULT_ENCRYPTION_ALG, f as DEFAULT_KEY_VERSION, g as DecryptInvocationFact, h as DecryptInvocationSigner, i as DecryptRequestBody, j as DecryptResponseBody, k as ENCRYPTION_SERVICE, l as ENCRYPTION_SERVICE_SHORT, m as ENVELOPE_VERSION, E as EncryptionCrypto, n as EncryptionError, o as EncryptionErrorInput, I as InlineEncryptedEnvelope, J as Json, N as NetworkDescriptor, R as ReceiverKeyPair, p as ReceiverKeySigner, q as base64Decode, r as base64Encode, s as canonicalHashHex, t as canonicalizeEncryptionJson, u as encryptionError, v as hexDecode, w as hexEncode, x as utf8Decode, y as utf8Encode } from './types-B_rbS4XE.cjs';
15
+ import { IEncryptionService } from './encryption/index.cjs';
16
+ export { BuildCanonicalDecryptRequestInput, BuildDecryptFactsInput, CanonicalDecryptRequest, DecryptEnvelopeOptions, DecryptTransport, DiscoverNetworkInput, DiscoveredNetwork, DiscoverySource, ENCRYPTION_NETWORK_URN_PREFIX, EncryptToNetworkInput, EncryptToNetworkOptions, EncryptToNetworkResult, EncryptionService, EncryptionServiceConfig, NETWORK_NAME_PATTERN, NetworkIdError, NodeDescriptorFetcher, ParsedNetworkId, RandomReceiverKeyInput, SignedReceiverKeyInput, VerifyDecryptResponseInput, WellKnownDescriptorFetcher, buildCanonicalDecryptRequest, buildDecryptAttenuation, buildDecryptFacts, buildDecryptInvocation, buildNetworkId, canonicalSignedResponse, checkDecryptInvocationInput, decryptEnvelopeWithKey, deriveSignedReceiverKey, discoverNetwork, encryptToNetwork, ensureNetworkUsableForDecrypt, generateRandomReceiverKey, isNetworkId, networkDiscoveryKey, openWrappedKey, parseNetworkId, validateEnvelope, verifyDecryptResponse } from './encryption/index.cjs';
15
17
  export { DecryptTransportResponseError } from '@tinycloud/sdk-services/internal/decrypt-transport-response-error';
16
18
  export { kvPrefixCovers } from './kv/replication/prefix.cjs';
17
19
  import './kv/replication/contractConstants.cjs';
@@ -1574,50 +1576,76 @@ interface StorageFullEvent {
1574
1576
  type StorageFullHandler = (event: StorageFullEvent) => void;
1575
1577
  /**
1576
1578
  * Turns the storage rejections seen by any number of service contexts into
1577
- * one `storage.full` event, and remembers whose storage is full so read paths
1579
+ * `storage.full` events, and remembers whose storage is full so read paths
1578
1580
  * can skip their best-effort writes.
1579
1581
  *
1580
1582
  * Storage is one budget per owner, so fullness is recorded per owner, apart
1581
1583
  * from the event: a rejection on one owner's space never hides a later
1582
- * rejection on another's. The event fires once per episode, which lasts until
1583
- * no owner is known to be full.
1584
+ * rejection on another's.
1585
+ *
1586
+ * The signed-in account's own storage has an episode. Its first rejection
1587
+ * starts the episode and emits `storage.full`; later rejections do not emit
1588
+ * again. The episode ends only when a write to that owner's storage succeeds,
1589
+ * or on {@link clear}. A status read does not end it. A rejection on another
1590
+ * owner's delegated space emits `storage.full` once per owner until a status
1591
+ * read or a successful write clears that owner, and starts no episode.
1584
1592
  *
1585
1593
  * The event goes through the rejecting context's `emit` (so telemetry and the
1586
1594
  * debug log see it) and then to the handlers registered with {@link on}.
1587
1595
  */
1588
1596
  declare class StorageFullMonitor {
1589
1597
  private readonly ownerOf;
1590
- /** The emitted event, until the episode ends. */
1591
- private event;
1592
- /** First rejection per owner key; {@link UNKNOWN_OWNER} when the space was unknown. */
1598
+ private readonly ownOwner;
1599
+ /** The own-storage episode, until a successful write or {@link clear} ends it. */
1600
+ private episode;
1601
+ /**
1602
+ * First rejection per owner key; {@link UNKNOWN_OWNER} when the space was unknown.
1603
+ * A status read or a successful write removes the owner, so best-effort writes resume.
1604
+ */
1593
1605
  private readonly full;
1594
1606
  private readonly handlers;
1607
+ private readonly episodeListeners;
1608
+ private readonly delegatedListeners;
1595
1609
  /**
1596
1610
  * @param ownerOf Owner key of a space id. Spaces with one key share one
1597
1611
  * storage budget. Defaults to the space id itself.
1612
+ * @param ownOwner Owner key of the signed-in account, or undefined when
1613
+ * there is none. Rejections on that owner's storage, or on a space that was
1614
+ * unknown, start the episode.
1598
1615
  */
1599
- constructor(ownerOf?: (spaceId: string) => string);
1600
- /** True while any owner's storage is known to be full. */
1616
+ constructor(ownerOf?: (spaceId: string) => string, ownOwner?: () => string | undefined);
1617
+ /** True while any owner's storage is known to be full for best-effort writes. */
1601
1618
  get isFull(): boolean;
1602
- /** The rejection `storage.full` announced, until the episode ends. */
1619
+ /** The rejection that started the own-storage episode, until it ends. */
1603
1620
  get rejection(): StorageFullEvent | undefined;
1604
1621
  /**
1605
1622
  * True when a write to `spaceId`'s owner was rejected, or a write whose
1606
1623
  * space was unknown was: that one could have been any owner's.
1607
1624
  */
1608
1625
  isFullFor(spaceId: string | undefined): boolean;
1609
- /** Watch a context's service errors. Returns the unsubscribe function. */
1626
+ /** Subscribe to the own-storage episode starting or ending. Returns the unsubscribe function. */
1627
+ onEpisode(listener: () => void): () => void;
1628
+ /**
1629
+ * Subscribe to a delegated owner's storage being found full: once per owner
1630
+ * until that owner is cleared. The own-storage episode does not fire it.
1631
+ * Returns the unsubscribe function.
1632
+ */
1633
+ onDelegated(listener: () => void): () => void;
1634
+ /** Watch a context's service errors and successful writes. Returns the unsubscribe function. */
1610
1635
  observe(context: IServiceContext): () => void;
1611
1636
  /** Subscribe to the `storage.full` event. Returns the unsubscribe function. */
1612
1637
  on(handler: StorageFullHandler): () => void;
1613
- /** Forget that any owner's storage is full; the next rejection emits `storage.full` again. */
1638
+ /** Forget every owner's full state and end the episode; the next rejection starts a new one. */
1614
1639
  clear(): void;
1615
1640
  /**
1616
- * Forget that `spaceId`'s owner is full (`undefined`: the rejections whose
1617
- * space was unknown). Once no owner is full, the next rejection emits
1618
- * `storage.full` again.
1641
+ * Forget that `spaceId`'s owner is full for best-effort writes (`undefined`:
1642
+ * the rejections whose space was unknown). Does not end the episode.
1619
1643
  */
1620
1644
  clearFor(spaceId: string | undefined): void;
1645
+ private isOwn;
1646
+ private ownerFor;
1647
+ private notifyEpisode;
1648
+ private notifyDelegated;
1621
1649
  }
1622
1650
 
1623
1651
  /** Where the owner frees up space or upgrades their plan. */
package/dist/index.d.ts CHANGED
@@ -10,8 +10,10 @@ export { IPrefixedKVService, KVService, PrefixedKVService } from './kv/index.js'
10
10
  export { A as AuthorityRefusal, C as CLEAR_PENDING_WARNING, aq as DEFAULT_SIGNED_READ_URL_EXPIRY_MS, ar as KVAction, as as KVActionType, at as KVBatchPutItem, au as KVBatchPutOptions, av as KVBatchPutResponse, aw as KVBatchReadResponse, ax as KVChange, ay as KVChangesAuthority, az as KVChangesOptions, aA as KVChangesResponse, aB as KVCreateSignedReadUrlOptions, aC as KVDeleteOptions, aD as KVGetOptions, aE as KVHeadOptions, aF as KVListOptions, b as KVListPage, aG as KVListResponse, aH as KVPutOptions, c as KVReadThrough, d as KVReplicaHandle, e as KVReplicaSpec, f as KVReplicaStorage, a as KVReplicationController, K as KVReplicationDeps, aI as KVResponse, aJ as KVResponseHeaders, aK as KVServiceConfig, aL as KVSignedReadUrlResponse, L as LocalGetResult, g as LocalListResult, h as LocalReadMeta, i as LocalReplicaStatus, j as LocalSyncResult, k as PendingWriteRecord, l as PendingWriteState, P as PendingWriteStore, m as PinnedKey, w as PurgeTarget, x as ReplicaAccountGate, y as ReplicaAdmitHook, z as ReplicaAuthorityState, B as ReplicaBrowserGateAccess, D as ReplicaBrowserGateSnapshot, E as ReplicaBrowserGateTx, F as ReplicaBrowserPageAdmission, G as ReplicaClock, H as ReplicaCoverage, I as ReplicaDeliveryDecision, J as ReplicaDenyInput, M as ReplicaDenyOutcome, N as ReplicaDevice, O as ReplicaEraseReport, Q as ReplicaGateAccess, S as ReplicaGateReason, T as ReplicaGateScope, U as ReplicaGateSnapshot, V as ReplicaGateTx, W as ReplicaGatedError, X as ReplicaGatedHandle, Y as ReplicaGatedSpec, Z as ReplicaGatedStorage, _ as ReplicaGrantInfo, $ as ReplicaGuardedStore, a0 as ReplicaInspectedStatus, a1 as ReplicaLifecycleGate, a2 as ReplicaPageAdmission, a3 as ReplicaPartitionEraser, a4 as ReplicaPartitionIndexEntry, a5 as ReplicaPeerCommit, a6 as ReplicaPrivateGateHash, a7 as ReplicaPurgeIntent, a8 as ReplicaQuotaLedger, a9 as ReplicaQuotaSpec, aa as ReplicaSessionStamp, ab as ReplicaStampedReply, ac as ReplicaState, ad as ReplicaStatusEntry, ae as ReplicationAuthority, af as ReplicationControl, ag as ReplicationEvent, R as ReplicationIdentity, ah as ReplicationOptions, ai as ReplicationPurgeReport, aj as ReplicationReason, ak as ReplicationScheduler, al as ResolvedReplicationOptions, am as SaltedHash, an as canonicalReplicationIdentity, ao as replicationIdentityKey, ap as requiresSecretsOptIn } from './scope-CAJbG-yz.js';
11
11
  export { ClampConsentAnchor, ConsentDeadlineMs, EffectiveGrantExpiry, EffectiveGrantExpiryInput, MintRemainingMs, NeedsSilentRenewal, ReplicaConsentAnchor, ReplicaConsentSource, ReplicaGrantPolicy } from './kv/replication/grantPolicyTypes.js';
12
12
  export { BatchOptions, BatchResponse, DatabaseHandle, ExecuteOptions, ExecuteResponse, IDatabaseHandle, ISQLMigrations, ISQLService, QueryOptions, QueryResponse, SQLAction, SQLActionType, SQLMigrations, SQLService, SQLServiceConfig, SqlMigration, SqlMigrationApplyOptions, SqlMigrationApplyResponse, SqlStatement, SqlValue } from './sql/index.js';
13
- import { IEncryptionService, DecryptCapabilityProof } from './encryption/index.js';
14
- export { BuildCanonicalDecryptRequestInput, BuildDecryptFactsInput, BuildDecryptInvocationInput, BuiltDecryptInvocation, CanonicalDecryptRequest, CanonicalJson, DECRYPT_ACTION, DECRYPT_FACT_TYPE, DECRYPT_RESULT_TYPE, DEFAULT_ENCRYPTION_ALG, DEFAULT_KEY_VERSION, DecryptEnvelopeOptions, DecryptInvocationFact, DecryptInvocationSigner, DecryptRequestBody, DecryptResponseBody, DecryptTransport, DiscoverNetworkInput, DiscoveredNetwork, DiscoverySource, ENCRYPTION_NETWORK_URN_PREFIX, ENCRYPTION_SERVICE, ENCRYPTION_SERVICE_SHORT, ENVELOPE_VERSION, EncryptToNetworkInput, EncryptToNetworkOptions, EncryptToNetworkResult, EncryptionCrypto, EncryptionError, EncryptionErrorInput, EncryptionService, EncryptionServiceConfig, InlineEncryptedEnvelope, Json, NETWORK_NAME_PATTERN, NetworkDescriptor, NetworkIdError, NodeDescriptorFetcher, ParsedNetworkId, RandomReceiverKeyInput, ReceiverKeyPair, ReceiverKeySigner, SignedReceiverKeyInput, VerifyDecryptResponseInput, WellKnownDescriptorFetcher, base64Decode, base64Encode, buildCanonicalDecryptRequest, buildDecryptAttenuation, buildDecryptFacts, buildDecryptInvocation, buildNetworkId, canonicalHashHex, canonicalSignedResponse, canonicalize as canonicalizeEncryptionJson, checkDecryptInvocationInput, decryptEnvelopeWithKey, deriveSignedReceiverKey, discoverNetwork, encryptToNetwork, encryptionError, ensureNetworkUsableForDecrypt, generateRandomReceiverKey, hexDecode, hexEncode, isNetworkId, networkDiscoveryKey, openWrappedKey, parseNetworkId, utf8Decode, utf8Encode, validateEnvelope, verifyDecryptResponse } from './encryption/index.js';
13
+ import { D as DecryptCapabilityProof } from './types-BGRzS1ob.js';
14
+ export { B as BuildDecryptInvocationInput, a as BuiltDecryptInvocation, C as CanonicalJson, b as DECRYPT_ACTION, c as DECRYPT_FACT_TYPE, d as DECRYPT_RESULT_TYPE, e as DEFAULT_ENCRYPTION_ALG, f as DEFAULT_KEY_VERSION, g as DecryptInvocationFact, h as DecryptInvocationSigner, i as DecryptRequestBody, j as DecryptResponseBody, k as ENCRYPTION_SERVICE, l as ENCRYPTION_SERVICE_SHORT, m as ENVELOPE_VERSION, E as EncryptionCrypto, n as EncryptionError, o as EncryptionErrorInput, I as InlineEncryptedEnvelope, J as Json, N as NetworkDescriptor, R as ReceiverKeyPair, p as ReceiverKeySigner, q as base64Decode, r as base64Encode, s as canonicalHashHex, t as canonicalizeEncryptionJson, u as encryptionError, v as hexDecode, w as hexEncode, x as utf8Decode, y as utf8Encode } from './types-BGRzS1ob.js';
15
+ import { IEncryptionService } from './encryption/index.js';
16
+ export { BuildCanonicalDecryptRequestInput, BuildDecryptFactsInput, CanonicalDecryptRequest, DecryptEnvelopeOptions, DecryptTransport, DiscoverNetworkInput, DiscoveredNetwork, DiscoverySource, ENCRYPTION_NETWORK_URN_PREFIX, EncryptToNetworkInput, EncryptToNetworkOptions, EncryptToNetworkResult, EncryptionService, EncryptionServiceConfig, NETWORK_NAME_PATTERN, NetworkIdError, NodeDescriptorFetcher, ParsedNetworkId, RandomReceiverKeyInput, SignedReceiverKeyInput, VerifyDecryptResponseInput, WellKnownDescriptorFetcher, buildCanonicalDecryptRequest, buildDecryptAttenuation, buildDecryptFacts, buildDecryptInvocation, buildNetworkId, canonicalSignedResponse, checkDecryptInvocationInput, decryptEnvelopeWithKey, deriveSignedReceiverKey, discoverNetwork, encryptToNetwork, ensureNetworkUsableForDecrypt, generateRandomReceiverKey, isNetworkId, networkDiscoveryKey, openWrappedKey, parseNetworkId, validateEnvelope, verifyDecryptResponse } from './encryption/index.js';
15
17
  export { DecryptTransportResponseError } from '@tinycloud/sdk-services/internal/decrypt-transport-response-error';
16
18
  export { kvPrefixCovers } from './kv/replication/prefix.js';
17
19
  import './kv/replication/contractConstants.js';
@@ -1574,50 +1576,76 @@ interface StorageFullEvent {
1574
1576
  type StorageFullHandler = (event: StorageFullEvent) => void;
1575
1577
  /**
1576
1578
  * Turns the storage rejections seen by any number of service contexts into
1577
- * one `storage.full` event, and remembers whose storage is full so read paths
1579
+ * `storage.full` events, and remembers whose storage is full so read paths
1578
1580
  * can skip their best-effort writes.
1579
1581
  *
1580
1582
  * Storage is one budget per owner, so fullness is recorded per owner, apart
1581
1583
  * from the event: a rejection on one owner's space never hides a later
1582
- * rejection on another's. The event fires once per episode, which lasts until
1583
- * no owner is known to be full.
1584
+ * rejection on another's.
1585
+ *
1586
+ * The signed-in account's own storage has an episode. Its first rejection
1587
+ * starts the episode and emits `storage.full`; later rejections do not emit
1588
+ * again. The episode ends only when a write to that owner's storage succeeds,
1589
+ * or on {@link clear}. A status read does not end it. A rejection on another
1590
+ * owner's delegated space emits `storage.full` once per owner until a status
1591
+ * read or a successful write clears that owner, and starts no episode.
1584
1592
  *
1585
1593
  * The event goes through the rejecting context's `emit` (so telemetry and the
1586
1594
  * debug log see it) and then to the handlers registered with {@link on}.
1587
1595
  */
1588
1596
  declare class StorageFullMonitor {
1589
1597
  private readonly ownerOf;
1590
- /** The emitted event, until the episode ends. */
1591
- private event;
1592
- /** First rejection per owner key; {@link UNKNOWN_OWNER} when the space was unknown. */
1598
+ private readonly ownOwner;
1599
+ /** The own-storage episode, until a successful write or {@link clear} ends it. */
1600
+ private episode;
1601
+ /**
1602
+ * First rejection per owner key; {@link UNKNOWN_OWNER} when the space was unknown.
1603
+ * A status read or a successful write removes the owner, so best-effort writes resume.
1604
+ */
1593
1605
  private readonly full;
1594
1606
  private readonly handlers;
1607
+ private readonly episodeListeners;
1608
+ private readonly delegatedListeners;
1595
1609
  /**
1596
1610
  * @param ownerOf Owner key of a space id. Spaces with one key share one
1597
1611
  * storage budget. Defaults to the space id itself.
1612
+ * @param ownOwner Owner key of the signed-in account, or undefined when
1613
+ * there is none. Rejections on that owner's storage, or on a space that was
1614
+ * unknown, start the episode.
1598
1615
  */
1599
- constructor(ownerOf?: (spaceId: string) => string);
1600
- /** True while any owner's storage is known to be full. */
1616
+ constructor(ownerOf?: (spaceId: string) => string, ownOwner?: () => string | undefined);
1617
+ /** True while any owner's storage is known to be full for best-effort writes. */
1601
1618
  get isFull(): boolean;
1602
- /** The rejection `storage.full` announced, until the episode ends. */
1619
+ /** The rejection that started the own-storage episode, until it ends. */
1603
1620
  get rejection(): StorageFullEvent | undefined;
1604
1621
  /**
1605
1622
  * True when a write to `spaceId`'s owner was rejected, or a write whose
1606
1623
  * space was unknown was: that one could have been any owner's.
1607
1624
  */
1608
1625
  isFullFor(spaceId: string | undefined): boolean;
1609
- /** Watch a context's service errors. Returns the unsubscribe function. */
1626
+ /** Subscribe to the own-storage episode starting or ending. Returns the unsubscribe function. */
1627
+ onEpisode(listener: () => void): () => void;
1628
+ /**
1629
+ * Subscribe to a delegated owner's storage being found full: once per owner
1630
+ * until that owner is cleared. The own-storage episode does not fire it.
1631
+ * Returns the unsubscribe function.
1632
+ */
1633
+ onDelegated(listener: () => void): () => void;
1634
+ /** Watch a context's service errors and successful writes. Returns the unsubscribe function. */
1610
1635
  observe(context: IServiceContext): () => void;
1611
1636
  /** Subscribe to the `storage.full` event. Returns the unsubscribe function. */
1612
1637
  on(handler: StorageFullHandler): () => void;
1613
- /** Forget that any owner's storage is full; the next rejection emits `storage.full` again. */
1638
+ /** Forget every owner's full state and end the episode; the next rejection starts a new one. */
1614
1639
  clear(): void;
1615
1640
  /**
1616
- * Forget that `spaceId`'s owner is full (`undefined`: the rejections whose
1617
- * space was unknown). Once no owner is full, the next rejection emits
1618
- * `storage.full` again.
1641
+ * Forget that `spaceId`'s owner is full for best-effort writes (`undefined`:
1642
+ * the rejections whose space was unknown). Does not end the episode.
1619
1643
  */
1620
1644
  clearFor(spaceId: string | undefined): void;
1645
+ private isOwn;
1646
+ private ownerFor;
1647
+ private notifyEpisode;
1648
+ private notifyDelegated;
1621
1649
  }
1622
1650
 
1623
1651
  /** Where the owner frees up space or upgrades their plan. */
package/dist/index.js CHANGED
@@ -5395,20 +5395,29 @@ var StorageFullMonitor = class {
5395
5395
  /**
5396
5396
  * @param ownerOf Owner key of a space id. Spaces with one key share one
5397
5397
  * storage budget. Defaults to the space id itself.
5398
+ * @param ownOwner Owner key of the signed-in account, or undefined when
5399
+ * there is none. Rejections on that owner's storage, or on a space that was
5400
+ * unknown, start the episode.
5398
5401
  */
5399
- constructor(ownerOf = (spaceId) => spaceId) {
5402
+ constructor(ownerOf = (spaceId) => spaceId, ownOwner = () => void 0) {
5400
5403
  this.ownerOf = ownerOf;
5401
- /** First rejection per owner key; {@link UNKNOWN_OWNER} when the space was unknown. */
5404
+ this.ownOwner = ownOwner;
5405
+ /**
5406
+ * First rejection per owner key; {@link UNKNOWN_OWNER} when the space was unknown.
5407
+ * A status read or a successful write removes the owner, so best-effort writes resume.
5408
+ */
5402
5409
  this.full = /* @__PURE__ */ new Map();
5403
5410
  this.handlers = /* @__PURE__ */ new Set();
5411
+ this.episodeListeners = /* @__PURE__ */ new Set();
5412
+ this.delegatedListeners = /* @__PURE__ */ new Set();
5404
5413
  }
5405
- /** True while any owner's storage is known to be full. */
5414
+ /** True while any owner's storage is known to be full for best-effort writes. */
5406
5415
  get isFull() {
5407
5416
  return this.full.size > 0;
5408
5417
  }
5409
- /** The rejection `storage.full` announced, until the episode ends. */
5418
+ /** The rejection that started the own-storage episode, until it ends. */
5410
5419
  get rejection() {
5411
- return this.event;
5420
+ return this.episode?.event;
5412
5421
  }
5413
5422
  /**
5414
5423
  * True when a write to `spaceId`'s owner was rejected, or a write whose
@@ -5417,13 +5426,31 @@ var StorageFullMonitor = class {
5417
5426
  isFullFor(spaceId) {
5418
5427
  return this.full.has(UNKNOWN_OWNER) || spaceId !== void 0 && this.full.has(this.ownerOf(spaceId));
5419
5428
  }
5420
- /** Watch a context's service errors. Returns the unsubscribe function. */
5429
+ /** Subscribe to the own-storage episode starting or ending. Returns the unsubscribe function. */
5430
+ onEpisode(listener) {
5431
+ this.episodeListeners.add(listener);
5432
+ return () => {
5433
+ this.episodeListeners.delete(listener);
5434
+ };
5435
+ }
5436
+ /**
5437
+ * Subscribe to a delegated owner's storage being found full: once per owner
5438
+ * until that owner is cleared. The own-storage episode does not fire it.
5439
+ * Returns the unsubscribe function.
5440
+ */
5441
+ onDelegated(listener) {
5442
+ this.delegatedListeners.add(listener);
5443
+ return () => {
5444
+ this.delegatedListeners.delete(listener);
5445
+ };
5446
+ }
5447
+ /** Watch a context's service errors and successful writes. Returns the unsubscribe function. */
5421
5448
  observe(context) {
5422
- return context.on(TelemetryEvents.SERVICE_ERROR, (data) => {
5449
+ const stopErrors = context.on(TelemetryEvents.SERVICE_ERROR, (data) => {
5423
5450
  const { service, error } = data;
5424
5451
  if (!error || !isStorageFullError(error)) return;
5425
5452
  const spaceId = context.session?.spaceId;
5426
- const owner = spaceId ? this.ownerOf(spaceId) : UNKNOWN_OWNER;
5453
+ const owner = this.ownerFor(spaceId);
5427
5454
  if (this.full.has(owner)) return;
5428
5455
  const meta = error.meta ?? {};
5429
5456
  const event = { service: service ?? error.service, error };
@@ -5434,8 +5461,13 @@ var StorageFullMonitor = class {
5434
5461
  }
5435
5462
  if (spaceId) event.spaceId = spaceId;
5436
5463
  this.full.set(owner, event);
5437
- if (this.event) return;
5438
- this.event = event;
5464
+ if (this.isOwn(owner)) {
5465
+ if (this.episode) return;
5466
+ this.episode = { owner, event };
5467
+ this.notifyEpisode();
5468
+ } else {
5469
+ this.notifyDelegated();
5470
+ }
5439
5471
  context.emit(TelemetryEvents.STORAGE_FULL, event);
5440
5472
  for (const handler of this.handlers) {
5441
5473
  try {
@@ -5445,6 +5477,19 @@ var StorageFullMonitor = class {
5445
5477
  }
5446
5478
  }
5447
5479
  });
5480
+ const stopWrites = context.on(TelemetryEvents.SERVICE_RESPONSE, (data) => {
5481
+ const { service, action, ok: ok2 } = data;
5482
+ if (ok2 !== true || !isCommittingWrite(service, action)) return;
5483
+ const owner = this.ownerFor(context.session?.spaceId);
5484
+ this.full.delete(owner);
5485
+ if (this.episode?.owner !== owner) return;
5486
+ this.episode = void 0;
5487
+ this.notifyEpisode();
5488
+ });
5489
+ return () => {
5490
+ stopErrors();
5491
+ stopWrites();
5492
+ };
5448
5493
  }
5449
5494
  /** Subscribe to the `storage.full` event. Returns the unsubscribe function. */
5450
5495
  on(handler) {
@@ -5453,22 +5498,56 @@ var StorageFullMonitor = class {
5453
5498
  this.handlers.delete(handler);
5454
5499
  };
5455
5500
  }
5456
- /** Forget that any owner's storage is full; the next rejection emits `storage.full` again. */
5501
+ /** Forget every owner's full state and end the episode; the next rejection starts a new one. */
5457
5502
  clear() {
5458
5503
  this.full.clear();
5459
- this.event = void 0;
5504
+ if (!this.episode) return;
5505
+ this.episode = void 0;
5506
+ this.notifyEpisode();
5460
5507
  }
5461
5508
  /**
5462
- * Forget that `spaceId`'s owner is full (`undefined`: the rejections whose
5463
- * space was unknown). Once no owner is full, the next rejection emits
5464
- * `storage.full` again.
5509
+ * Forget that `spaceId`'s owner is full for best-effort writes (`undefined`:
5510
+ * the rejections whose space was unknown). Does not end the episode.
5465
5511
  */
5466
5512
  clearFor(spaceId) {
5467
- this.full.delete(spaceId === void 0 ? UNKNOWN_OWNER : this.ownerOf(spaceId));
5468
- if (this.full.size === 0) this.event = void 0;
5513
+ this.full.delete(this.ownerFor(spaceId));
5514
+ }
5515
+ isOwn(owner) {
5516
+ return owner === UNKNOWN_OWNER || owner === this.ownOwner();
5517
+ }
5518
+ ownerFor(spaceId) {
5519
+ return spaceId ? this.ownerOf(spaceId) : UNKNOWN_OWNER;
5520
+ }
5521
+ notifyEpisode() {
5522
+ for (const listener of this.episodeListeners) {
5523
+ try {
5524
+ listener();
5525
+ } catch (listenerError) {
5526
+ console.error("Error in storage episode listener:", listenerError);
5527
+ }
5528
+ }
5529
+ }
5530
+ notifyDelegated() {
5531
+ for (const listener of this.delegatedListeners) {
5532
+ try {
5533
+ listener();
5534
+ } catch (listenerError) {
5535
+ console.error("Error in delegated storage listener:", listenerError);
5536
+ }
5537
+ }
5469
5538
  }
5470
5539
  };
5471
5540
  var UNKNOWN_OWNER = "\0unknown";
5541
+ var COMMITTING_ACTIONS = /* @__PURE__ */ new Map([
5542
+ ["kv", ["put", "batchPut"]],
5543
+ ["vault", ["put"]],
5544
+ ["sql", ["execute", "batch", "executeStatement", "migrations.apply"]],
5545
+ ["duckdb", ["execute", "batch", "executeStatement", "import"]]
5546
+ ]);
5547
+ function isCommittingWrite(service, action) {
5548
+ if (service === void 0 || action === void 0) return false;
5549
+ return COMMITTING_ACTIONS.get(service)?.includes(action) ?? false;
5550
+ }
5472
5551
 
5473
5552
  // src/storage/status.ts
5474
5553
  var STORAGE_MANAGE_URL = "https://account.tinycloud.xyz/storage";