@tinycloud/sdk-services 3.1.0-beta.1 → 3.1.0-beta.11

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.
@@ -1,5 +1,5 @@
1
- import { B as BaseService } from '../BaseService-BhpVpA9b.cjs';
2
- import { R as Result, I as IService } from '../types-qwMmB7qB.cjs';
1
+ import { B as BaseService } from '../BaseService-DWbPbov7.cjs';
2
+ import { R as Result, I as IService } from '../types-CVwwxqXF.cjs';
3
3
 
4
4
  /**
5
5
  * KV Service Types
@@ -23,8 +23,11 @@ interface KVServiceConfig {
23
23
  */
24
24
  prefix?: string;
25
25
  /**
26
- * Default timeout in milliseconds for KV operations.
27
- * Overrides the context-level timeout if set.
26
+ * Default timeout in milliseconds for each KV request, including reading
27
+ * the response body. When it elapses the request is aborted and the
28
+ * operation returns an `ErrorCodes.TIMEOUT` error. A per-call `timeout`
29
+ * option overrides it. Unset (the default), `0`, or `Infinity` means no
30
+ * timeout.
28
31
  */
29
32
  timeout?: number;
30
33
  /** Allow additional config properties */
@@ -55,7 +58,8 @@ interface KVGetOptions {
55
58
  */
56
59
  maxResponseBytes?: number;
57
60
  /**
58
- * Custom timeout for this operation in milliseconds.
61
+ * Custom timeout for this operation in milliseconds. Overrides
62
+ * `KVServiceConfig.timeout`; `0` disables a configured timeout.
59
63
  */
60
64
  timeout?: number;
61
65
  /**
@@ -85,7 +89,8 @@ interface KVPutOptions {
85
89
  /** Only write when the key does not exist. */
86
90
  ifNoneMatch?: "*";
87
91
  /**
88
- * Custom timeout for this operation in milliseconds.
92
+ * Custom timeout for this operation in milliseconds. Overrides
93
+ * `KVServiceConfig.timeout`; `0` disables a configured timeout.
89
94
  */
90
95
  timeout?: number;
91
96
  /**
@@ -123,7 +128,8 @@ interface KVBatchPutOptions {
123
128
  */
124
129
  prefix?: string;
125
130
  /**
126
- * Custom timeout for this operation in milliseconds.
131
+ * Custom timeout for this operation in milliseconds. Overrides
132
+ * `KVServiceConfig.timeout`; `0` disables a configured timeout.
127
133
  */
128
134
  timeout?: number;
129
135
  /**
@@ -182,7 +188,8 @@ interface KVListOptions {
182
188
  /** Opaque scope-bound cursor returned by a previous bounded list. */
183
189
  cursor?: string;
184
190
  /**
185
- * Custom timeout for this operation in milliseconds.
191
+ * Custom timeout for this operation in milliseconds. Overrides
192
+ * `KVServiceConfig.timeout`; `0` disables a configured timeout.
186
193
  */
187
194
  timeout?: number;
188
195
  /**
@@ -201,7 +208,8 @@ interface KVDeleteOptions {
201
208
  /** Only delete when the current object has this strong ETag. */
202
209
  ifMatch?: string;
203
210
  /**
204
- * Custom timeout for this operation in milliseconds.
211
+ * Custom timeout for this operation in milliseconds. Overrides
212
+ * `KVServiceConfig.timeout`; `0` disables a configured timeout.
205
213
  */
206
214
  timeout?: number;
207
215
  /**
@@ -218,7 +226,8 @@ interface KVHeadOptions {
218
226
  */
219
227
  prefix?: string;
220
228
  /**
221
- * Custom timeout for this operation in milliseconds.
229
+ * Custom timeout for this operation in milliseconds. Overrides
230
+ * `KVServiceConfig.timeout`; `0` disables a configured timeout.
222
231
  */
223
232
  timeout?: number;
224
233
  /**
@@ -259,7 +268,8 @@ interface KVCreateSignedReadUrlOptions {
259
268
  */
260
269
  etag?: string;
261
270
  /**
262
- * Custom timeout for this operation in milliseconds.
271
+ * Custom timeout for this operation in milliseconds. Overrides
272
+ * `KVServiceConfig.timeout`; `0` disables a configured timeout.
263
273
  */
264
274
  timeout?: number;
265
275
  /**
@@ -323,6 +333,102 @@ interface KVListResponse {
323
333
  /** Opaque cursor for the next page, when the node returned one. */
324
334
  nextCursor?: string;
325
335
  }
336
+ /**
337
+ * Options for {@link IKVService.changes}, the `tinycloud.kv/sync` change feed.
338
+ */
339
+ interface KVChangesOptions {
340
+ /**
341
+ * The space-relative KV prefix to follow, sent as the invocation path. It
342
+ * is not joined with the service's configured prefix, and must be non-empty.
343
+ * Prefixes match whole path segments: `notes` covers `notes` and
344
+ * `notes/a`, while `notes/` covers everything under `notes/` but not
345
+ * `notes` itself. The session must hold `tinycloud.kv/sync` on it by name.
346
+ *
347
+ * Returned `changes[].key` values are full paths in the space, like this
348
+ * prefix. On a service with a configured prefix (for example
349
+ * `DelegatedAccess.kv`, whose prefix is the delegation path), `get` joins
350
+ * that prefix again, so strip it from the key first, or follow the feed
351
+ * with `kv.withPrefix(...).changes()`, which returns relative keys.
352
+ */
353
+ prefix: string;
354
+ /**
355
+ * The `cursor` from the previous page. Omit it to bootstrap from the start
356
+ * of the prefix. Opaque; never parse or build one.
357
+ */
358
+ cursor?: string;
359
+ /**
360
+ * Page size, 1 through 1000 (node default 500). A page never splits one
361
+ * invocation's changes, so it can hold more than `limit` changes.
362
+ */
363
+ limit?: number;
364
+ /**
365
+ * CID of a never-invoked delegation carrying `tinycloud.kv/retain` on the
366
+ * prefix. When valid, the node attests `authority.retainUntil`.
367
+ */
368
+ retentionGrant?: string;
369
+ /**
370
+ * Custom timeout for this operation in milliseconds. Overrides
371
+ * `KVServiceConfig.timeout`; `0` disables a configured timeout.
372
+ */
373
+ timeout?: number;
374
+ /**
375
+ * Custom abort signal for this operation.
376
+ */
377
+ signal?: AbortSignal;
378
+ }
379
+ /**
380
+ * One key's latest state in a change feed page. Content is not included:
381
+ * read it with `get`/`batchGet` and check it against `etag`.
382
+ */
383
+ type KVChange = {
384
+ key: string;
385
+ deleted: false;
386
+ /** The strong ETag `get` returns for this state. */
387
+ etag: string;
388
+ metadata: Record<string, string>;
389
+ } | {
390
+ key: string;
391
+ deleted: true;
392
+ };
393
+ /**
394
+ * The node-attested window in which the caller's authority holds. Values are
395
+ * RFC 3339 timestamps, or `null` when nothing in the chain sets one.
396
+ */
397
+ interface KVChangesAuthority {
398
+ /** The latest `nbf` across the invocation's whole delegation chain. */
399
+ notBefore: string | null;
400
+ /** The earliest `exp` across the invocation's whole delegation chain. */
401
+ expiresAt: string | null;
402
+ /**
403
+ * The earliest `exp` across a valid retention grant's own chain. `null`
404
+ * means no retention: none was presented, or its chain sets no expiry.
405
+ */
406
+ retainUntil: string | null;
407
+ }
408
+ /**
409
+ * One page of the `tinycloud.kv/sync` change feed.
410
+ */
411
+ interface KVChangesResponse {
412
+ /** Each changed key's latest state, in commit order. */
413
+ changes: KVChange[];
414
+ /**
415
+ * More changes are available now. A page may be empty with `more: true`;
416
+ * keep paging with the returned cursor.
417
+ */
418
+ more: boolean;
419
+ /**
420
+ * The cursor for the next request. An empty poll returns the request's
421
+ * cursor unchanged.
422
+ */
423
+ cursor: string;
424
+ /** The node, space and prefix that served the page. */
425
+ source: {
426
+ nodeDid: string;
427
+ space: string;
428
+ prefix: string;
429
+ };
430
+ authority: KVChangesAuthority;
431
+ }
326
432
  /**
327
433
  * Response from signed KV read URL creation.
328
434
  */
@@ -353,6 +459,10 @@ declare const KVAction: {
353
459
  readonly LIST: "tinycloud.kv/list";
354
460
  readonly DELETE: "tinycloud.kv/del";
355
461
  readonly HEAD: "tinycloud.kv/metadata";
462
+ /** The change feed; never implied by `*` or `tinycloud.kv/*`. */
463
+ readonly SYNC: "tinycloud.kv/sync";
464
+ /** Retention attestation; presented through `retentionGrant`, never invoked or implied. */
465
+ readonly RETAIN: "tinycloud.kv/retain";
356
466
  };
357
467
  type KVActionType = (typeof KVAction)[keyof typeof KVAction];
358
468
 
@@ -456,6 +566,20 @@ interface IPrefixedKVService {
456
566
  * ```
457
567
  */
458
568
  list(options?: Omit<KVListOptions, 'prefix'>): Promise<Result<KVListResponse>>;
569
+ /**
570
+ * Read one page of the change feed for everything under this prefix
571
+ * (`tinycloud.kv/sync` on `<prefix>/`). Keys are returned relative to the
572
+ * prefix, like `list`; `source.prefix` stays the node's full prefix.
573
+ * See `IKVService.changes` for paging and errors.
574
+ *
575
+ * @example
576
+ * ```typescript
577
+ * const notes = kv.withPrefix('notes');
578
+ * const page = await notes.changes({ limit: 100 });
579
+ * // -> Feed of: notes/*, keys like 'a.md'
580
+ * ```
581
+ */
582
+ changes(options?: Omit<KVChangesOptions, 'prefix'>): Promise<Result<KVChangesResponse>>;
459
583
  /**
460
584
  * Delete a key.
461
585
  *
@@ -530,6 +654,7 @@ interface IKVServiceLike {
530
654
  put(key: string, value: unknown, options?: KVPutOptions): Promise<Result<KVResponse<void>>>;
531
655
  batchPut(items: KVBatchPutItem[], options?: KVBatchPutOptions): Promise<Result<KVBatchPutResponse>>;
532
656
  list(options?: KVListOptions): Promise<Result<KVListResponse>>;
657
+ changes(options: KVChangesOptions): Promise<Result<KVChangesResponse>>;
533
658
  delete(key: string, options?: KVDeleteOptions): Promise<Result<KVResponse<void>>>;
534
659
  head(key: string, options?: KVHeadOptions): Promise<Result<KVResponse<void>>>;
535
660
  batchHead(keys: string[], options?: KVHeadOptions): Promise<Result<KVBatchReadResponse<void>>>;
@@ -611,6 +736,12 @@ declare class PrefixedKVService implements IPrefixedKVService {
611
736
  * List keys within this prefix.
612
737
  */
613
738
  list(options?: Omit<KVListOptions, 'prefix'>): Promise<Result<KVListResponse>>;
739
+ /**
740
+ * Read the change feed for everything under this prefix. A view with an
741
+ * empty prefix (`""` or `"/"`) is refused: the node does not serve a
742
+ * whole-space feed.
743
+ */
744
+ changes(options?: Omit<KVChangesOptions, 'prefix'>): Promise<Result<KVChangesResponse>>;
614
745
  /**
615
746
  * Delete a key.
616
747
  */
@@ -759,6 +890,41 @@ interface IKVService extends IService {
759
890
  */
760
891
  head(key: string, options?: KVHeadOptions): Promise<Result<KVResponse<void>>>;
761
892
  batchHead(keys: string[], options?: KVHeadOptions): Promise<Result<KVBatchReadResponse<void>>>;
893
+ /**
894
+ * Read one page of the ordered, delete-aware change feed for a KV prefix
895
+ * (`tinycloud.kv/sync`). Each change is a key's latest state; read content
896
+ * with `get`/`batchGet` and check it against the change's `etag`.
897
+ *
898
+ * `options.prefix` and every returned `key` are full paths in the space;
899
+ * the service's configured prefix is not applied to either. On a prefixed
900
+ * service (for example `DelegatedAccess.kv`), `get(change.key)` would join
901
+ * that prefix twice: strip the prefix from the key, or use
902
+ * `withPrefix(...).changes()`, which returns keys relative to the view.
903
+ *
904
+ * The session must hold `tinycloud.kv/sync` on `options.prefix` by name: no
905
+ * wildcard grants it. Page with the returned `cursor` while `more` is true;
906
+ * a page can exceed `limit`, or be empty with `more: true`.
907
+ *
908
+ * Errors: `KV_SYNC_RESET_REQUIRED` (410, `meta.reason`) means discard sync
909
+ * state and restart without a cursor. `AUTH_DELEGATION_REVOKED` and
910
+ * `AUTH_DELEGATION_ANCESTOR_REVOKED` mean the grant is revoked.
911
+ * `KV_RETENTION_GRANT_REFUSED` (403, `meta.reason`) rejects `retentionGrant`.
912
+ *
913
+ * Requires a node that advertises the `kv-sync-v1` feature in `/info`.
914
+ *
915
+ * @example
916
+ * ```typescript
917
+ * let cursor: string | undefined;
918
+ * do {
919
+ * const page = await kv.changes({ prefix: 'notes/', cursor });
920
+ * if (!page.ok) throw new Error(page.error.message);
921
+ * for (const change of page.data.changes) apply(change);
922
+ * cursor = page.data.cursor;
923
+ * if (!page.data.more) break;
924
+ * } while (true);
925
+ * ```
926
+ */
927
+ changes(options: KVChangesOptions): Promise<Result<KVChangesResponse>>;
762
928
  /**
763
929
  * Create a short-lived signed URL for reading a KV object.
764
930
  *
@@ -850,7 +1016,6 @@ declare class KVService extends BaseService implements IKVService {
850
1016
  * Get the service configuration.
851
1017
  */
852
1018
  get config(): KVServiceConfig;
853
- private parseQuotaInfo;
854
1019
  private handleQuotaErrorResponse;
855
1020
  /**
856
1021
  * Classify a KV 404 by reading the response body once.
@@ -883,7 +1048,7 @@ declare class KVService extends BaseService implements IKVService {
883
1048
  * @param path - Resource path
884
1049
  * @param action - KV action
885
1050
  * @param body - Optional request body
886
- * @param signal - Optional abort signal
1051
+ * @param signal - Request signal from createRequestSignal()
887
1052
  * @returns Fetch response
888
1053
  */
889
1054
  private invokeOperation;
@@ -919,12 +1084,16 @@ declare class KVService extends BaseService implements IKVService {
919
1084
  */
920
1085
  private parseResponse;
921
1086
  private createSignedReadUrlError;
1087
+ /** A known 401/403 keeps its typed status even if reading its body fails. */
1088
+ private readAuthorizationText;
922
1089
  /**
923
1090
  * AUTH_UNAUTHORIZED for a 401/403 response. The message keeps the HTTP
924
1091
  * status and the server text (`<context>: <status> - <text>`, the same
925
1092
  * shape as every other KV failure) so callers that rethrow only the message
926
1093
  * still see the status; `meta.status` carries it for typed callers.
927
- * Resource/ability hints are parsed from the raw body, never the message.
1094
+ * Server hints must match the requested path/action (whether the node sends
1095
+ * a relative path or full resource); metadata uses only the request-derived
1096
+ * canonical resource under the authenticated session's space.
928
1097
  */
929
1098
  private authorizationFailure;
930
1099
  private normalizeSignedReadUrlResponse;
@@ -945,6 +1114,19 @@ declare class KVService extends BaseService implements IKVService {
945
1114
  * List keys with optional prefix filtering.
946
1115
  */
947
1116
  list(options?: KVListOptions): Promise<Result<KVListResponse>>;
1117
+ /**
1118
+ * Read one page of the `tinycloud.kv/sync` change feed for a prefix.
1119
+ */
1120
+ changes(options: KVChangesOptions): Promise<Result<KVChangesResponse>>;
1121
+ /**
1122
+ * Map a failed `kv/sync` response. 410 and the retention 403 carry
1123
+ * `{"error":{"code","reason"}}`; a revoked grant is a 401 whose whole body
1124
+ * is the node's revocation error (see REVOKED_BODY). A body read that
1125
+ * fails because the request was cancelled or timed out rethrows, so the
1126
+ * caller sees `ABORTED`/`TIMEOUT` rather than a mapped status.
1127
+ */
1128
+ private changesFailure;
1129
+ private normalizeChangesResponse;
948
1130
  /**
949
1131
  * Delete a key.
950
1132
  */
@@ -1003,4 +1185,4 @@ declare class KVService extends BaseService implements IKVService {
1003
1185
  withPrefix(prefix: string): IPrefixedKVService;
1004
1186
  }
1005
1187
 
1006
- export { DEFAULT_SIGNED_READ_URL_EXPIRY_MS, type IKVService, type IPrefixedKVService, KVAction, type KVActionType, type KVBatchPutItem, type KVBatchPutOptions, type KVBatchPutResponse, type KVBatchReadResponse, type KVCreateSignedReadUrlOptions, type KVDeleteOptions, type KVGetOptions, type KVHeadOptions, type KVListOptions, type KVListResponse, type KVPutOptions, type KVResponse, type KVResponseHeaders, KVService, type KVServiceConfig, type KVSignedReadUrlResponse, PrefixedKVService };
1188
+ export { DEFAULT_SIGNED_READ_URL_EXPIRY_MS, type IKVService, type IPrefixedKVService, KVAction, type KVActionType, type KVBatchPutItem, type KVBatchPutOptions, type KVBatchPutResponse, type KVBatchReadResponse, type KVChange, type KVChangesAuthority, type KVChangesOptions, type KVChangesResponse, type KVCreateSignedReadUrlOptions, type KVDeleteOptions, type KVGetOptions, type KVHeadOptions, type KVListOptions, type KVListResponse, type KVPutOptions, type KVResponse, type KVResponseHeaders, KVService, type KVServiceConfig, type KVSignedReadUrlResponse, PrefixedKVService };
@@ -1,5 +1,5 @@
1
- import { B as BaseService } from '../BaseService-CuV-ZVPs.js';
2
- import { R as Result, I as IService } from '../types-qwMmB7qB.js';
1
+ import { B as BaseService } from '../BaseService-B_UKhVht.js';
2
+ import { R as Result, I as IService } from '../types-CVwwxqXF.js';
3
3
 
4
4
  /**
5
5
  * KV Service Types
@@ -23,8 +23,11 @@ interface KVServiceConfig {
23
23
  */
24
24
  prefix?: string;
25
25
  /**
26
- * Default timeout in milliseconds for KV operations.
27
- * Overrides the context-level timeout if set.
26
+ * Default timeout in milliseconds for each KV request, including reading
27
+ * the response body. When it elapses the request is aborted and the
28
+ * operation returns an `ErrorCodes.TIMEOUT` error. A per-call `timeout`
29
+ * option overrides it. Unset (the default), `0`, or `Infinity` means no
30
+ * timeout.
28
31
  */
29
32
  timeout?: number;
30
33
  /** Allow additional config properties */
@@ -55,7 +58,8 @@ interface KVGetOptions {
55
58
  */
56
59
  maxResponseBytes?: number;
57
60
  /**
58
- * Custom timeout for this operation in milliseconds.
61
+ * Custom timeout for this operation in milliseconds. Overrides
62
+ * `KVServiceConfig.timeout`; `0` disables a configured timeout.
59
63
  */
60
64
  timeout?: number;
61
65
  /**
@@ -85,7 +89,8 @@ interface KVPutOptions {
85
89
  /** Only write when the key does not exist. */
86
90
  ifNoneMatch?: "*";
87
91
  /**
88
- * Custom timeout for this operation in milliseconds.
92
+ * Custom timeout for this operation in milliseconds. Overrides
93
+ * `KVServiceConfig.timeout`; `0` disables a configured timeout.
89
94
  */
90
95
  timeout?: number;
91
96
  /**
@@ -123,7 +128,8 @@ interface KVBatchPutOptions {
123
128
  */
124
129
  prefix?: string;
125
130
  /**
126
- * Custom timeout for this operation in milliseconds.
131
+ * Custom timeout for this operation in milliseconds. Overrides
132
+ * `KVServiceConfig.timeout`; `0` disables a configured timeout.
127
133
  */
128
134
  timeout?: number;
129
135
  /**
@@ -182,7 +188,8 @@ interface KVListOptions {
182
188
  /** Opaque scope-bound cursor returned by a previous bounded list. */
183
189
  cursor?: string;
184
190
  /**
185
- * Custom timeout for this operation in milliseconds.
191
+ * Custom timeout for this operation in milliseconds. Overrides
192
+ * `KVServiceConfig.timeout`; `0` disables a configured timeout.
186
193
  */
187
194
  timeout?: number;
188
195
  /**
@@ -201,7 +208,8 @@ interface KVDeleteOptions {
201
208
  /** Only delete when the current object has this strong ETag. */
202
209
  ifMatch?: string;
203
210
  /**
204
- * Custom timeout for this operation in milliseconds.
211
+ * Custom timeout for this operation in milliseconds. Overrides
212
+ * `KVServiceConfig.timeout`; `0` disables a configured timeout.
205
213
  */
206
214
  timeout?: number;
207
215
  /**
@@ -218,7 +226,8 @@ interface KVHeadOptions {
218
226
  */
219
227
  prefix?: string;
220
228
  /**
221
- * Custom timeout for this operation in milliseconds.
229
+ * Custom timeout for this operation in milliseconds. Overrides
230
+ * `KVServiceConfig.timeout`; `0` disables a configured timeout.
222
231
  */
223
232
  timeout?: number;
224
233
  /**
@@ -259,7 +268,8 @@ interface KVCreateSignedReadUrlOptions {
259
268
  */
260
269
  etag?: string;
261
270
  /**
262
- * Custom timeout for this operation in milliseconds.
271
+ * Custom timeout for this operation in milliseconds. Overrides
272
+ * `KVServiceConfig.timeout`; `0` disables a configured timeout.
263
273
  */
264
274
  timeout?: number;
265
275
  /**
@@ -323,6 +333,102 @@ interface KVListResponse {
323
333
  /** Opaque cursor for the next page, when the node returned one. */
324
334
  nextCursor?: string;
325
335
  }
336
+ /**
337
+ * Options for {@link IKVService.changes}, the `tinycloud.kv/sync` change feed.
338
+ */
339
+ interface KVChangesOptions {
340
+ /**
341
+ * The space-relative KV prefix to follow, sent as the invocation path. It
342
+ * is not joined with the service's configured prefix, and must be non-empty.
343
+ * Prefixes match whole path segments: `notes` covers `notes` and
344
+ * `notes/a`, while `notes/` covers everything under `notes/` but not
345
+ * `notes` itself. The session must hold `tinycloud.kv/sync` on it by name.
346
+ *
347
+ * Returned `changes[].key` values are full paths in the space, like this
348
+ * prefix. On a service with a configured prefix (for example
349
+ * `DelegatedAccess.kv`, whose prefix is the delegation path), `get` joins
350
+ * that prefix again, so strip it from the key first, or follow the feed
351
+ * with `kv.withPrefix(...).changes()`, which returns relative keys.
352
+ */
353
+ prefix: string;
354
+ /**
355
+ * The `cursor` from the previous page. Omit it to bootstrap from the start
356
+ * of the prefix. Opaque; never parse or build one.
357
+ */
358
+ cursor?: string;
359
+ /**
360
+ * Page size, 1 through 1000 (node default 500). A page never splits one
361
+ * invocation's changes, so it can hold more than `limit` changes.
362
+ */
363
+ limit?: number;
364
+ /**
365
+ * CID of a never-invoked delegation carrying `tinycloud.kv/retain` on the
366
+ * prefix. When valid, the node attests `authority.retainUntil`.
367
+ */
368
+ retentionGrant?: string;
369
+ /**
370
+ * Custom timeout for this operation in milliseconds. Overrides
371
+ * `KVServiceConfig.timeout`; `0` disables a configured timeout.
372
+ */
373
+ timeout?: number;
374
+ /**
375
+ * Custom abort signal for this operation.
376
+ */
377
+ signal?: AbortSignal;
378
+ }
379
+ /**
380
+ * One key's latest state in a change feed page. Content is not included:
381
+ * read it with `get`/`batchGet` and check it against `etag`.
382
+ */
383
+ type KVChange = {
384
+ key: string;
385
+ deleted: false;
386
+ /** The strong ETag `get` returns for this state. */
387
+ etag: string;
388
+ metadata: Record<string, string>;
389
+ } | {
390
+ key: string;
391
+ deleted: true;
392
+ };
393
+ /**
394
+ * The node-attested window in which the caller's authority holds. Values are
395
+ * RFC 3339 timestamps, or `null` when nothing in the chain sets one.
396
+ */
397
+ interface KVChangesAuthority {
398
+ /** The latest `nbf` across the invocation's whole delegation chain. */
399
+ notBefore: string | null;
400
+ /** The earliest `exp` across the invocation's whole delegation chain. */
401
+ expiresAt: string | null;
402
+ /**
403
+ * The earliest `exp` across a valid retention grant's own chain. `null`
404
+ * means no retention: none was presented, or its chain sets no expiry.
405
+ */
406
+ retainUntil: string | null;
407
+ }
408
+ /**
409
+ * One page of the `tinycloud.kv/sync` change feed.
410
+ */
411
+ interface KVChangesResponse {
412
+ /** Each changed key's latest state, in commit order. */
413
+ changes: KVChange[];
414
+ /**
415
+ * More changes are available now. A page may be empty with `more: true`;
416
+ * keep paging with the returned cursor.
417
+ */
418
+ more: boolean;
419
+ /**
420
+ * The cursor for the next request. An empty poll returns the request's
421
+ * cursor unchanged.
422
+ */
423
+ cursor: string;
424
+ /** The node, space and prefix that served the page. */
425
+ source: {
426
+ nodeDid: string;
427
+ space: string;
428
+ prefix: string;
429
+ };
430
+ authority: KVChangesAuthority;
431
+ }
326
432
  /**
327
433
  * Response from signed KV read URL creation.
328
434
  */
@@ -353,6 +459,10 @@ declare const KVAction: {
353
459
  readonly LIST: "tinycloud.kv/list";
354
460
  readonly DELETE: "tinycloud.kv/del";
355
461
  readonly HEAD: "tinycloud.kv/metadata";
462
+ /** The change feed; never implied by `*` or `tinycloud.kv/*`. */
463
+ readonly SYNC: "tinycloud.kv/sync";
464
+ /** Retention attestation; presented through `retentionGrant`, never invoked or implied. */
465
+ readonly RETAIN: "tinycloud.kv/retain";
356
466
  };
357
467
  type KVActionType = (typeof KVAction)[keyof typeof KVAction];
358
468
 
@@ -456,6 +566,20 @@ interface IPrefixedKVService {
456
566
  * ```
457
567
  */
458
568
  list(options?: Omit<KVListOptions, 'prefix'>): Promise<Result<KVListResponse>>;
569
+ /**
570
+ * Read one page of the change feed for everything under this prefix
571
+ * (`tinycloud.kv/sync` on `<prefix>/`). Keys are returned relative to the
572
+ * prefix, like `list`; `source.prefix` stays the node's full prefix.
573
+ * See `IKVService.changes` for paging and errors.
574
+ *
575
+ * @example
576
+ * ```typescript
577
+ * const notes = kv.withPrefix('notes');
578
+ * const page = await notes.changes({ limit: 100 });
579
+ * // -> Feed of: notes/*, keys like 'a.md'
580
+ * ```
581
+ */
582
+ changes(options?: Omit<KVChangesOptions, 'prefix'>): Promise<Result<KVChangesResponse>>;
459
583
  /**
460
584
  * Delete a key.
461
585
  *
@@ -530,6 +654,7 @@ interface IKVServiceLike {
530
654
  put(key: string, value: unknown, options?: KVPutOptions): Promise<Result<KVResponse<void>>>;
531
655
  batchPut(items: KVBatchPutItem[], options?: KVBatchPutOptions): Promise<Result<KVBatchPutResponse>>;
532
656
  list(options?: KVListOptions): Promise<Result<KVListResponse>>;
657
+ changes(options: KVChangesOptions): Promise<Result<KVChangesResponse>>;
533
658
  delete(key: string, options?: KVDeleteOptions): Promise<Result<KVResponse<void>>>;
534
659
  head(key: string, options?: KVHeadOptions): Promise<Result<KVResponse<void>>>;
535
660
  batchHead(keys: string[], options?: KVHeadOptions): Promise<Result<KVBatchReadResponse<void>>>;
@@ -611,6 +736,12 @@ declare class PrefixedKVService implements IPrefixedKVService {
611
736
  * List keys within this prefix.
612
737
  */
613
738
  list(options?: Omit<KVListOptions, 'prefix'>): Promise<Result<KVListResponse>>;
739
+ /**
740
+ * Read the change feed for everything under this prefix. A view with an
741
+ * empty prefix (`""` or `"/"`) is refused: the node does not serve a
742
+ * whole-space feed.
743
+ */
744
+ changes(options?: Omit<KVChangesOptions, 'prefix'>): Promise<Result<KVChangesResponse>>;
614
745
  /**
615
746
  * Delete a key.
616
747
  */
@@ -759,6 +890,41 @@ interface IKVService extends IService {
759
890
  */
760
891
  head(key: string, options?: KVHeadOptions): Promise<Result<KVResponse<void>>>;
761
892
  batchHead(keys: string[], options?: KVHeadOptions): Promise<Result<KVBatchReadResponse<void>>>;
893
+ /**
894
+ * Read one page of the ordered, delete-aware change feed for a KV prefix
895
+ * (`tinycloud.kv/sync`). Each change is a key's latest state; read content
896
+ * with `get`/`batchGet` and check it against the change's `etag`.
897
+ *
898
+ * `options.prefix` and every returned `key` are full paths in the space;
899
+ * the service's configured prefix is not applied to either. On a prefixed
900
+ * service (for example `DelegatedAccess.kv`), `get(change.key)` would join
901
+ * that prefix twice: strip the prefix from the key, or use
902
+ * `withPrefix(...).changes()`, which returns keys relative to the view.
903
+ *
904
+ * The session must hold `tinycloud.kv/sync` on `options.prefix` by name: no
905
+ * wildcard grants it. Page with the returned `cursor` while `more` is true;
906
+ * a page can exceed `limit`, or be empty with `more: true`.
907
+ *
908
+ * Errors: `KV_SYNC_RESET_REQUIRED` (410, `meta.reason`) means discard sync
909
+ * state and restart without a cursor. `AUTH_DELEGATION_REVOKED` and
910
+ * `AUTH_DELEGATION_ANCESTOR_REVOKED` mean the grant is revoked.
911
+ * `KV_RETENTION_GRANT_REFUSED` (403, `meta.reason`) rejects `retentionGrant`.
912
+ *
913
+ * Requires a node that advertises the `kv-sync-v1` feature in `/info`.
914
+ *
915
+ * @example
916
+ * ```typescript
917
+ * let cursor: string | undefined;
918
+ * do {
919
+ * const page = await kv.changes({ prefix: 'notes/', cursor });
920
+ * if (!page.ok) throw new Error(page.error.message);
921
+ * for (const change of page.data.changes) apply(change);
922
+ * cursor = page.data.cursor;
923
+ * if (!page.data.more) break;
924
+ * } while (true);
925
+ * ```
926
+ */
927
+ changes(options: KVChangesOptions): Promise<Result<KVChangesResponse>>;
762
928
  /**
763
929
  * Create a short-lived signed URL for reading a KV object.
764
930
  *
@@ -850,7 +1016,6 @@ declare class KVService extends BaseService implements IKVService {
850
1016
  * Get the service configuration.
851
1017
  */
852
1018
  get config(): KVServiceConfig;
853
- private parseQuotaInfo;
854
1019
  private handleQuotaErrorResponse;
855
1020
  /**
856
1021
  * Classify a KV 404 by reading the response body once.
@@ -883,7 +1048,7 @@ declare class KVService extends BaseService implements IKVService {
883
1048
  * @param path - Resource path
884
1049
  * @param action - KV action
885
1050
  * @param body - Optional request body
886
- * @param signal - Optional abort signal
1051
+ * @param signal - Request signal from createRequestSignal()
887
1052
  * @returns Fetch response
888
1053
  */
889
1054
  private invokeOperation;
@@ -919,12 +1084,16 @@ declare class KVService extends BaseService implements IKVService {
919
1084
  */
920
1085
  private parseResponse;
921
1086
  private createSignedReadUrlError;
1087
+ /** A known 401/403 keeps its typed status even if reading its body fails. */
1088
+ private readAuthorizationText;
922
1089
  /**
923
1090
  * AUTH_UNAUTHORIZED for a 401/403 response. The message keeps the HTTP
924
1091
  * status and the server text (`<context>: <status> - <text>`, the same
925
1092
  * shape as every other KV failure) so callers that rethrow only the message
926
1093
  * still see the status; `meta.status` carries it for typed callers.
927
- * Resource/ability hints are parsed from the raw body, never the message.
1094
+ * Server hints must match the requested path/action (whether the node sends
1095
+ * a relative path or full resource); metadata uses only the request-derived
1096
+ * canonical resource under the authenticated session's space.
928
1097
  */
929
1098
  private authorizationFailure;
930
1099
  private normalizeSignedReadUrlResponse;
@@ -945,6 +1114,19 @@ declare class KVService extends BaseService implements IKVService {
945
1114
  * List keys with optional prefix filtering.
946
1115
  */
947
1116
  list(options?: KVListOptions): Promise<Result<KVListResponse>>;
1117
+ /**
1118
+ * Read one page of the `tinycloud.kv/sync` change feed for a prefix.
1119
+ */
1120
+ changes(options: KVChangesOptions): Promise<Result<KVChangesResponse>>;
1121
+ /**
1122
+ * Map a failed `kv/sync` response. 410 and the retention 403 carry
1123
+ * `{"error":{"code","reason"}}`; a revoked grant is a 401 whose whole body
1124
+ * is the node's revocation error (see REVOKED_BODY). A body read that
1125
+ * fails because the request was cancelled or timed out rethrows, so the
1126
+ * caller sees `ABORTED`/`TIMEOUT` rather than a mapped status.
1127
+ */
1128
+ private changesFailure;
1129
+ private normalizeChangesResponse;
948
1130
  /**
949
1131
  * Delete a key.
950
1132
  */
@@ -1003,4 +1185,4 @@ declare class KVService extends BaseService implements IKVService {
1003
1185
  withPrefix(prefix: string): IPrefixedKVService;
1004
1186
  }
1005
1187
 
1006
- export { DEFAULT_SIGNED_READ_URL_EXPIRY_MS, type IKVService, type IPrefixedKVService, KVAction, type KVActionType, type KVBatchPutItem, type KVBatchPutOptions, type KVBatchPutResponse, type KVBatchReadResponse, type KVCreateSignedReadUrlOptions, type KVDeleteOptions, type KVGetOptions, type KVHeadOptions, type KVListOptions, type KVListResponse, type KVPutOptions, type KVResponse, type KVResponseHeaders, KVService, type KVServiceConfig, type KVSignedReadUrlResponse, PrefixedKVService };
1188
+ export { DEFAULT_SIGNED_READ_URL_EXPIRY_MS, type IKVService, type IPrefixedKVService, KVAction, type KVActionType, type KVBatchPutItem, type KVBatchPutOptions, type KVBatchPutResponse, type KVBatchReadResponse, type KVChange, type KVChangesAuthority, type KVChangesOptions, type KVChangesResponse, type KVCreateSignedReadUrlOptions, type KVDeleteOptions, type KVGetOptions, type KVHeadOptions, type KVListOptions, type KVListResponse, type KVPutOptions, type KVResponse, type KVResponseHeaders, KVService, type KVServiceConfig, type KVSignedReadUrlResponse, PrefixedKVService };