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

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,6 @@
1
- import { K as KVReplicationDeps, a as KVReplicationController, R as ReplicationIdentity, P as PendingWriteStore } from '../../scope-D855kRDx.cjs';
2
- export { A as AuthorityRefusal, C as CLEAR_PENDING_WARNING, b as KVListPage, c as KVReadThrough, d as KVReplicaHandle, e as KVReplicaSpec, f as KVReplicaStorage, L as LocalGetResult, g as LocalListResult, h as LocalReadMeta, i as LocalReplicaStatus, j as LocalSyncResult, k as PendingWriteRecord, l as PendingWriteState, m as PinnedKey, n as PurgeTarget, o as ReplicaAuthorityState, p as ReplicaCoverage, q as ReplicaDevice, r as ReplicaGrantInfo, s as ReplicaState, t as ReplicaStatusEntry, u as ReplicationAuthority, v as ReplicationControl, w as ReplicationEvent, x as ReplicationOptions, y as ReplicationPurgeReport, z as ReplicationReason, B as ReplicationScheduler, D as ResolvedReplicationOptions, E as canonicalReplicationIdentity, F as kvPrefixCovers, G as replicationIdentityKey, H as requiresSecretsOptIn } from '../../scope-D855kRDx.cjs';
1
+ import { K as KVReplicationDeps, a as KVReplicationController, R as ReplicationIdentity, P as PendingWriteStore } from '../../scope-C05VVAXH.cjs';
2
+ export { A as AuthorityRefusal, C as CLEAR_PENDING_WARNING, b as KVListPage, c as KVReadThrough, d as KVReplicaHandle, e as KVReplicaSpec, f as KVReplicaStorage, L as LocalGetResult, g as LocalListResult, h as LocalReadMeta, i as LocalReplicaStatus, j as LocalSyncResult, k as PendingWriteRecord, l as PendingWriteState, m as PinnedKey, n as PurgeTarget, o as ReplicaAuthorityState, p as ReplicaCoverage, q as ReplicaDevice, r as ReplicaGrantInfo, s as ReplicaState, t as ReplicaStatusEntry, u as ReplicationAuthority, v as ReplicationControl, w as ReplicationEvent, x as ReplicationOptions, y as ReplicationPurgeReport, z as ReplicationReason, B as ReplicationScheduler, D as ResolvedReplicationOptions, E as canonicalReplicationIdentity, F as replicationIdentityKey, G as requiresSecretsOptIn } from '../../scope-C05VVAXH.cjs';
3
+ export { kvPrefixCovers } from './prefix.cjs';
3
4
  import '../../types-7A5rHWRY.cjs';
4
5
 
5
6
  declare function createKVReplication(deps: KVReplicationDeps): KVReplicationController;
@@ -1,5 +1,6 @@
1
- import { K as KVReplicationDeps, a as KVReplicationController, R as ReplicationIdentity, P as PendingWriteStore } from '../../scope-BmLxwyl-.js';
2
- export { A as AuthorityRefusal, C as CLEAR_PENDING_WARNING, b as KVListPage, c as KVReadThrough, d as KVReplicaHandle, e as KVReplicaSpec, f as KVReplicaStorage, L as LocalGetResult, g as LocalListResult, h as LocalReadMeta, i as LocalReplicaStatus, j as LocalSyncResult, k as PendingWriteRecord, l as PendingWriteState, m as PinnedKey, n as PurgeTarget, o as ReplicaAuthorityState, p as ReplicaCoverage, q as ReplicaDevice, r as ReplicaGrantInfo, s as ReplicaState, t as ReplicaStatusEntry, u as ReplicationAuthority, v as ReplicationControl, w as ReplicationEvent, x as ReplicationOptions, y as ReplicationPurgeReport, z as ReplicationReason, B as ReplicationScheduler, D as ResolvedReplicationOptions, E as canonicalReplicationIdentity, F as kvPrefixCovers, G as replicationIdentityKey, H as requiresSecretsOptIn } from '../../scope-BmLxwyl-.js';
1
+ import { K as KVReplicationDeps, a as KVReplicationController, R as ReplicationIdentity, P as PendingWriteStore } from '../../scope-BKge7zDT.js';
2
+ export { A as AuthorityRefusal, C as CLEAR_PENDING_WARNING, b as KVListPage, c as KVReadThrough, d as KVReplicaHandle, e as KVReplicaSpec, f as KVReplicaStorage, L as LocalGetResult, g as LocalListResult, h as LocalReadMeta, i as LocalReplicaStatus, j as LocalSyncResult, k as PendingWriteRecord, l as PendingWriteState, m as PinnedKey, n as PurgeTarget, o as ReplicaAuthorityState, p as ReplicaCoverage, q as ReplicaDevice, r as ReplicaGrantInfo, s as ReplicaState, t as ReplicaStatusEntry, u as ReplicationAuthority, v as ReplicationControl, w as ReplicationEvent, x as ReplicationOptions, y as ReplicationPurgeReport, z as ReplicationReason, B as ReplicationScheduler, D as ResolvedReplicationOptions, E as canonicalReplicationIdentity, F as replicationIdentityKey, G as requiresSecretsOptIn } from '../../scope-BKge7zDT.js';
3
+ export { kvPrefixCovers } from './prefix.js';
3
4
  import '../../types-7A5rHWRY.js';
4
5
 
5
6
  declare function createKVReplication(deps: KVReplicationDeps): KVReplicationController;
@@ -45,16 +45,17 @@ ${canonical.space}
45
45
  ${canonical.principal}`;
46
46
  }
47
47
 
48
- // src/kv/replication/scope.ts
48
+ // src/kv/replication/prefix.ts
49
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}/`);
50
+ return prefix === "/" || key.startsWith(prefix);
53
51
  }
52
+
53
+ // src/kv/replication/scope.ts
54
54
  function listRangeCovers(path, key) {
55
- return key === path || key.startsWith(path.endsWith("/") ? path : `${path}/`);
55
+ return path === "" || key === path || key.startsWith(path.endsWith("/") ? path : `${path}/`);
56
56
  }
57
57
  function requiresSecretsOptIn(space, prefix) {
58
+ if (prefix === "/") return true;
58
59
  if (space === "secrets" || space.endsWith(":secrets")) return true;
59
60
  return prefix.split("/", 1)[0] === "vault";
60
61
  }
@@ -339,9 +340,9 @@ function cursorRestart() {
339
340
  }
340
341
  function localList(handle, space, path, limit, cursor, validateMeta) {
341
342
  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
+ const exact = path !== "" && !path.endsWith("/") && (after === void 0 || utf8Compare(path, after) > 0) ? awaitExact(handle, path, after, validateMeta) : Promise.resolve(false);
343
344
  return exact.then(async (hasExact) => {
344
- const child = path.endsWith("/") ? path : `${path}/`;
345
+ const child = path === "" ? "" : path.endsWith("/") ? path : `${path}/`;
345
346
  const want = limit === void 0 ? void 0 : limit + 1 - (hasExact ? 1 : 0);
346
347
  const children = await handle.list({ prefix: child, after, limit: want });
347
348
  validateMeta(children.meta);
@@ -358,9 +359,7 @@ async function awaitExact(handle, path, after, validateMeta) {
358
359
  return result.keys[0] === path;
359
360
  }
360
361
  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;
362
+ return prefixes.find((prefix) => kvPrefixCovers(prefix, path));
364
363
  }
365
364
 
366
365
  // src/kv/replication/events.ts
@@ -389,6 +388,7 @@ function raceSignal(job, signal) {
389
388
  }
390
389
  function createKVReplication(deps) {
391
390
  const { options, mode, storage, identity, session, authority, pending, scheduler } = deps;
391
+ const emit = (event) => emitEvent(deps.emit, scheduler, event);
392
392
  const replicas = /* @__PURE__ */ new Map();
393
393
  const statusInspections = /* @__PURE__ */ new Set();
394
394
  let isClosed = false;
@@ -397,7 +397,6 @@ function createKVReplication(deps) {
397
397
  let previous = deps.previous;
398
398
  let pinnedStateReported = false;
399
399
  const inProcessProof = /* @__PURE__ */ new Set();
400
- const emit = (event) => emitEvent(deps.emit, scheduler, event);
401
400
  const configuredPrefix = (path) => options.prefixes.find((prefix) => kvPrefixCovers(prefix, path));
402
401
  function recordRevocation(prefix, error) {
403
402
  if (errorCode(error) !== "GRANT_REVOKED") return;
@@ -407,6 +406,20 @@ function createKVReplication(deps) {
407
406
  state.lastError = "GRANT_REVOKED";
408
407
  replicas.set(prefix, state);
409
408
  }
409
+ function markRootUnsupported(prefix) {
410
+ const state = replicas.get(prefix) ?? {};
411
+ const firstRefusal = state.lastError !== "REPLICATION_ROOT_UNSUPPORTED";
412
+ state.reason = "replica_unavailable";
413
+ state.lastError = "REPLICATION_ROOT_UNSUPPORTED";
414
+ replicas.set(prefix, state);
415
+ if (firstRefusal) {
416
+ emit({ type: "replication.state", space: session.space, replica: prefix, state: "unavailable", code: "REPLICATION_ROOT_UNSUPPORTED" });
417
+ }
418
+ }
419
+ function unsupportedRoot(prefix) {
420
+ markRootUnsupported(prefix);
421
+ return Object.assign(new Error("This node doesn't support whole-space sync yet."), { code: "REPLICATION_ROOT_UNSUPPORTED" });
422
+ }
410
423
  async function open(prefix) {
411
424
  if (isClosed || purgeStarted) throw Object.assign(new Error("Replication controller is closed"), { code: "REPLICA_CLOSED" });
412
425
  let state = replicas.get(prefix);
@@ -440,6 +453,9 @@ function createKVReplication(deps) {
440
453
  state.strategy = "session";
441
454
  }
442
455
  const spec = { identity, space: session.space, prefix, allowSecrets: options.allowSecrets, ...device ? { device } : {}, ...deps.fetch ? { fetch: deps.fetch } : {} };
456
+ if (prefix === "/" && deps.nodeFeatures !== void 0 && !deps.nodeFeatures.includes("kv-sync-root")) {
457
+ throw unsupportedRoot(prefix);
458
+ }
443
459
  const handle = await storage.open(spec);
444
460
  state.handle = handle;
445
461
  emit({ type: "replication.state", space: session.space, replica: prefix, state: "opened" });
@@ -489,7 +505,7 @@ function createKVReplication(deps) {
489
505
  state.lastError = code;
490
506
  state.failures = (state.failures ?? 0) + 1;
491
507
  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));
508
+ else if (code !== "REPLICA_CLOSED" && code !== "REPLICATION_ROOT_UNSUPPORTED") state.retryAt = scheduler.now() + Math.min(3e5, 5e3 * 2 ** Math.min(state.failures - 1, 6));
493
509
  state.handle?.close().catch(() => void 0);
494
510
  state.handle = void 0;
495
511
  throw error;
@@ -506,6 +522,9 @@ function createKVReplication(deps) {
506
522
  }, options.syncIntervalMs);
507
523
  }
508
524
  async function syncPrefix(prefix, trigger, signal, context) {
525
+ if (prefix === "/" && deps.nodeFeatures !== void 0 && !deps.nodeFeatures.includes("kv-sync-root")) {
526
+ throw unsupportedRoot(prefix);
527
+ }
509
528
  const handle = await open(prefix);
510
529
  const state = replicas.get(prefix);
511
530
  if (state.sync) return state.sync;
@@ -807,7 +826,7 @@ function createKVReplication(deps) {
807
826
  observeNetworkRequested({ op: "get", space: r.space, path: r.path, reason: "NETWORK_REQUESTED", outcome, latencyMs: scheduler.now() - started });
808
827
  }
809
828
  }
810
- if (!prefix) {
829
+ if (prefix === void 0) {
811
830
  const value = await r.network();
812
831
  readEvent("get", r.path, null, "network", "not_covered", value.ok ? "found" : value.error.code === ErrorCodes.KV_NOT_FOUND ? "not_found" : "error", started);
813
832
  return value;
@@ -958,10 +977,10 @@ function createKVReplication(deps) {
958
977
  return cursorRestart();
959
978
  };
960
979
  if (cursor && decoded && (decoded.space !== r.space || decoded.path !== r.listPath)) return restart();
961
- if (!prefix || !r.listPath || r.options?.raw) {
980
+ if (prefix === void 0 || r.options?.raw) {
962
981
  if (cursor && decoded) return restart();
963
982
  const result = await r.network();
964
- readEvent("list", r.listPath, prefix ?? null, "network", prefix ? "unsupported_option" : "not_covered", result.ok ? "found" : "error", started);
983
+ readEvent("list", r.listPath, prefix ?? null, "network", prefix !== void 0 ? "unsupported_option" : "not_covered", result.ok ? "found" : "error", started);
965
984
  return result;
966
985
  }
967
986
  let handle;
@@ -1056,7 +1075,7 @@ function createKVReplication(deps) {
1056
1075
  async function write(r) {
1057
1076
  const covered = r.entries.flatMap((entry) => {
1058
1077
  const prefix = configuredPrefix(entry.path);
1059
- return prefix ? [{ key: entry.path, op: r.op === "delete" ? "delete" : "put" }] : [];
1078
+ return prefix !== void 0 ? [{ key: entry.path, op: r.op === "delete" ? "delete" : "put" }] : [];
1060
1079
  });
1061
1080
  if (covered.length === 0) return r.network();
1062
1081
  await reportExistingPins();
@@ -1132,6 +1151,9 @@ function createKVReplication(deps) {
1132
1151
  const persistedSnapshot = pending.readExisting ? await pending.readExisting() : await pending.read();
1133
1152
  const snapshot = persistedSnapshot ?? emptyPendingState(identity);
1134
1153
  const entries = await Promise.all(options.prefixes.map(async (prefix) => {
1154
+ if (prefix === "/" && deps.nodeFeatures !== void 0 && !deps.nodeFeatures.includes("kv-sync-root")) {
1155
+ markRootUnsupported(prefix);
1156
+ }
1135
1157
  const records = snapshot.records.filter((r) => kvPrefixCovers(prefix, r.key));
1136
1158
  const opened = replicas.get(prefix);
1137
1159
  let local;
@@ -1146,12 +1168,13 @@ function createKVReplication(deps) {
1146
1168
  local ?? (local = inspected.local);
1147
1169
  if (inspected.missing && !opened?.reason && !opened?.authorityRevoked && !opened?.opening && !isClosed) return void 0;
1148
1170
  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";
1171
+ const rootUnsupported = opened?.lastError === "REPLICATION_ROOT_UNSUPPORTED";
1172
+ const state = isClosed ? "closed" : revoked ? "revoked" : rootUnsupported ? "unavailable" : opened?.reason === "grant_missing" ? "grant_missing" : local?.coverage === "complete" && local.authority.state === "valid" ? "ready" : opened?.reason || inspected.errorCode ? "unavailable" : "idle";
1150
1173
  return {
1151
1174
  prefix,
1152
1175
  state,
1153
1176
  ...opened?.reason ? { reason: opened.reason } : inspected.errorCode ? { reason: "replica_unavailable" } : {},
1154
- ...inspected.errorCode ? { errorCode: inspected.errorCode } : {},
1177
+ ...inspected.errorCode ?? opened?.lastError ? { errorCode: inspected.errorCode ?? opened?.lastError } : {},
1155
1178
  ...local ?? {},
1156
1179
  ...revoked ? { authority: { state: "revoked", expiresAt: local?.authority.expiresAt ?? null } } : {},
1157
1180
  pending: {
@@ -1166,7 +1189,7 @@ function createKVReplication(deps) {
1166
1189
  return entries.filter((entry) => entry !== void 0);
1167
1190
  }
1168
1191
  async function sync(o) {
1169
- const prefixes = o?.prefix ? options.prefixes.filter((p) => p === o.prefix) : options.prefixes;
1192
+ const prefixes = o?.prefix !== void 0 ? options.prefixes.filter((p) => p === o.prefix) : options.prefixes;
1170
1193
  await Promise.all(prefixes.map((prefix) => syncPrefix(prefix, "manual", o?.signal).then(() => void 0)));
1171
1194
  }
1172
1195
  async function clearPending2() {