@tinycloud/sdk-services 3.2.0-beta.2 → 3.2.0-beta.3

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 (39) hide show
  1. package/dist/{BaseService-DWbPbov7.d.cts → BaseService-BDQXjWMx.d.cts} +1 -1
  2. package/dist/{BaseService-B_UKhVht.d.ts → BaseService-Di_hVfUV.d.ts} +1 -1
  3. package/dist/encryption/index.cjs +7 -1
  4. package/dist/encryption/index.cjs.map +1 -1
  5. package/dist/encryption/index.d.cts +2 -2
  6. package/dist/encryption/index.d.ts +2 -2
  7. package/dist/encryption/index.js +7 -1
  8. package/dist/encryption/index.js.map +1 -1
  9. package/dist/index.cjs +595 -353
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +36 -26
  12. package/dist/index.d.ts +36 -26
  13. package/dist/index.js +590 -353
  14. package/dist/index.js.map +1 -1
  15. package/dist/internal/decrypt-transport-response-error.d.cts +1 -1
  16. package/dist/internal/decrypt-transport-response-error.d.ts +1 -1
  17. package/dist/kv/index.cjs +530 -334
  18. package/dist/kv/index.cjs.map +1 -1
  19. package/dist/kv/index.d.cts +17 -468
  20. package/dist/kv/index.d.ts +17 -468
  21. package/dist/kv/index.js +524 -333
  22. package/dist/kv/index.js.map +1 -1
  23. package/dist/kv/replication/index.cjs +1375 -0
  24. package/dist/kv/replication/index.cjs.map +1 -0
  25. package/dist/kv/replication/index.d.cts +9 -0
  26. package/dist/kv/replication/index.d.ts +9 -0
  27. package/dist/kv/replication/index.js +1342 -0
  28. package/dist/kv/replication/index.js.map +1 -0
  29. package/dist/scope-BmLxwyl-.d.ts +957 -0
  30. package/dist/scope-D855kRDx.d.cts +957 -0
  31. package/dist/sql/index.cjs +12 -6
  32. package/dist/sql/index.cjs.map +1 -1
  33. package/dist/sql/index.d.cts +2 -2
  34. package/dist/sql/index.d.ts +2 -2
  35. package/dist/sql/index.js +12 -6
  36. package/dist/sql/index.js.map +1 -1
  37. package/dist/{types-CVwwxqXF.d.cts → types-7A5rHWRY.d.cts} +10 -1
  38. package/dist/{types-CVwwxqXF.d.ts → types-7A5rHWRY.d.ts} +10 -1
  39. package/package.json +18 -1
@@ -0,0 +1,1342 @@
1
+ // src/kv/replication/constants.ts
2
+ var CLEAR_PENDING_WARNING = "Clearing pending writes surrenders read-your-writes protection for the cleared keys. If a write whose record was cleared had an unknown outcome or never finished, it may commit now or later; this device can serve an older value until the next sync after that commit, as it would for a write from another device.";
3
+
4
+ // src/kv/replication/identity.ts
5
+ var SPACE_ID = /^(tinycloud:pkh:eip155:\d+:)([^:]+)(:.+)$/;
6
+ var PRINCIPAL_DID = /^(did:pkh:eip155:\d+:)([^:]+)$/;
7
+ function canonicalReplicationIdentity(raw) {
8
+ if (!raw || typeof raw.host !== "string" || typeof raw.space !== "string" || typeof raw.principal !== "string") {
9
+ throw new TypeError(
10
+ "Replication identity host, space, and principal must be strings"
11
+ );
12
+ }
13
+ const authority = /^https?:\/\/([^/?#]*)/i.exec(raw.host)?.[1];
14
+ let url;
15
+ try {
16
+ url = new URL(raw.host);
17
+ } catch {
18
+ throw new TypeError(
19
+ "Replication identity host must be an absolute HTTP(S) URL"
20
+ );
21
+ }
22
+ if (!authority || authority.includes("@") || raw.host.includes("?") || raw.host.includes("#") || url.username || url.password) {
23
+ throw new TypeError(
24
+ "Replication identity host must be HTTP(S) without credentials, query, or fragment"
25
+ );
26
+ }
27
+ const space = SPACE_ID.exec(raw.space);
28
+ const principal = PRINCIPAL_DID.exec(raw.principal);
29
+ if (!space || !principal) {
30
+ throw new TypeError(
31
+ "Replication identity requires a full TinyCloud space id and a pkh principal DID"
32
+ );
33
+ }
34
+ return {
35
+ host: `${url.origin}${url.pathname.replace(/\/+$/, "")}`,
36
+ space: `${space[1]}${space[2].toLowerCase()}${space[3]}`,
37
+ principal: `${principal[1]}${principal[2].toLowerCase()}`
38
+ };
39
+ }
40
+ function replicationIdentityKey(id) {
41
+ const canonical = canonicalReplicationIdentity(id);
42
+ return `tc-replication/v1
43
+ ${canonical.host}
44
+ ${canonical.space}
45
+ ${canonical.principal}`;
46
+ }
47
+
48
+ // src/kv/replication/scope.ts
49
+ function kvPrefixCovers(prefix, key) {
50
+ if (prefix === "") return true;
51
+ if (prefix.endsWith("/")) return key.startsWith(prefix);
52
+ return key === prefix || key.startsWith(`${prefix}/`);
53
+ }
54
+ function listRangeCovers(path, key) {
55
+ return key === path || key.startsWith(path.endsWith("/") ? path : `${path}/`);
56
+ }
57
+ function requiresSecretsOptIn(space, prefix) {
58
+ if (space === "secrets" || space.endsWith(":secrets")) return true;
59
+ return prefix.split("/", 1)[0] === "vault";
60
+ }
61
+
62
+ // src/errors.ts
63
+ import { CAPABILITY_REGISTRY } from "@tinycloud/bootstrap";
64
+
65
+ // src/types.ts
66
+ var ErrorCodes = {
67
+ // Common errors
68
+ NOT_FOUND: "NOT_FOUND",
69
+ AUTH_EXPIRED: "AUTH_EXPIRED",
70
+ AUTH_REQUIRED: "AUTH_REQUIRED",
71
+ AUTH_UNAUTHORIZED: "AUTH_UNAUTHORIZED",
72
+ /** HTTP 401: the delegation the invocation cites was revoked. */
73
+ AUTH_DELEGATION_REVOKED: "AUTH_DELEGATION_REVOKED",
74
+ /** HTTP 401: a delegation the cited one rests on was revoked. */
75
+ AUTH_DELEGATION_ANCESTOR_REVOKED: "AUTH_DELEGATION_ANCESTOR_REVOKED",
76
+ NETWORK_ERROR: "NETWORK_ERROR",
77
+ TIMEOUT: "TIMEOUT",
78
+ ABORTED: "ABORTED",
79
+ INVALID_INPUT: "INVALID_INPUT",
80
+ PERMISSION_DENIED: "PERMISSION_DENIED",
81
+ // KV-specific errors
82
+ KV_NOT_FOUND: "KV_NOT_FOUND",
83
+ KV_WRITE_FAILED: "KV_WRITE_FAILED",
84
+ KV_PRECONDITION_FAILED: "KV_PRECONDITION_FAILED",
85
+ KV_CONFLICT: "KV_CONFLICT",
86
+ KV_RESPONSE_TOO_LARGE: "KV_RESPONSE_TOO_LARGE",
87
+ /** HTTP 410 from the change feed: discard sync state and restart without a cursor; `meta.reason` says why. */
88
+ KV_SYNC_RESET_REQUIRED: "KV_SYNC_RESET_REQUIRED",
89
+ /** HTTP 403 from the change feed: the retention grant was refused; `meta.reason` says why. */
90
+ KV_RETENTION_GRANT_REFUSED: "KV_RETENTION_GRANT_REFUSED",
91
+ // SQL-specific errors
92
+ SQL_ERROR: "SQL_ERROR",
93
+ SQL_PERMISSION_DENIED: "SQL_PERMISSION_DENIED",
94
+ SQL_DATABASE_NOT_FOUND: "SQL_DATABASE_NOT_FOUND",
95
+ SQL_RESPONSE_TOO_LARGE: "SQL_RESPONSE_TOO_LARGE",
96
+ SQL_QUOTA_EXCEEDED: "SQL_QUOTA_EXCEEDED",
97
+ SQL_INVALID_STATEMENT: "SQL_INVALID_STATEMENT",
98
+ SQL_SCHEMA_ERROR: "SQL_SCHEMA_ERROR",
99
+ SQL_READONLY_VIOLATION: "SQL_READONLY_VIOLATION",
100
+ // Storage quota errors
101
+ STORAGE_QUOTA_EXCEEDED: "STORAGE_QUOTA_EXCEEDED",
102
+ STORAGE_LIMIT_REACHED: "STORAGE_LIMIT_REACHED",
103
+ // DuckDB-specific errors
104
+ DUCKDB_ERROR: "DUCKDB_ERROR",
105
+ DUCKDB_PERMISSION_DENIED: "DUCKDB_PERMISSION_DENIED",
106
+ DUCKDB_DATABASE_NOT_FOUND: "DUCKDB_DATABASE_NOT_FOUND",
107
+ DUCKDB_RESPONSE_TOO_LARGE: "DUCKDB_RESPONSE_TOO_LARGE",
108
+ DUCKDB_QUOTA_EXCEEDED: "DUCKDB_QUOTA_EXCEEDED",
109
+ DUCKDB_INVALID_STATEMENT: "DUCKDB_INVALID_STATEMENT",
110
+ DUCKDB_SCHEMA_ERROR: "DUCKDB_SCHEMA_ERROR",
111
+ DUCKDB_READONLY_VIOLATION: "DUCKDB_READONLY_VIOLATION"
112
+ };
113
+ var defaultRetryPolicy = {
114
+ maxAttempts: 3,
115
+ backoff: "exponential",
116
+ baseDelayMs: 1e3,
117
+ maxDelayMs: 1e4,
118
+ retryableErrors: [ErrorCodes.NETWORK_ERROR, ErrorCodes.TIMEOUT]
119
+ };
120
+ function ok(data) {
121
+ return { ok: true, data };
122
+ }
123
+ function err(error) {
124
+ return { ok: false, error };
125
+ }
126
+ function serviceError(code, message, service, options) {
127
+ return {
128
+ code,
129
+ message,
130
+ service,
131
+ cause: options?.cause,
132
+ meta: options?.meta
133
+ };
134
+ }
135
+
136
+ // src/errors.ts
137
+ var RequestTimeoutError = class extends Error {
138
+ constructor(timeoutMs) {
139
+ super(`Request timed out after ${timeoutMs}ms.`);
140
+ this.name = "TimeoutError";
141
+ this.timeoutMs = timeoutMs;
142
+ }
143
+ };
144
+ function isRequestTimeoutError(error) {
145
+ return error instanceof RequestTimeoutError || error instanceof Error && error.name === "TimeoutError" && typeof error.timeoutMs === "number";
146
+ }
147
+ function timeoutError(service, timeoutMs, cause) {
148
+ if (timeoutMs === void 0) {
149
+ return {
150
+ code: ErrorCodes.TIMEOUT,
151
+ message: "Request timed out.",
152
+ service,
153
+ ...cause === void 0 ? {} : { cause }
154
+ };
155
+ }
156
+ return {
157
+ code: ErrorCodes.TIMEOUT,
158
+ message: `Request timed out after ${timeoutMs}ms.`,
159
+ service,
160
+ ...cause === void 0 ? {} : { cause },
161
+ meta: { timeoutMs }
162
+ };
163
+ }
164
+ function abortedError(service) {
165
+ return {
166
+ code: ErrorCodes.ABORTED,
167
+ message: "Request was aborted.",
168
+ service
169
+ };
170
+ }
171
+ var grantableActions = new Set(
172
+ CAPABILITY_REGISTRY.map(({ urn }) => urn).filter((urn) => !urn.endsWith("/*"))
173
+ );
174
+ var capabilityServices = new Set(
175
+ CAPABILITY_REGISTRY.map(({ service }) => service.slice("tinycloud.".length))
176
+ );
177
+ function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
178
+ if (error instanceof Error) {
179
+ if (isRequestTimeoutError(error)) {
180
+ return timeoutError(service, error.timeoutMs, error);
181
+ }
182
+ if (error.name === "AbortError") {
183
+ return abortedError(service);
184
+ }
185
+ if (error.name === "TimeoutError" || error.message.toLowerCase().includes("timeout")) {
186
+ return timeoutError(service);
187
+ }
188
+ return {
189
+ code: defaultCode,
190
+ message: error.message,
191
+ service,
192
+ cause: error
193
+ };
194
+ }
195
+ return {
196
+ code: defaultCode,
197
+ message: String(error),
198
+ service
199
+ };
200
+ }
201
+
202
+ // src/kv/replication/outcome.ts
203
+ var DEFINITIVE_FAILURES = /* @__PURE__ */ new Set([
204
+ ErrorCodes.AUTH_REQUIRED,
205
+ ErrorCodes.INVALID_INPUT,
206
+ ErrorCodes.AUTH_UNAUTHORIZED,
207
+ ErrorCodes.KV_PRECONDITION_FAILED,
208
+ ErrorCodes.STORAGE_QUOTA_EXCEEDED,
209
+ ErrorCodes.STORAGE_LIMIT_REACHED
210
+ ]);
211
+ function classifyWriteOutcome(op, result, options = {}) {
212
+ if (result.ok) return "committed";
213
+ const { code, meta } = result.error;
214
+ if (op === "delete" && code === ErrorCodes.KV_NOT_FOUND) {
215
+ return options.conditional === true ? "failed" : "committed";
216
+ }
217
+ if (meta?.requestMayHaveDispatched === false) return "failed";
218
+ if (DEFINITIVE_FAILURES.has(code)) return "failed";
219
+ if (meta?.outcome === "batch-unconfirmed") return "ambiguous";
220
+ const status = typeof meta?.status === "number" ? meta.status : void 0;
221
+ if (status !== void 0 && status >= 400 && status < 500) return "failed";
222
+ return "ambiguous";
223
+ }
224
+
225
+ // src/kv/replication/pendingWrites.ts
226
+ function emptyPendingState(identity) {
227
+ return { v: 2, identity: { ...identity }, committedEpoch: 0, seq: 0, records: [] };
228
+ }
229
+ function begin(s, opId, keys, at) {
230
+ const records = keys.map(({ key, op }) => ({ opId, seq: ++s.seq, key, op, state: "in_flight", epoch: null, at, settledAt: null }));
231
+ s.records.push(...records);
232
+ return records;
233
+ }
234
+ function settle(s, opId, outcome, at, code) {
235
+ if (outcome === "failed") {
236
+ s.records = s.records.filter((r) => r.opId !== opId);
237
+ return;
238
+ }
239
+ const own = s.records.filter((r) => r.opId === opId && r.state === "in_flight");
240
+ if (outcome === "committed" && own.length > 0) {
241
+ const epoch = ++s.committedEpoch;
242
+ for (const r of own) {
243
+ r.state = "committed";
244
+ r.epoch = epoch;
245
+ r.settledAt = at;
246
+ }
247
+ return;
248
+ }
249
+ for (const r of own) {
250
+ r.state = "ambiguous";
251
+ r.settledAt = at;
252
+ if (code) r.code = code;
253
+ }
254
+ }
255
+ function afterSync(s, prefix, syncStartEpoch) {
256
+ const before = s.records.length;
257
+ s.records = s.records.filter((r) => !(r.state === "committed" && r.epoch !== null && r.epoch <= syncStartEpoch && kvPrefixCovers(prefix, r.key)));
258
+ return before - s.records.length;
259
+ }
260
+ function clearPrefix(s, prefix) {
261
+ const before = s.records.length;
262
+ s.records = s.records.filter((r) => !kvPrefixCovers(prefix, r.key));
263
+ return before - s.records.length;
264
+ }
265
+ function clearPending(s, now) {
266
+ const before = s.records.length;
267
+ s.records = s.records.filter((r) => r.state === "committed" || r.state === "in_flight" && now - Date.parse(r.at) <= 10 * 6e4);
268
+ return before - s.records.length;
269
+ }
270
+ function pinnedKeys(s, prefix, now, cap = 100) {
271
+ return s.records.filter((r) => kvPrefixCovers(prefix, r.key) && (r.state === "in_flight" || r.state === "ambiguous")).sort((a, b) => a.seq - b.seq).slice(0, cap).map((r) => ({ key: r.key, state: r.state, op: r.op, since: r.at, ...r.code ? { code: r.code } : {}, likelyOrphaned: r.state === "in_flight" && now - Date.parse(r.at) > 10 * 6e4 }));
272
+ }
273
+
274
+ // src/kv/replication/localResponse.ts
275
+ function parseLocalValue(bytes, contentType, raw, binary) {
276
+ if (binary) return bytes;
277
+ const text = new TextDecoder().decode(bytes);
278
+ if (raw) return text;
279
+ if (contentType?.includes("application/json")) return JSON.parse(text);
280
+ if (contentType?.startsWith("text/")) return text;
281
+ if (!text) return void 0;
282
+ try {
283
+ return JSON.parse(text);
284
+ } catch {
285
+ return text;
286
+ }
287
+ }
288
+ function localGet(result, key, options) {
289
+ if (result.status === "present") {
290
+ if (options.maxResponseBytes !== void 0 && result.value.byteLength > options.maxResponseBytes) {
291
+ return err(serviceError(ErrorCodes.KV_RESPONSE_TOO_LARGE, `KV value at key ${JSON.stringify(key)} exceeds the requested response limit`, "kv", { meta: { status: 413 } }));
292
+ }
293
+ const contentType = Object.entries(result.metadata).find(([name]) => name.toLowerCase() === "content-type")?.[1];
294
+ const headers = new Headers({ etag: result.etag, "content-length": String(result.value.byteLength), "cache-control": "private, no-cache", ...result.metadata, "x-tinycloud-source": "replica" });
295
+ return ok({
296
+ data: parseLocalValue(result.value, contentType, options.raw, options.binary),
297
+ headers: {
298
+ etag: result.etag,
299
+ contentType,
300
+ contentLength: result.value.byteLength,
301
+ get: (name) => headers.get(name)
302
+ }
303
+ });
304
+ }
305
+ if (result.status === "absent" || result.status === "deleted") return err(serviceError(ErrorCodes.KV_NOT_FOUND, `Key not found: ${key}`, "kv"));
306
+ return err(serviceError(ErrorCodes.NETWORK_ERROR, "Local value is not available", "kv"));
307
+ }
308
+
309
+ // src/kv/replication/listLocal.ts
310
+ var encoder = new TextEncoder();
311
+ var decoder = new TextDecoder("utf-8", { fatal: true });
312
+ function utf8Compare(a, b) {
313
+ const x = encoder.encode(a), y = encoder.encode(b);
314
+ for (let i = 0; i < Math.min(x.length, y.length); i++) if (x[i] !== y[i]) return x[i] - y[i];
315
+ return x.length - y.length;
316
+ }
317
+ function encodeTcr1(space, path, last) {
318
+ const bytes = encoder.encode(JSON.stringify({ v: 1, space, path, last }));
319
+ let binary = "";
320
+ for (let offset = 0; offset < bytes.length; offset += 32768) {
321
+ binary += String.fromCharCode(...bytes.subarray(offset, offset + 32768));
322
+ }
323
+ const payload = btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
324
+ return `tcr1.${payload}`;
325
+ }
326
+ function decodeTcr1(cursor) {
327
+ try {
328
+ const encoded = cursor.slice(5).replace(/-/g, "+").replace(/_/g, "/");
329
+ const binary = atob(encoded + "=".repeat((4 - encoded.length % 4) % 4));
330
+ const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
331
+ const x = JSON.parse(decoder.decode(bytes));
332
+ return x?.v === 1 && typeof x.space === "string" && typeof x.path === "string" && typeof x.last === "string" ? x : null;
333
+ } catch {
334
+ return null;
335
+ }
336
+ }
337
+ function cursorRestart() {
338
+ return err(serviceError(ErrorCodes.INVALID_INPUT, "Local list cursor cannot be served; restart without the cursor", "kv", { meta: { replication: "cursor_restart" } }));
339
+ }
340
+ function localList(handle, space, path, limit, cursor, validateMeta) {
341
+ const after = cursor ? decodeTcr1(cursor)?.last : void 0;
342
+ const exact = !path.endsWith("/") && (after === void 0 || utf8Compare(path, after) > 0) ? awaitExact(handle, path, after, validateMeta) : Promise.resolve(false);
343
+ return exact.then(async (hasExact) => {
344
+ const child = path.endsWith("/") ? path : `${path}/`;
345
+ const want = limit === void 0 ? void 0 : limit + 1 - (hasExact ? 1 : 0);
346
+ const children = await handle.list({ prefix: child, after, limit: want });
347
+ validateMeta(children.meta);
348
+ let keys = (hasExact ? [path] : []).concat(children.keys);
349
+ const truncated = limit !== void 0 && keys.length > limit;
350
+ if (truncated) keys = keys.slice(0, limit);
351
+ return { keys, truncated, ...truncated ? { nextCursor: encodeTcr1(space, path, keys[keys.length - 1]) } : {} };
352
+ });
353
+ }
354
+ async function awaitExact(handle, path, after, validateMeta) {
355
+ if (after !== void 0 && utf8Compare(path, after) <= 0) return false;
356
+ const result = await handle.list({ prefix: path, limit: 1 });
357
+ validateMeta(result.meta);
358
+ return result.keys[0] === path;
359
+ }
360
+ function listPathCovered(prefixes, path) {
361
+ const descendant = path.endsWith("/") ? path : `${path}/`;
362
+ const covering = prefixes.filter((prefix) => kvPrefixCovers(prefix, path) && kvPrefixCovers(prefix, descendant));
363
+ return covering.length === 1 ? covering[0] : void 0;
364
+ }
365
+
366
+ // src/kv/replication/events.ts
367
+ function emitEvent(emit, scheduler, event) {
368
+ try {
369
+ emit({ ...event, at: new Date(scheduler.now()).toISOString() });
370
+ } catch {
371
+ }
372
+ }
373
+
374
+ // src/kv/replication/controller.ts
375
+ var CLOSE_TIMEOUT_MS = 3e3;
376
+ var STATUS_REFRESH_TIMEOUT_MS = CLOSE_TIMEOUT_MS;
377
+ var nowDate = (now) => new Date(now).toISOString();
378
+ var errorCode = (e) => typeof e === "object" && e !== null && "code" in e && typeof e.code === "string" ? e.code : "REPLICA_UNAVAILABLE";
379
+ function raceSignal(job, signal) {
380
+ if (signal.aborted) return Promise.reject(signal.reason ?? Object.assign(new Error("KV request was aborted"), { code: ErrorCodes.ABORTED }));
381
+ let remove = () => {
382
+ };
383
+ const aborted = new Promise((_, reject) => {
384
+ const onAbort = () => reject(signal.reason ?? Object.assign(new Error("KV request was aborted"), { code: ErrorCodes.ABORTED }));
385
+ signal.addEventListener("abort", onAbort, { once: true });
386
+ remove = () => signal.removeEventListener("abort", onAbort);
387
+ });
388
+ return Promise.race([job, aborted]).finally(remove);
389
+ }
390
+ function createKVReplication(deps) {
391
+ const { options, mode, storage, identity, session, authority, pending, scheduler } = deps;
392
+ const replicas = /* @__PURE__ */ new Map();
393
+ const statusInspections = /* @__PURE__ */ new Set();
394
+ let isClosed = false;
395
+ let purgeStarted = false;
396
+ let purgeInProgress = false;
397
+ let previous = deps.previous;
398
+ let pinnedStateReported = false;
399
+ const inProcessProof = /* @__PURE__ */ new Set();
400
+ const emit = (event) => emitEvent(deps.emit, scheduler, event);
401
+ const configuredPrefix = (path) => options.prefixes.find((prefix) => kvPrefixCovers(prefix, path));
402
+ function recordRevocation(prefix, error) {
403
+ if (errorCode(error) !== "GRANT_REVOKED") return;
404
+ const state = replicas.get(prefix) ?? {};
405
+ state.authorityRevoked = true;
406
+ state.reason = "grant_revoked";
407
+ state.lastError = "GRANT_REVOKED";
408
+ replicas.set(prefix, state);
409
+ }
410
+ async function open(prefix) {
411
+ if (isClosed || purgeStarted) throw Object.assign(new Error("Replication controller is closed"), { code: "REPLICA_CLOSED" });
412
+ let state = replicas.get(prefix);
413
+ if (state?.handle) return state.handle;
414
+ if (state?.opening) return state.opening;
415
+ state ?? (state = {});
416
+ replicas.set(prefix, state);
417
+ if (state.unsupported) throw Object.assign(new Error("Replication runtime is unsupported"), { code: "RUNTIME_UNSUPPORTED" });
418
+ if (state.retryAt !== void 0 && scheduler.now() < state.retryAt) throw Object.assign(new Error("Replica open is backing off"), { code: state.lastError ?? "REPLICA_UNAVAILABLE" });
419
+ if (requiresSecretsOptIn(session.space, prefix) && !options.allowSecrets) {
420
+ const firstRefusal = state.lastError !== "SECRETS_OPT_IN_REQUIRED";
421
+ state.reason = "replica_unavailable";
422
+ state.lastError = "SECRETS_OPT_IN_REQUIRED";
423
+ if (firstRefusal) emit({ type: "replication.state", space: session.space, replica: prefix, state: "unavailable", code: "SECRETS_OPT_IN_REQUIRED" });
424
+ throw Object.assign(new Error("Replication requires allowSecrets for this space or prefix"), { code: "SECRETS_OPT_IN_REQUIRED" });
425
+ }
426
+ state.opening = (async () => {
427
+ if (previous) {
428
+ await previous;
429
+ previous = void 0;
430
+ }
431
+ const sessionGrant = authority.sessionGrant(prefix);
432
+ const delegated = "refused" in sessionGrant ? void 0 : sessionGrant;
433
+ const plan = authority.sessionOnly ? void 0 : authority.plan(prefix);
434
+ let refusalCode;
435
+ if ("refused" in sessionGrant) refusalCode = sessionGrant.refused;
436
+ if (plan && "refused" in plan) refusalCode ?? (refusalCode = plan.refused);
437
+ let device;
438
+ if (delegated) {
439
+ device = delegated.device;
440
+ state.strategy = "session";
441
+ }
442
+ const spec = { identity, space: session.space, prefix, allowSecrets: options.allowSecrets, ...device ? { device } : {}, ...deps.fetch ? { fetch: deps.fetch } : {} };
443
+ const handle = await storage.open(spec);
444
+ state.handle = handle;
445
+ emit({ type: "replication.state", space: session.space, replica: prefix, state: "opened" });
446
+ const runtimePlan = !authority.sessionOnly && plan && !("refused" in plan) ? plan : void 0;
447
+ let installed = runtimePlan ? await handle.grant() : void 0;
448
+ if (delegated) installed = await handle.installGrant(delegated.ucan);
449
+ else if (runtimePlan) {
450
+ if (!installed || !installed.unconstrained || installed.parentCid !== runtimePlan.parentCid || installed.expiresAt !== null && runtimePlan.expiresAt > installed.expiresAt + 3e5) {
451
+ try {
452
+ const mintAbort = new AbortController();
453
+ state.mintAbort = mintAbort;
454
+ const minted = await authority.mint(handle.deviceDid, prefix, mintAbort.signal);
455
+ installed = await handle.installGrant(minted.ucan);
456
+ state.strategy = "minted";
457
+ } catch (error) {
458
+ if (!installed?.unconstrained) throw error;
459
+ } finally {
460
+ state.mintAbort = void 0;
461
+ }
462
+ }
463
+ if (installed?.unconstrained) {
464
+ state.strategy ?? (state.strategy = "installed");
465
+ state.reason = void 0;
466
+ }
467
+ }
468
+ if (!installed?.unconstrained) {
469
+ state.reason = "grant_missing";
470
+ state.lastError = refusalCode ?? "SESSION_LACKS_SYNC";
471
+ emit({ type: "replication.state", space: session.space, replica: prefix, state: "grant_missing", code: state.lastError });
472
+ } else emit({ type: "replication.state", space: session.space, replica: prefix, state: "grant_installed", strategy: state.strategy });
473
+ if (isClosed || purgeStarted) {
474
+ await handle.close();
475
+ throw Object.assign(new Error("Replication controller is closed"), { code: "REPLICA_CLOSED" });
476
+ }
477
+ if (mode === "background" && state.reason !== "grant_missing") {
478
+ void syncPrefix(prefix, "start").catch(() => void 0);
479
+ schedule(prefix);
480
+ }
481
+ return handle;
482
+ })();
483
+ try {
484
+ return await state.opening;
485
+ } catch (error) {
486
+ const code = errorCode(error);
487
+ recordRevocation(prefix, error);
488
+ if (code !== "GRANT_REVOKED") state.reason = "replica_unavailable";
489
+ state.lastError = code;
490
+ state.failures = (state.failures ?? 0) + 1;
491
+ if (code === "RUNTIME_UNSUPPORTED") state.unsupported = true;
492
+ else if (code !== "REPLICA_CLOSED") state.retryAt = scheduler.now() + Math.min(3e5, 5e3 * 2 ** Math.min(state.failures - 1, 6));
493
+ state.handle?.close().catch(() => void 0);
494
+ state.handle = void 0;
495
+ throw error;
496
+ } finally {
497
+ state.opening = void 0;
498
+ }
499
+ }
500
+ function schedule(prefix) {
501
+ const state = replicas.get(prefix);
502
+ if (!state || mode !== "background" || isClosed || purgeStarted) return;
503
+ state.timer = scheduler.setTimeout(() => {
504
+ state.timer = void 0;
505
+ void syncPrefix(prefix, "interval").catch(() => void 0).finally(() => schedule(prefix));
506
+ }, options.syncIntervalMs);
507
+ }
508
+ async function syncPrefix(prefix, trigger, signal, context) {
509
+ const handle = await open(prefix);
510
+ const state = replicas.get(prefix);
511
+ if (state.sync) return state.sync;
512
+ const abort = new AbortController();
513
+ const syncContext = context ?? {};
514
+ state.syncContext = syncContext;
515
+ state.abort = abort;
516
+ const abortFromCaller = () => {
517
+ syncContext.abortCause ?? (syncContext.abortCause = "caller");
518
+ abort.abort(signal?.reason);
519
+ };
520
+ signal?.addEventListener("abort", abortFromCaller, { once: true });
521
+ const started = scheduler.now();
522
+ let job;
523
+ job = (async () => {
524
+ try {
525
+ const syncStartEpoch = (await pending.read()).committedEpoch;
526
+ const result = await handle.sync({ signal: abort.signal, syncStartEpoch });
527
+ if (result.status === "busy") {
528
+ emit({ type: "replication.sync", space: session.space, replica: prefix, trigger, outcome: "busy", durationMs: scheduler.now() - started, lagMs: null });
529
+ return result;
530
+ }
531
+ inProcessProof.add(prefix);
532
+ const snapshot = await pending.read();
533
+ const clearedCandidates = snapshot.records.some((r) => r.state === "committed" && r.epoch !== null && r.epoch <= syncStartEpoch && kvPrefixCovers(prefix, r.key));
534
+ const cleared = clearedCandidates ? await pending.update((s) => afterSync(s, prefix, syncStartEpoch)) : 0;
535
+ emit({ type: "replication.sync", space: session.space, replica: prefix, trigger, outcome: "ok", durationMs: scheduler.now() - started, lagMs: null, pendingCleared: cleared, pages: result.pages, changes: result.changes, deleted: result.deleted, fetched: result.fetched, contentMissing: result.contentMissing, coverage: result.coverage });
536
+ return result;
537
+ } catch (error) {
538
+ const code = errorCode(error);
539
+ state.lastError = code;
540
+ recordRevocation(prefix, error);
541
+ emit({ type: "replication.sync", space: session.space, replica: prefix, trigger, outcome: abort.signal.aborted ? "aborted" : "error", class: code === "NETWORK_ERROR" || code === "TIMEOUT" ? "offline" : "node", code, durationMs: scheduler.now() - started, lagMs: null });
542
+ throw error;
543
+ } finally {
544
+ signal?.removeEventListener("abort", abortFromCaller);
545
+ state.abort = void 0;
546
+ }
547
+ })();
548
+ state.sync = job;
549
+ try {
550
+ return await job;
551
+ } finally {
552
+ if (state.sync === job) {
553
+ state.sync = void 0;
554
+ state.syncContext = void 0;
555
+ }
556
+ }
557
+ }
558
+ async function drainForegroundSync(prefix, sync2) {
559
+ let cancel = () => {
560
+ };
561
+ const settled = sync2.then(
562
+ (result) => ({ settled: true, result }),
563
+ (error) => ({ settled: true, error })
564
+ );
565
+ const timeout = new Promise((resolve) => {
566
+ cancel = scheduler.setTimeout(() => resolve({ settled: false }), CLOSE_TIMEOUT_MS);
567
+ });
568
+ try {
569
+ const drained = await Promise.race([settled, timeout]);
570
+ if (!drained.settled) emit({ type: "replication.sync", space: session.space, replica: prefix, trigger: "stale_read", outcome: "aborted", code: "DRAIN_TIMEOUT", durationMs: CLOSE_TIMEOUT_MS, lagMs: null });
571
+ return drained;
572
+ } finally {
573
+ cancel();
574
+ }
575
+ }
576
+ function validateReadMeta(meta) {
577
+ if (meta.coverage !== "complete") throw Object.assign(new Error("Replica coverage changed before the local read"), { code: "COVERAGE_INCOMPLETE" });
578
+ if (meta.authority !== "valid") {
579
+ const code = meta.authority === "expired" ? "GRANT_EXPIRED" : meta.authority === "revoked" ? "GRANT_REVOKED" : "GRANT_NOT_YET_VALID";
580
+ throw Object.assign(new Error("Replica authority changed before the local read"), { code });
581
+ }
582
+ }
583
+ function reasonForReadMetaCode(code) {
584
+ if (code === "COVERAGE_INCOMPLETE") return "coverage_incomplete";
585
+ if (code === "GRANT_EXPIRED") return "grant_expired";
586
+ if (code === "GRANT_REVOKED") return "grant_revoked";
587
+ if (code === "GRANT_NOT_YET_VALID") return "grant_not_yet_valid";
588
+ return "replica_error";
589
+ }
590
+ async function statusAfterDrain(handle, signal) {
591
+ let cancel = () => {
592
+ };
593
+ const timeout = new Promise((_resolve, reject) => {
594
+ cancel = scheduler.setTimeout(
595
+ () => reject(Object.assign(new Error("Replica status refresh timed out"), { code: "STATUS_TIMEOUT" })),
596
+ STATUS_REFRESH_TIMEOUT_MS
597
+ );
598
+ });
599
+ try {
600
+ return await raceSignal(Promise.race([handle.status(), timeout]), signal);
601
+ } finally {
602
+ cancel();
603
+ }
604
+ }
605
+ function staleSyncOutcome(outcome, timedOut, currentStatus) {
606
+ if ("error" in outcome) {
607
+ const code = errorCode(outcome.error);
608
+ if (code === "NETWORK_ERROR" || timedOut && (code === "ABORTED" || code === "TIMEOUT")) return { status: currentStatus, syncedBeforeRead: false, syncError: code === "NETWORK_ERROR" ? code : "TIMEOUT" };
609
+ return { status: currentStatus, syncedBeforeRead: false, syncError: code, failure: "error" };
610
+ }
611
+ if (outcome.result.status === "busy") return { status: currentStatus, syncedBeforeRead: false, failure: "busy" };
612
+ return { status: currentStatus, syncedBeforeRead: true };
613
+ }
614
+ async function freshness(prefix, handle, signal) {
615
+ let currentStatus = await handle.status();
616
+ const syncedAt = currentStatus.lastSyncAt === null ? null : Date.parse(currentStatus.lastSyncAt);
617
+ const stale = syncedAt === null || !Number.isFinite(syncedAt) || scheduler.now() - syncedAt > options.maxStalenessMs;
618
+ let snapshot;
619
+ try {
620
+ snapshot = await pending.read();
621
+ } catch (error) {
622
+ return { status: currentStatus, syncedBeforeRead: false, syncError: errorCode(error), failure: "error" };
623
+ }
624
+ const behind = currentStatus.syncedThroughEpoch < snapshot.committedEpoch;
625
+ const unproven = !pending.durable && !inProcessProof.has(prefix);
626
+ if ((stale || behind || unproven) && !signal.aborted) {
627
+ const syncController = mode === "foreground" ? new AbortController() : void 0;
628
+ const syncState = replicas.get(prefix);
629
+ const ownsSync = !syncState?.sync;
630
+ const syncContext = syncState?.syncContext ?? {};
631
+ const abortFromCaller = () => {
632
+ if (ownsSync) syncContext.abortCause ?? (syncContext.abortCause = "caller");
633
+ syncController?.abort(signal.reason);
634
+ };
635
+ if (syncController) signal.addEventListener("abort", abortFromCaller, { once: true });
636
+ let cancelTimeout = () => {
637
+ };
638
+ const timeout = new Promise((resolve) => {
639
+ cancelTimeout = scheduler.setTimeout(() => resolve({ timeout: true }), options.staleSyncTimeoutMs);
640
+ });
641
+ let cancelAbort = () => {
642
+ };
643
+ const callerAbort = new Promise((resolve) => {
644
+ if (signal.aborted) resolve({ callerAborted: true });
645
+ else {
646
+ const listener = () => resolve({ callerAborted: true });
647
+ signal.addEventListener("abort", listener, { once: true });
648
+ cancelAbort = () => signal.removeEventListener("abort", listener);
649
+ }
650
+ });
651
+ const sync2 = syncPrefix(prefix, "stale_read", syncController?.signal, syncContext);
652
+ const outcome = await Promise.race([
653
+ sync2.then((result) => ({ result }), (error) => ({ error })),
654
+ timeout,
655
+ callerAbort
656
+ ]);
657
+ cancelTimeout();
658
+ cancelAbort();
659
+ signal.removeEventListener("abort", abortFromCaller);
660
+ if ("callerAborted" in outcome) {
661
+ if (syncController && ownsSync) {
662
+ syncContext.abortCause ?? (syncContext.abortCause = "caller");
663
+ syncController.abort(signal.reason);
664
+ await drainForegroundSync(prefix, sync2);
665
+ }
666
+ return { status: currentStatus, syncedBeforeRead: false, syncError: cancellationCode(signal) };
667
+ }
668
+ if ("timeout" in outcome) {
669
+ if (ownsSync) {
670
+ syncContext.abortCause ?? (syncContext.abortCause = "timeout");
671
+ syncController?.abort(Object.assign(new Error("Stale-read sync timed out"), { code: "TIMEOUT" }));
672
+ }
673
+ const drained = syncController ? await drainForegroundSync(prefix, sync2) : void 0;
674
+ if (signal.aborted) return { status: currentStatus, syncedBeforeRead: false, syncError: cancellationCode(signal) };
675
+ if (drained && !drained.settled) return { status: currentStatus, syncedBeforeRead: false, syncError: "DRAIN_TIMEOUT", failure: "error" };
676
+ try {
677
+ currentStatus = await statusAfterDrain(handle, signal);
678
+ } catch (error) {
679
+ if (signal.aborted) return { status: currentStatus, syncedBeforeRead: false, syncError: cancellationCode(signal), failure: "error" };
680
+ return { status: currentStatus, syncedBeforeRead: false, syncError: errorCode(error), failure: "error" };
681
+ }
682
+ const timeoutAbort = syncContext.abortCause === "timeout";
683
+ return staleSyncOutcome(
684
+ drained?.settled ? "result" in drained ? { result: drained.result } : { error: drained.error } : { error: Object.assign(new Error("Stale-read sync timed out"), { code: "TIMEOUT" }) },
685
+ timeoutAbort || !syncController,
686
+ currentStatus
687
+ );
688
+ }
689
+ if ("error" in outcome) return staleSyncOutcome({ error: outcome.error }, false, currentStatus);
690
+ if (outcome.result.status === "busy") return { status: currentStatus, syncedBeforeRead: false, failure: "busy" };
691
+ currentStatus = await handle.status();
692
+ return { status: currentStatus, syncedBeforeRead: true };
693
+ }
694
+ return { status: currentStatus, syncedBeforeRead: false };
695
+ }
696
+ function readEvent(op, path, prefix, source, reason, outcome, started, extra = {}) {
697
+ emit({ type: "replication.read", op, space: session.space, key: path, replica: prefix, source, reason, outcome, latencyMs: scheduler.now() - started, stalenessMs: null, coverage: null, authority: null, ...extra });
698
+ }
699
+ async function verifyGet(r, local, reason, started, extra) {
700
+ const prefix = configuredPrefix(r.path) ?? null;
701
+ const localFound = local.ok;
702
+ const localNotFound = !local.ok && local.error.code === ErrorCodes.KV_NOT_FOUND;
703
+ const outcome = localFound ? "found" : localNotFound ? "not_found" : "error";
704
+ try {
705
+ const network = await raceSignal(r.network(), r.signal);
706
+ if (r.signal.aborted) {
707
+ readEvent("get", r.path, prefix, "none", "aborted", "error", started, { ...extra, verify: "aborted" });
708
+ return abortedResult(r.signal);
709
+ }
710
+ if (!network.ok && network.error.code !== ErrorCodes.KV_NOT_FOUND) {
711
+ readEvent("get", r.path, prefix, "replica", reason, outcome, started, { ...extra, code: network.error.code, verify: "error" });
712
+ return local;
713
+ }
714
+ const networkFound = network.ok;
715
+ const networkNotFound = !network.ok && network.error.code === ErrorCodes.KV_NOT_FOUND;
716
+ const localEtag = local.ok ? local.data.headers.etag ?? null : null;
717
+ const networkEtag = network.ok ? network.data.headers.etag ?? null : null;
718
+ const matches = localFound && networkFound ? localEtag === networkEtag : localNotFound && networkNotFound;
719
+ const verify = matches ? "match" : "diverged";
720
+ if (!matches) {
721
+ emit({
722
+ type: "replication.divergence",
723
+ op: "get",
724
+ space: r.space,
725
+ key: r.path,
726
+ replica: prefix ?? "",
727
+ kind: localFound && networkFound ? "value" : localNotFound ? "missing_local" : "extra_local",
728
+ localEtag,
729
+ networkEtag,
730
+ stalenessMs: typeof extra.stalenessMs === "number" ? extra.stalenessMs : null
731
+ });
732
+ }
733
+ readEvent("get", r.path, prefix, "replica", reason, outcome, started, { ...extra, verify });
734
+ return local;
735
+ } catch (error) {
736
+ if (r.signal.aborted) {
737
+ readEvent("get", r.path, prefix, "none", "aborted", "error", started, { ...extra, verify: "aborted" });
738
+ return abortedResult(r.signal);
739
+ }
740
+ readEvent("get", r.path, prefix, "replica", reason, outcome, started, { ...extra, code: errorCode(error), verify: "error" });
741
+ return local;
742
+ }
743
+ }
744
+ async function verifyList(r, local, started, extra) {
745
+ const prefix = listPathCovered(options.prefixes, r.listPath) ?? null;
746
+ try {
747
+ const network = await raceSignal(r.network(), r.signal);
748
+ if (r.signal.aborted) {
749
+ readEvent("list", r.listPath, prefix, "none", "aborted", "error", started, { ...extra, verify: "aborted" });
750
+ return abortedResult(r.signal);
751
+ }
752
+ if (!network.ok) {
753
+ readEvent("list", r.listPath, prefix, "replica", "hit", "found", started, { ...extra, code: network.error.code, verify: "error" });
754
+ return ok(local);
755
+ }
756
+ const localSet = new Set(local.keys);
757
+ const networkSet = new Set(network.data.keys);
758
+ const localOnly = local.keys.filter((key) => !networkSet.has(key));
759
+ const networkOnly = network.data.keys.filter((key) => !localSet.has(key));
760
+ const sameKeys = localOnly.length === 0 && networkOnly.length === 0;
761
+ const sameOrder = sameKeys && local.keys.every((key, index) => key === network.data.keys[index]);
762
+ const verify = sameOrder ? "match" : "diverged";
763
+ if (!sameKeys || !sameOrder) {
764
+ emit({
765
+ type: "replication.divergence",
766
+ op: "list",
767
+ space: r.space,
768
+ key: r.listPath,
769
+ replica: prefix ?? "",
770
+ kind: sameKeys ? "order" : "keys",
771
+ ...!sameKeys ? { localOnly, networkOnly } : {},
772
+ stalenessMs: typeof extra.stalenessMs === "number" ? extra.stalenessMs : null
773
+ });
774
+ }
775
+ readEvent("list", r.listPath, prefix, "replica", "hit", "found", started, { ...extra, verify });
776
+ return ok(local);
777
+ } catch (error) {
778
+ if (r.signal.aborted) {
779
+ readEvent("list", r.listPath, prefix, "none", "aborted", "error", started, { ...extra, verify: "aborted" });
780
+ return abortedResult(r.signal);
781
+ }
782
+ readEvent("list", r.listPath, prefix, "replica", "hit", "found", started, { ...extra, code: errorCode(error), verify: "error" });
783
+ return ok(local);
784
+ }
785
+ }
786
+ async function reportExistingPins() {
787
+ if (pinnedStateReported) return;
788
+ pinnedStateReported = true;
789
+ try {
790
+ const records = await pending.read();
791
+ const pins = pinnedKeys(records, "", scheduler.now());
792
+ if (pins.length > 0) emit({ type: "replication.state", space: session.space, state: "pinned", count: pins.length, keys: pins.map((pin) => pin.key) });
793
+ } catch {
794
+ emit({ type: "replication.state", space: session.space, state: "pending_store_error", code: "STORAGE_ERROR" });
795
+ }
796
+ }
797
+ async function get(r) {
798
+ const started = scheduler.now();
799
+ const prefix = configuredPrefix(r.path);
800
+ if (r.options?.source === "network") {
801
+ let outcome = "error";
802
+ try {
803
+ const value = await r.network();
804
+ outcome = value.ok ? "found" : value.error.code === ErrorCodes.KV_NOT_FOUND ? "not_found" : "error";
805
+ return value;
806
+ } finally {
807
+ observeNetworkRequested({ op: "get", space: r.space, path: r.path, reason: "NETWORK_REQUESTED", outcome, latencyMs: scheduler.now() - started });
808
+ }
809
+ }
810
+ if (!prefix) {
811
+ const value = await r.network();
812
+ readEvent("get", r.path, null, "network", "not_covered", value.ok ? "found" : value.error.code === ErrorCodes.KV_NOT_FOUND ? "not_found" : "error", started);
813
+ return value;
814
+ }
815
+ try {
816
+ await raceSignal(reportExistingPins(), r.signal);
817
+ } catch {
818
+ if (r.signal.aborted) return abortedResult(r.signal);
819
+ }
820
+ let handle;
821
+ try {
822
+ handle = await raceSignal(open(prefix), r.signal);
823
+ } catch (error) {
824
+ if (r.signal.aborted) return abortedResult(r.signal);
825
+ const value = await r.network();
826
+ readEvent("get", r.path, prefix, "network", errorCode(error) === "RUNTIME_UNSUPPORTED" ? "runtime_unsupported" : "replica_unavailable", value.ok ? "found" : "error", started, { code: errorCode(error) });
827
+ return value;
828
+ }
829
+ if (replicas.get(prefix)?.reason === "grant_missing") {
830
+ const value = await r.network();
831
+ readEvent("get", r.path, prefix, "network", "grant_missing", value.ok ? "found" : "error", started, { code: replicas.get(prefix)?.lastError });
832
+ return value;
833
+ }
834
+ let fresh;
835
+ try {
836
+ fresh = await freshness(prefix, handle, r.signal);
837
+ } catch (error) {
838
+ recordRevocation(prefix, error);
839
+ if (r.signal.aborted) return abortedResult(r.signal);
840
+ const value = await r.network();
841
+ readEvent("get", r.path, prefix, "network", "replica_error", value.ok ? "found" : "error", started, { code: errorCode(error) });
842
+ return value;
843
+ }
844
+ if (r.signal.aborted) return abortedResult(r.signal);
845
+ if (fresh.failure) {
846
+ const value = await r.network();
847
+ readEvent("get", r.path, prefix, "network", fresh.failure === "busy" ? "stale" : "stale", value.ok ? "found" : "error", started, { code: fresh.failure === "busy" ? "REPLICA_BUSY" : fresh.syncError, syncedBeforeRead: fresh.syncedBeforeRead });
848
+ return value;
849
+ }
850
+ let pendingState;
851
+ try {
852
+ pendingState = await raceSignal(pending.read(), r.signal);
853
+ } catch (error) {
854
+ if (r.signal.aborted) return abortedResult(r.signal);
855
+ const value = await r.network();
856
+ readEvent("get", r.path, prefix, "network", "replica_unavailable", value.ok ? "found" : "error", started, { code: errorCode(error) });
857
+ return value;
858
+ }
859
+ const record = pendingState.records.find((x) => x.key === r.path);
860
+ if (record) {
861
+ const value = await r.network();
862
+ readEvent("get", r.path, prefix, "network", "pending_write", value.ok ? "found" : value.error.code === ErrorCodes.KV_NOT_FOUND ? "not_found" : "error", started, { pendingState: record.state, syncedBeforeRead: fresh.syncedBeforeRead });
863
+ return value;
864
+ }
865
+ if (!pending.durable && !inProcessProof.has(prefix)) {
866
+ const value = await r.network();
867
+ readEvent("get", r.path, prefix, "network", "REPLICA_UNPROVEN_SINCE_START", value.ok ? "found" : "error", started);
868
+ return value;
869
+ }
870
+ if (fresh.status.syncedThroughEpoch < pendingState.committedEpoch) {
871
+ const value = await r.network();
872
+ readEvent("get", r.path, prefix, "network", "REPLICA_BEHIND_OWN_WRITES", value.ok ? "found" : "error", started);
873
+ return value;
874
+ }
875
+ if (fresh.status.authority.state !== "valid") {
876
+ const value = await r.network();
877
+ readEvent("get", r.path, prefix, "network", fresh.status.authority.state === "expired" ? "grant_expired" : fresh.status.authority.state === "revoked" ? "grant_revoked" : "grant_not_yet_valid", value.ok ? "found" : "error", started);
878
+ return value;
879
+ }
880
+ if (fresh.status.coverage !== "complete") {
881
+ const value = await r.network();
882
+ readEvent("get", r.path, prefix, "network", "coverage_incomplete", value.ok ? "found" : "error", started);
883
+ return value;
884
+ }
885
+ try {
886
+ const local = await raceSignal(handle.get(r.path), r.signal);
887
+ try {
888
+ validateReadMeta(local.meta);
889
+ } catch (error) {
890
+ const code = errorCode(error);
891
+ recordRevocation(prefix, error);
892
+ const value = await r.network();
893
+ readEvent("get", r.path, prefix, "network", reasonForReadMetaCode(code), value.ok ? "found" : "error", started, { code });
894
+ return value;
895
+ }
896
+ if (local.status === "coverage_incomplete" || local.status === "not_covered" || local.status === "content_missing") {
897
+ const value = await r.network();
898
+ readEvent("get", r.path, prefix, "network", local.status === "content_missing" ? "content_missing" : local.status === "coverage_incomplete" ? "coverage_incomplete" : "not_covered", value.ok ? "found" : "error", started);
899
+ return value;
900
+ }
901
+ if (local.status === "present" && r.options?.maxResponseBytes !== void 0 && local.value.byteLength > r.options.maxResponseBytes) {
902
+ const value = await r.network();
903
+ readEvent("get", r.path, prefix, "network", "unsupported_option", value.ok ? "found" : "error", started);
904
+ return value;
905
+ }
906
+ const result = localGet(local, r.path, r.options ?? {});
907
+ const reason = local.status === "present" ? "hit" : local.status;
908
+ const syncedAt = fresh.status.lastSyncAt === null ? null : Date.parse(fresh.status.lastSyncAt);
909
+ const event = {
910
+ syncedBeforeRead: fresh.syncedBeforeRead,
911
+ coverage: local.meta.coverage,
912
+ authority: local.meta.authority,
913
+ stalenessMs: syncedAt === null ? null : scheduler.now() - syncedAt,
914
+ ...fresh.syncError ? { syncError: fresh.syncError } : {}
915
+ };
916
+ if (options.verify) return await verifyGet(r, result, reason, started, event);
917
+ readEvent("get", r.path, prefix, "replica", reason, result.ok ? "found" : result.error.code === ErrorCodes.KV_NOT_FOUND ? "not_found" : "error", started, event);
918
+ return result;
919
+ } catch (error) {
920
+ recordRevocation(prefix, error);
921
+ if (r.signal.aborted) return abortedResult(r.signal);
922
+ const value = await r.network();
923
+ readEvent("get", r.path, prefix, "network", "replica_error", value.ok ? "found" : "error", started, { code: errorCode(error) });
924
+ return value;
925
+ }
926
+ }
927
+ async function list(r) {
928
+ const started = scheduler.now();
929
+ if (r.options?.source === "network") {
930
+ let outcome = "error";
931
+ try {
932
+ const result = await r.network();
933
+ outcome = result.ok ? "found" : "error";
934
+ return result;
935
+ } finally {
936
+ observeNetworkRequested({ op: "list", space: r.space, path: r.listPath, reason: "NETWORK_REQUESTED", outcome, latencyMs: scheduler.now() - started });
937
+ }
938
+ }
939
+ const cursor = r.options?.cursor;
940
+ const decoded = cursor?.startsWith("tcr1.") ? decodeTcr1(cursor) : null;
941
+ if (cursor && cursor.startsWith("tcr1.") && !decoded) {
942
+ readEvent("list", r.listPath, null, "none", "cursor_restart", "error", started);
943
+ return cursorRestart();
944
+ }
945
+ if (cursor && !cursor.startsWith("tcr1.")) {
946
+ const result = await r.network();
947
+ readEvent("list", r.listPath, null, "network", "network_cursor", result.ok ? "found" : "error", started);
948
+ return result;
949
+ }
950
+ try {
951
+ await raceSignal(reportExistingPins(), r.signal);
952
+ } catch {
953
+ if (r.signal.aborted) return abortedResult(r.signal);
954
+ }
955
+ const prefix = listPathCovered(options.prefixes, r.listPath);
956
+ const restart = () => {
957
+ readEvent("list", r.listPath, prefix ?? null, "none", "cursor_restart", "error", started);
958
+ return cursorRestart();
959
+ };
960
+ if (cursor && decoded && (decoded.space !== r.space || decoded.path !== r.listPath)) return restart();
961
+ if (!prefix || !r.listPath || r.options?.raw) {
962
+ if (cursor && decoded) return restart();
963
+ const result = await r.network();
964
+ readEvent("list", r.listPath, prefix ?? null, "network", prefix ? "unsupported_option" : "not_covered", result.ok ? "found" : "error", started);
965
+ return result;
966
+ }
967
+ let handle;
968
+ try {
969
+ handle = await raceSignal(open(prefix), r.signal);
970
+ } catch (error) {
971
+ if (r.signal.aborted) return abortedResult(r.signal);
972
+ if (cursor && decoded) return restart();
973
+ const result = await r.network();
974
+ readEvent("list", r.listPath, prefix, "network", errorCode(error) === "RUNTIME_UNSUPPORTED" ? "runtime_unsupported" : "replica_unavailable", result.ok ? "found" : "error", started, { code: errorCode(error) });
975
+ return result;
976
+ }
977
+ if (replicas.get(prefix)?.reason === "grant_missing") {
978
+ if (cursor && decoded) return restart();
979
+ const result = await r.network();
980
+ readEvent("list", r.listPath, prefix, "network", "grant_missing", result.ok ? "found" : "error", started);
981
+ return result;
982
+ }
983
+ let fresh;
984
+ try {
985
+ fresh = await freshness(prefix, handle, r.signal);
986
+ } catch (error) {
987
+ recordRevocation(prefix, error);
988
+ if (r.signal.aborted) return abortedResult(r.signal);
989
+ if (cursor && decoded) return restart();
990
+ const result = await r.network();
991
+ readEvent("list", r.listPath, prefix, "network", "replica_error", result.ok ? "found" : "error", started, { code: errorCode(error) });
992
+ return result;
993
+ }
994
+ if (r.signal.aborted) return abortedResult(r.signal);
995
+ let state;
996
+ try {
997
+ state = await raceSignal(pending.read(), r.signal);
998
+ } catch (error) {
999
+ if (r.signal.aborted) return abortedResult(r.signal);
1000
+ if (cursor && decoded) return restart();
1001
+ const result = await r.network();
1002
+ readEvent("list", r.listPath, prefix, "network", "replica_unavailable", result.ok ? "found" : "error", started, { code: errorCode(error) });
1003
+ return result;
1004
+ }
1005
+ const pendingRange = state.records.find((record) => listRangeCovers(r.listPath, record.key) && (!decoded || utf8Compare(record.key, decoded.last) > 0));
1006
+ const behind = fresh.status.syncedThroughEpoch < state.committedEpoch;
1007
+ if (cursor && decoded && (fresh.failure || pendingRange || behind || fresh.status.coverage !== "complete" || fresh.status.authority.state !== "valid" || !pending.durable && !inProcessProof.has(prefix))) return restart();
1008
+ if (behind) {
1009
+ const result = await r.network();
1010
+ readEvent("list", r.listPath, prefix, "network", "REPLICA_BEHIND_OWN_WRITES", result.ok ? "found" : "error", started);
1011
+ return result;
1012
+ }
1013
+ if (fresh.failure) {
1014
+ const result = await r.network();
1015
+ readEvent("list", r.listPath, prefix, "network", "stale", result.ok ? "found" : "error", started, { code: fresh.failure === "busy" ? "REPLICA_BUSY" : fresh.syncError });
1016
+ return result;
1017
+ }
1018
+ if (pendingRange) {
1019
+ const result = await r.network();
1020
+ readEvent("list", r.listPath, prefix, "network", "pending_write", result.ok ? "found" : "error", started, { pendingState: pendingRange.state });
1021
+ return result;
1022
+ }
1023
+ if (!pending.durable && !inProcessProof.has(prefix)) {
1024
+ const result = await r.network();
1025
+ readEvent("list", r.listPath, prefix, "network", "REPLICA_UNPROVEN_SINCE_START", result.ok ? "found" : "error", started);
1026
+ return result;
1027
+ }
1028
+ if (fresh.status.coverage !== "complete" || fresh.status.authority.state !== "valid") {
1029
+ const result = await r.network();
1030
+ readEvent("list", r.listPath, prefix, "network", fresh.status.coverage !== "complete" ? "coverage_incomplete" : fresh.status.authority.state === "expired" ? "grant_expired" : fresh.status.authority.state === "revoked" ? "grant_revoked" : "grant_not_yet_valid", result.ok ? "found" : "error", started);
1031
+ return result;
1032
+ }
1033
+ try {
1034
+ const signalHandle = { list: (o) => raceSignal(handle.list(o), r.signal) };
1035
+ const page = await raceSignal(localList(signalHandle, r.space, r.listPath, r.options?.limit, cursor, validateReadMeta), r.signal);
1036
+ const event = {
1037
+ count: page.keys.length,
1038
+ syncedBeforeRead: fresh.syncedBeforeRead,
1039
+ stalenessMs: fresh.status.lastSyncAt === null ? null : scheduler.now() - Date.parse(fresh.status.lastSyncAt),
1040
+ ...fresh.syncError ? { syncError: fresh.syncError } : {}
1041
+ };
1042
+ if (options.verify && !cursor) return await verifyList(r, page, started, event);
1043
+ readEvent("list", r.listPath, prefix, "replica", "hit", "found", started, event);
1044
+ return ok(page);
1045
+ } catch (error) {
1046
+ if (r.signal.aborted) return abortedResult(r.signal);
1047
+ const code = errorCode(error);
1048
+ recordRevocation(prefix, error);
1049
+ if (cursor && decoded) return restart();
1050
+ const reason = reasonForReadMetaCode(code);
1051
+ const result = await r.network();
1052
+ readEvent("list", r.listPath, prefix, "network", reason, result.ok ? "found" : "error", started, { code });
1053
+ return result;
1054
+ }
1055
+ }
1056
+ async function write(r) {
1057
+ const covered = r.entries.flatMap((entry) => {
1058
+ const prefix = configuredPrefix(entry.path);
1059
+ return prefix ? [{ key: entry.path, op: r.op === "delete" ? "delete" : "put" }] : [];
1060
+ });
1061
+ if (covered.length === 0) return r.network();
1062
+ await reportExistingPins();
1063
+ const opId = globalThis.crypto?.randomUUID?.() ?? `${scheduler.now()}-${Math.random()}`;
1064
+ try {
1065
+ await pending.update((s) => begin(s, opId, covered, nowDate(scheduler.now())));
1066
+ } catch (error) {
1067
+ return err(serviceError(ErrorCodes.KV_WRITE_FAILED, "Unable to persist pending KV write; request was not dispatched", "kv", { cause: error instanceof Error ? error : void 0, meta: { replication: "pending_store_unavailable", requestDispatched: false } }));
1068
+ }
1069
+ const started = scheduler.now();
1070
+ let result;
1071
+ try {
1072
+ result = await r.network();
1073
+ } catch (error) {
1074
+ const wrapped = wrapError("kv", error);
1075
+ result = err({
1076
+ ...wrapped,
1077
+ meta: { ...wrapped.meta, requestMayHaveDispatched: true }
1078
+ });
1079
+ }
1080
+ const outcome = classifyWriteOutcome(r.op, result, { conditional: r.conditional === true });
1081
+ let settleError;
1082
+ try {
1083
+ await pending.update((s) => settle(s, opId, outcome, nowDate(scheduler.now()), result.ok ? void 0 : result.error.code));
1084
+ } catch (error) {
1085
+ settleError = error;
1086
+ }
1087
+ emit({ type: "replication.write", space: r.space, op: r.op, keys: covered.map((x) => x.key), outcome, ...!result.ok ? { code: result.error.code } : {}, latencyMs: scheduler.now() - started });
1088
+ const state = await pending.read().catch(() => void 0);
1089
+ const pins = state ? pinnedKeys(state, "", scheduler.now()) : [];
1090
+ if (outcome === "ambiguous") emit({ type: "replication.state", space: r.space, state: "pinned", count: pins.length, keys: pins.map((p) => p.key) });
1091
+ if (settleError) emit({ type: "replication.state", space: r.space, state: "pending_store_error", code: "STORAGE_ERROR" });
1092
+ return result;
1093
+ }
1094
+ function observeNetworkRequested(r) {
1095
+ emit({ type: "replication.read", op: r.op, space: r.space, key: r.path, replica: null, source: "network", reason: "NETWORK_REQUESTED", outcome: r.outcome, latencyMs: r.latencyMs, stalenessMs: null, coverage: null, authority: null });
1096
+ }
1097
+ async function inspectPersistedStatus(prefix) {
1098
+ if (!storage.inspectStatus || isClosed || purgeInProgress) return { missing: false };
1099
+ const sessionGrant = authority.sessionGrant(prefix);
1100
+ const device = "refused" in sessionGrant ? void 0 : sessionGrant.device;
1101
+ const spec = { identity, space: session.space, prefix, allowSecrets: options.allowSecrets, ...device ? { device } : {}, ...deps.fetch ? { fetch: deps.fetch } : {} };
1102
+ const abort = new AbortController();
1103
+ const job = Promise.resolve().then(() => storage.inspectStatus(spec, { signal: abort.signal }));
1104
+ const settled = job.then(() => void 0, () => void 0);
1105
+ const inspection = { abort, settled };
1106
+ statusInspections.add(inspection);
1107
+ void settled.then(() => statusInspections.delete(inspection));
1108
+ let cancel = () => {
1109
+ };
1110
+ const timeout = new Promise((resolve) => {
1111
+ cancel = scheduler.setTimeout(() => {
1112
+ abort.abort(Object.assign(new Error("Replica status inspection timed out"), { code: "STATUS_TIMEOUT" }));
1113
+ resolve({ timedOut: true });
1114
+ }, STATUS_REFRESH_TIMEOUT_MS);
1115
+ });
1116
+ try {
1117
+ const result = await Promise.race([job.then((local) => ({ local })), timeout]);
1118
+ if ("timedOut" in result) return { missing: false, errorCode: "STATUS_TIMEOUT" };
1119
+ return { missing: result.local === void 0, ...result.local ? { local: result.local } : {} };
1120
+ } catch (error) {
1121
+ return { missing: false, errorCode: errorCode(error) };
1122
+ } finally {
1123
+ cancel();
1124
+ }
1125
+ }
1126
+ function cancelStatusInspections() {
1127
+ const active = [...statusInspections];
1128
+ for (const inspection of active) inspection.abort.abort(Object.assign(new Error("Replica status inspection cancelled"), { code: "ABORTED" }));
1129
+ return Promise.all(active.map((inspection) => inspection.settled)).then(() => void 0);
1130
+ }
1131
+ async function status() {
1132
+ const persistedSnapshot = pending.readExisting ? await pending.readExisting() : await pending.read();
1133
+ const snapshot = persistedSnapshot ?? emptyPendingState(identity);
1134
+ const entries = await Promise.all(options.prefixes.map(async (prefix) => {
1135
+ const records = snapshot.records.filter((r) => kvPrefixCovers(prefix, r.key));
1136
+ const opened = replicas.get(prefix);
1137
+ let local;
1138
+ if (opened?.handle) {
1139
+ try {
1140
+ local = await opened.handle.status();
1141
+ } catch {
1142
+ }
1143
+ }
1144
+ let inspected = { missing: false };
1145
+ if (!local && !opened?.opening && !opened?.authorityRevoked) inspected = await inspectPersistedStatus(prefix);
1146
+ local ?? (local = inspected.local);
1147
+ if (inspected.missing && !opened?.reason && !opened?.authorityRevoked && !opened?.opening && !isClosed) return void 0;
1148
+ const revoked = opened?.authorityRevoked === true || local?.authority.state === "revoked";
1149
+ const state = isClosed ? "closed" : revoked ? "revoked" : opened?.reason === "grant_missing" ? "grant_missing" : local?.coverage === "complete" && local.authority.state === "valid" ? "ready" : opened?.reason || inspected.errorCode ? "unavailable" : "idle";
1150
+ return {
1151
+ prefix,
1152
+ state,
1153
+ ...opened?.reason ? { reason: opened.reason } : inspected.errorCode ? { reason: "replica_unavailable" } : {},
1154
+ ...inspected.errorCode ? { errorCode: inspected.errorCode } : {},
1155
+ ...local ?? {},
1156
+ ...revoked ? { authority: { state: "revoked", expiresAt: local?.authority.expiresAt ?? null } } : {},
1157
+ pending: {
1158
+ inFlight: records.filter((r) => r.state === "in_flight").length,
1159
+ committed: records.filter((r) => r.state === "committed").length,
1160
+ ambiguous: records.filter((r) => r.state === "ambiguous").length
1161
+ },
1162
+ pinned: pinnedKeys(snapshot, prefix, scheduler.now()),
1163
+ lagMs: local?.lastSyncAt ? scheduler.now() - Date.parse(local.lastSyncAt) : null
1164
+ };
1165
+ }));
1166
+ return entries.filter((entry) => entry !== void 0);
1167
+ }
1168
+ async function sync(o) {
1169
+ const prefixes = o?.prefix ? options.prefixes.filter((p) => p === o.prefix) : options.prefixes;
1170
+ await Promise.all(prefixes.map((prefix) => syncPrefix(prefix, "manual", o?.signal).then(() => void 0)));
1171
+ }
1172
+ async function clearPending2() {
1173
+ const count = await pending.update((s) => clearPending(s, scheduler.now()));
1174
+ emit({ type: "replication.state", space: session.space, state: "pending_cleared", count });
1175
+ return count;
1176
+ }
1177
+ function purge(o) {
1178
+ var _a;
1179
+ purgeStarted = true;
1180
+ purgeInProgress = true;
1181
+ const hasStatusInspections = statusInspections.size > 0;
1182
+ const statusDrain = cancelStatusInspections();
1183
+ for (const state of replicas.values()) {
1184
+ state.timer?.();
1185
+ state.timer = void 0;
1186
+ state.syncContext && ((_a = state.syncContext).abortCause ?? (_a.abortCause = "purge"));
1187
+ state.abort?.abort();
1188
+ state.mintAbort?.abort();
1189
+ }
1190
+ return Promise.resolve().then(async () => {
1191
+ const report = { purged: [], failed: [] };
1192
+ const timeoutMs = o?.timeoutMs ?? 5e3;
1193
+ const withTimeout = async (job) => {
1194
+ let cancel = () => {
1195
+ };
1196
+ const timeout = new Promise((_, reject) => {
1197
+ cancel = scheduler.setTimeout(() => reject(Object.assign(new Error("Replication cleanup timed out"), { code: "TIMEOUT" })), timeoutMs);
1198
+ });
1199
+ try {
1200
+ return await Promise.race([job, timeout]);
1201
+ } finally {
1202
+ cancel();
1203
+ }
1204
+ };
1205
+ const drain = Promise.all([
1206
+ ...[...replicas.values()].flatMap((state) => [
1207
+ state.opening?.catch(() => void 0),
1208
+ state.sync?.catch(() => void 0),
1209
+ state.handle?.close().catch(() => void 0)
1210
+ ]),
1211
+ ...hasStatusInspections ? [statusDrain] : []
1212
+ ]);
1213
+ let drainCode;
1214
+ try {
1215
+ await withTimeout(drain);
1216
+ } catch (error) {
1217
+ drainCode = errorCode(error);
1218
+ }
1219
+ const jobs = options.prefixes.map(async (prefix) => {
1220
+ try {
1221
+ if (drainCode) throw Object.assign(new Error("Replica cleanup did not settle before purge"), { code: drainCode });
1222
+ const grant = authority.sessionGrant(prefix);
1223
+ const sessionDeviceDid = replicas.get(prefix)?.handle?.deviceDid ?? ("refused" in grant ? void 0 : grant.device.did);
1224
+ await withTimeout(storage.purge({ identity, space: session.space, prefix, ...sessionDeviceDid ? { sessionDeviceDid } : {} }));
1225
+ await pending.update((s) => clearPrefix(s, prefix));
1226
+ replicas.delete(prefix);
1227
+ inProcessProof.delete(prefix);
1228
+ report.purged.push(prefix);
1229
+ emit({ type: "replication.state", space: session.space, replica: prefix, state: "purged" });
1230
+ } catch (error) {
1231
+ const code = errorCode(error);
1232
+ report.failed.push({ prefix, code });
1233
+ emit({ type: "replication.state", space: session.space, replica: prefix, state: "purge_failed", code });
1234
+ }
1235
+ });
1236
+ await Promise.all(jobs);
1237
+ return report;
1238
+ }).finally(() => {
1239
+ purgeInProgress = false;
1240
+ });
1241
+ }
1242
+ async function close() {
1243
+ var _a;
1244
+ if (isClosed) return;
1245
+ isClosed = true;
1246
+ const hasStatusInspections = statusInspections.size > 0;
1247
+ const statusDrain = cancelStatusInspections();
1248
+ for (const [prefix, state] of replicas) {
1249
+ state.timer?.();
1250
+ state.timer = void 0;
1251
+ state.mintAbort?.abort();
1252
+ state.syncContext && ((_a = state.syncContext).abortCause ?? (_a.abortCause = "close"));
1253
+ state.abort?.abort();
1254
+ emit({ type: "replication.state", space: session.space, replica: prefix, state: "closed" });
1255
+ }
1256
+ const drain = Promise.all([
1257
+ ...[...replicas.values()].map(async (state) => {
1258
+ await state.opening?.catch(() => void 0);
1259
+ await state.sync?.catch(() => void 0);
1260
+ await state.handle?.close().catch(() => void 0);
1261
+ }),
1262
+ ...hasStatusInspections ? [statusDrain] : []
1263
+ ]);
1264
+ let cancel = () => {
1265
+ };
1266
+ let drained = false;
1267
+ void drain.then(
1268
+ () => {
1269
+ drained = true;
1270
+ cancel();
1271
+ },
1272
+ () => {
1273
+ drained = true;
1274
+ cancel();
1275
+ }
1276
+ );
1277
+ const timeout = new Promise((resolve) => {
1278
+ queueMicrotask(() => {
1279
+ if (!drained) cancel = scheduler.setTimeout(resolve, CLOSE_TIMEOUT_MS);
1280
+ });
1281
+ });
1282
+ try {
1283
+ await Promise.race([drain, timeout]);
1284
+ } finally {
1285
+ cancel();
1286
+ }
1287
+ }
1288
+ return { get, list, write, observeNetworkRequested, status, sync, purge, clearPending: clearPending2, close };
1289
+ }
1290
+ function cancellationCode(signal) {
1291
+ return wrapError("kv", signal.reason).code === ErrorCodes.TIMEOUT || typeof signal.reason === "object" && signal.reason !== null && "code" in signal.reason && signal.reason.code === ErrorCodes.TIMEOUT ? ErrorCodes.TIMEOUT : ErrorCodes.ABORTED;
1292
+ }
1293
+ function abortedResult(signal) {
1294
+ const code = cancellationCode(signal);
1295
+ return err(serviceError(code, code === ErrorCodes.TIMEOUT ? "KV request timed out" : "KV request was aborted", "kv"));
1296
+ }
1297
+
1298
+ // src/kv/replication/memoryPendingStore.ts
1299
+ function createMemoryPendingStore(identity) {
1300
+ const canonical = canonicalReplicationIdentity(identity);
1301
+ const key = replicationIdentityKey(canonical);
1302
+ let state = emptyPendingState(canonical);
1303
+ let tail = Promise.resolve();
1304
+ return {
1305
+ identity: canonical,
1306
+ durable: false,
1307
+ async read() {
1308
+ return structuredClone(state);
1309
+ },
1310
+ async update(mutate) {
1311
+ let resolve;
1312
+ let reject;
1313
+ const result = new Promise((res, rej) => {
1314
+ resolve = res;
1315
+ reject = rej;
1316
+ });
1317
+ const operation = tail.then(() => {
1318
+ try {
1319
+ if (replicationIdentityKey(state.identity) !== key) throw Object.assign(new Error("Pending-store identity mismatch"), { code: "STORAGE_ERROR" });
1320
+ const next = structuredClone(state);
1321
+ const value = mutate(next);
1322
+ state = next;
1323
+ resolve(value);
1324
+ } catch (error) {
1325
+ reject(error);
1326
+ }
1327
+ });
1328
+ tail = operation.then(() => void 0, () => void 0);
1329
+ return result;
1330
+ }
1331
+ };
1332
+ }
1333
+ export {
1334
+ CLEAR_PENDING_WARNING,
1335
+ canonicalReplicationIdentity,
1336
+ createKVReplication,
1337
+ createMemoryPendingStore,
1338
+ kvPrefixCovers,
1339
+ replicationIdentityKey,
1340
+ requiresSecretsOptIn
1341
+ };
1342
+ //# sourceMappingURL=index.js.map