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

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 (45) hide show
  1. package/dist/{BaseService-BDQXjWMx.d.cts → BaseService-4wwJq1Gn.d.cts} +1 -1
  2. package/dist/{BaseService-Di_hVfUV.d.ts → BaseService-BGr2slUz.d.ts} +1 -1
  3. package/dist/encryption/index.cjs +5 -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 +5 -1
  8. package/dist/encryption/index.js.map +1 -1
  9. package/dist/index.cjs +190 -11
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +157 -6
  12. package/dist/index.d.ts +157 -6
  13. package/dist/index.js +184 -11
  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 +56 -11
  18. package/dist/kv/index.cjs.map +1 -1
  19. package/dist/kv/index.d.cts +5 -4
  20. package/dist/kv/index.d.ts +5 -4
  21. package/dist/kv/index.js +56 -11
  22. package/dist/kv/index.js.map +1 -1
  23. package/dist/kv/replication/index.cjs +44 -19
  24. package/dist/kv/replication/index.cjs.map +1 -1
  25. package/dist/kv/replication/index.d.cts +4 -3
  26. package/dist/kv/replication/index.d.ts +4 -3
  27. package/dist/kv/replication/index.js +44 -19
  28. package/dist/kv/replication/index.js.map +1 -1
  29. package/dist/kv/replication/prefix.cjs +43 -0
  30. package/dist/kv/replication/prefix.cjs.map +1 -0
  31. package/dist/kv/replication/prefix.d.cts +6 -0
  32. package/dist/kv/replication/prefix.d.ts +6 -0
  33. package/dist/kv/replication/prefix.js +17 -0
  34. package/dist/kv/replication/prefix.js.map +1 -0
  35. package/dist/{scope-BmLxwyl-.d.ts → scope-CoKKmMiI.d.ts} +5 -5
  36. package/dist/{scope-D855kRDx.d.cts → scope-D7wmjbMH.d.cts} +5 -5
  37. package/dist/sql/index.cjs +44 -2
  38. package/dist/sql/index.cjs.map +1 -1
  39. package/dist/sql/index.d.cts +2 -2
  40. package/dist/sql/index.d.ts +2 -2
  41. package/dist/sql/index.js +44 -2
  42. package/dist/sql/index.js.map +1 -1
  43. package/dist/{types-7A5rHWRY.d.cts → types-D-9A3Sut.d.cts} +6 -0
  44. package/dist/{types-7A5rHWRY.d.ts → types-D-9A3Sut.d.ts} +6 -0
  45. package/package.json +14 -1
@@ -77,16 +77,17 @@ ${canonical.space}
77
77
  ${canonical.principal}`;
78
78
  }
79
79
 
80
- // src/kv/replication/scope.ts
80
+ // src/kv/replication/prefix.ts
81
81
  function kvPrefixCovers(prefix, key) {
82
- if (prefix === "") return true;
83
- if (prefix.endsWith("/")) return key.startsWith(prefix);
84
- return key === prefix || key.startsWith(`${prefix}/`);
82
+ return prefix === "/" || key.startsWith(prefix);
85
83
  }
84
+
85
+ // src/kv/replication/scope.ts
86
86
  function listRangeCovers(path, key) {
87
- return key === path || key.startsWith(path.endsWith("/") ? path : `${path}/`);
87
+ return path === "" || key === path || key.startsWith(path.endsWith("/") ? path : `${path}/`);
88
88
  }
89
89
  function requiresSecretsOptIn(space, prefix) {
90
+ if (prefix === "/") return true;
90
91
  if (space === "secrets" || space.endsWith(":secrets")) return true;
91
92
  return prefix.split("/", 1)[0] === "vault";
92
93
  }
@@ -132,6 +133,8 @@ var ErrorCodes = {
132
133
  // Storage quota errors
133
134
  STORAGE_QUOTA_EXCEEDED: "STORAGE_QUOTA_EXCEEDED",
134
135
  STORAGE_LIMIT_REACHED: "STORAGE_LIMIT_REACHED",
136
+ /** The node answered the usage read without usage numbers (it predates the read). */
137
+ STORAGE_STATUS_UNAVAILABLE: "STORAGE_STATUS_UNAVAILABLE",
135
138
  // DuckDB-specific errors
136
139
  DUCKDB_ERROR: "DUCKDB_ERROR",
137
140
  DUCKDB_PERMISSION_DENIED: "DUCKDB_PERMISSION_DENIED",
@@ -371,9 +374,9 @@ function cursorRestart() {
371
374
  }
372
375
  function localList(handle, space, path, limit, cursor, validateMeta) {
373
376
  const after = cursor ? decodeTcr1(cursor)?.last : void 0;
374
- const exact = !path.endsWith("/") && (after === void 0 || utf8Compare(path, after) > 0) ? awaitExact(handle, path, after, validateMeta) : Promise.resolve(false);
377
+ const exact = path !== "" && !path.endsWith("/") && (after === void 0 || utf8Compare(path, after) > 0) ? awaitExact(handle, path, after, validateMeta) : Promise.resolve(false);
375
378
  return exact.then(async (hasExact) => {
376
- const child = path.endsWith("/") ? path : `${path}/`;
379
+ const child = path === "" ? "" : path.endsWith("/") ? path : `${path}/`;
377
380
  const want = limit === void 0 ? void 0 : limit + 1 - (hasExact ? 1 : 0);
378
381
  const children = await handle.list({ prefix: child, after, limit: want });
379
382
  validateMeta(children.meta);
@@ -390,9 +393,7 @@ async function awaitExact(handle, path, after, validateMeta) {
390
393
  return result.keys[0] === path;
391
394
  }
392
395
  function listPathCovered(prefixes, path) {
393
- const descendant = path.endsWith("/") ? path : `${path}/`;
394
- const covering = prefixes.filter((prefix) => kvPrefixCovers(prefix, path) && kvPrefixCovers(prefix, descendant));
395
- return covering.length === 1 ? covering[0] : void 0;
396
+ return prefixes.find((prefix) => kvPrefixCovers(prefix, path));
396
397
  }
397
398
 
398
399
  // src/kv/replication/events.ts
@@ -421,6 +422,7 @@ function raceSignal(job, signal) {
421
422
  }
422
423
  function createKVReplication(deps) {
423
424
  const { options, mode, storage, identity, session, authority, pending, scheduler } = deps;
425
+ const emit = (event) => emitEvent(deps.emit, scheduler, event);
424
426
  const replicas = /* @__PURE__ */ new Map();
425
427
  const statusInspections = /* @__PURE__ */ new Set();
426
428
  let isClosed = false;
@@ -429,7 +431,6 @@ function createKVReplication(deps) {
429
431
  let previous = deps.previous;
430
432
  let pinnedStateReported = false;
431
433
  const inProcessProof = /* @__PURE__ */ new Set();
432
- const emit = (event) => emitEvent(deps.emit, scheduler, event);
433
434
  const configuredPrefix = (path) => options.prefixes.find((prefix) => kvPrefixCovers(prefix, path));
434
435
  function recordRevocation(prefix, error) {
435
436
  if (errorCode(error) !== "GRANT_REVOKED") return;
@@ -439,6 +440,20 @@ function createKVReplication(deps) {
439
440
  state.lastError = "GRANT_REVOKED";
440
441
  replicas.set(prefix, state);
441
442
  }
443
+ function markRootUnsupported(prefix) {
444
+ const state = replicas.get(prefix) ?? {};
445
+ const firstRefusal = state.lastError !== "REPLICATION_ROOT_UNSUPPORTED";
446
+ state.reason = "replica_unavailable";
447
+ state.lastError = "REPLICATION_ROOT_UNSUPPORTED";
448
+ replicas.set(prefix, state);
449
+ if (firstRefusal) {
450
+ emit({ type: "replication.state", space: session.space, replica: prefix, state: "unavailable", code: "REPLICATION_ROOT_UNSUPPORTED" });
451
+ }
452
+ }
453
+ function unsupportedRoot(prefix) {
454
+ markRootUnsupported(prefix);
455
+ return Object.assign(new Error("This node doesn't support whole-space sync yet."), { code: "REPLICATION_ROOT_UNSUPPORTED" });
456
+ }
442
457
  async function open(prefix) {
443
458
  if (isClosed || purgeStarted) throw Object.assign(new Error("Replication controller is closed"), { code: "REPLICA_CLOSED" });
444
459
  let state = replicas.get(prefix);
@@ -472,6 +487,9 @@ function createKVReplication(deps) {
472
487
  state.strategy = "session";
473
488
  }
474
489
  const spec = { identity, space: session.space, prefix, allowSecrets: options.allowSecrets, ...device ? { device } : {}, ...deps.fetch ? { fetch: deps.fetch } : {} };
490
+ if (prefix === "/" && deps.nodeFeatures !== void 0 && !deps.nodeFeatures.includes("kv-sync-root")) {
491
+ throw unsupportedRoot(prefix);
492
+ }
475
493
  const handle = await storage.open(spec);
476
494
  state.handle = handle;
477
495
  emit({ type: "replication.state", space: session.space, replica: prefix, state: "opened" });
@@ -521,7 +539,7 @@ function createKVReplication(deps) {
521
539
  state.lastError = code;
522
540
  state.failures = (state.failures ?? 0) + 1;
523
541
  if (code === "RUNTIME_UNSUPPORTED") state.unsupported = true;
524
- else if (code !== "REPLICA_CLOSED") state.retryAt = scheduler.now() + Math.min(3e5, 5e3 * 2 ** Math.min(state.failures - 1, 6));
542
+ else if (code !== "REPLICA_CLOSED" && code !== "REPLICATION_ROOT_UNSUPPORTED") state.retryAt = scheduler.now() + Math.min(3e5, 5e3 * 2 ** Math.min(state.failures - 1, 6));
525
543
  state.handle?.close().catch(() => void 0);
526
544
  state.handle = void 0;
527
545
  throw error;
@@ -538,6 +556,9 @@ function createKVReplication(deps) {
538
556
  }, options.syncIntervalMs);
539
557
  }
540
558
  async function syncPrefix(prefix, trigger, signal, context) {
559
+ if (prefix === "/" && deps.nodeFeatures !== void 0 && !deps.nodeFeatures.includes("kv-sync-root")) {
560
+ throw unsupportedRoot(prefix);
561
+ }
541
562
  const handle = await open(prefix);
542
563
  const state = replicas.get(prefix);
543
564
  if (state.sync) return state.sync;
@@ -839,7 +860,7 @@ function createKVReplication(deps) {
839
860
  observeNetworkRequested({ op: "get", space: r.space, path: r.path, reason: "NETWORK_REQUESTED", outcome, latencyMs: scheduler.now() - started });
840
861
  }
841
862
  }
842
- if (!prefix) {
863
+ if (prefix === void 0) {
843
864
  const value = await r.network();
844
865
  readEvent("get", r.path, null, "network", "not_covered", value.ok ? "found" : value.error.code === ErrorCodes.KV_NOT_FOUND ? "not_found" : "error", started);
845
866
  return value;
@@ -990,10 +1011,10 @@ function createKVReplication(deps) {
990
1011
  return cursorRestart();
991
1012
  };
992
1013
  if (cursor && decoded && (decoded.space !== r.space || decoded.path !== r.listPath)) return restart();
993
- if (!prefix || !r.listPath || r.options?.raw) {
1014
+ if (prefix === void 0 || r.options?.raw) {
994
1015
  if (cursor && decoded) return restart();
995
1016
  const result = await r.network();
996
- readEvent("list", r.listPath, prefix ?? null, "network", prefix ? "unsupported_option" : "not_covered", result.ok ? "found" : "error", started);
1017
+ readEvent("list", r.listPath, prefix ?? null, "network", prefix !== void 0 ? "unsupported_option" : "not_covered", result.ok ? "found" : "error", started);
997
1018
  return result;
998
1019
  }
999
1020
  let handle;
@@ -1088,7 +1109,7 @@ function createKVReplication(deps) {
1088
1109
  async function write(r) {
1089
1110
  const covered = r.entries.flatMap((entry) => {
1090
1111
  const prefix = configuredPrefix(entry.path);
1091
- return prefix ? [{ key: entry.path, op: r.op === "delete" ? "delete" : "put" }] : [];
1112
+ return prefix !== void 0 ? [{ key: entry.path, op: r.op === "delete" ? "delete" : "put" }] : [];
1092
1113
  });
1093
1114
  if (covered.length === 0) return r.network();
1094
1115
  await reportExistingPins();
@@ -1164,6 +1185,9 @@ function createKVReplication(deps) {
1164
1185
  const persistedSnapshot = pending.readExisting ? await pending.readExisting() : await pending.read();
1165
1186
  const snapshot = persistedSnapshot ?? emptyPendingState(identity);
1166
1187
  const entries = await Promise.all(options.prefixes.map(async (prefix) => {
1188
+ if (prefix === "/" && deps.nodeFeatures !== void 0 && !deps.nodeFeatures.includes("kv-sync-root")) {
1189
+ markRootUnsupported(prefix);
1190
+ }
1167
1191
  const records = snapshot.records.filter((r) => kvPrefixCovers(prefix, r.key));
1168
1192
  const opened = replicas.get(prefix);
1169
1193
  let local;
@@ -1178,12 +1202,13 @@ function createKVReplication(deps) {
1178
1202
  local ?? (local = inspected.local);
1179
1203
  if (inspected.missing && !opened?.reason && !opened?.authorityRevoked && !opened?.opening && !isClosed) return void 0;
1180
1204
  const revoked = opened?.authorityRevoked === true || local?.authority.state === "revoked";
1181
- 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";
1205
+ const rootUnsupported = opened?.lastError === "REPLICATION_ROOT_UNSUPPORTED";
1206
+ 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";
1182
1207
  return {
1183
1208
  prefix,
1184
1209
  state,
1185
1210
  ...opened?.reason ? { reason: opened.reason } : inspected.errorCode ? { reason: "replica_unavailable" } : {},
1186
- ...inspected.errorCode ? { errorCode: inspected.errorCode } : {},
1211
+ ...inspected.errorCode ?? opened?.lastError ? { errorCode: inspected.errorCode ?? opened?.lastError } : {},
1187
1212
  ...local ?? {},
1188
1213
  ...revoked ? { authority: { state: "revoked", expiresAt: local?.authority.expiresAt ?? null } } : {},
1189
1214
  pending: {
@@ -1198,7 +1223,7 @@ function createKVReplication(deps) {
1198
1223
  return entries.filter((entry) => entry !== void 0);
1199
1224
  }
1200
1225
  async function sync(o) {
1201
- const prefixes = o?.prefix ? options.prefixes.filter((p) => p === o.prefix) : options.prefixes;
1226
+ const prefixes = o?.prefix !== void 0 ? options.prefixes.filter((p) => p === o.prefix) : options.prefixes;
1202
1227
  await Promise.all(prefixes.map((prefix) => syncPrefix(prefix, "manual", o?.signal).then(() => void 0)));
1203
1228
  }
1204
1229
  async function clearPending2() {