@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.
- package/dist/index.cjs +14 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +14 -8
- package/dist/index.js.map +1 -1
- package/dist/kv/index.cjs +14 -8
- package/dist/kv/index.cjs.map +1 -1
- package/dist/kv/index.d.cts +3 -2
- package/dist/kv/index.d.ts +3 -2
- package/dist/kv/index.js +14 -8
- package/dist/kv/index.js.map +1 -1
- package/dist/kv/replication/index.cjs +42 -19
- package/dist/kv/replication/index.cjs.map +1 -1
- package/dist/kv/replication/index.d.cts +3 -2
- package/dist/kv/replication/index.d.ts +3 -2
- package/dist/kv/replication/index.js +42 -19
- package/dist/kv/replication/index.js.map +1 -1
- package/dist/kv/replication/prefix.cjs +43 -0
- package/dist/kv/replication/prefix.cjs.map +1 -0
- package/dist/kv/replication/prefix.d.cts +6 -0
- package/dist/kv/replication/prefix.d.ts +6 -0
- package/dist/kv/replication/prefix.js +17 -0
- package/dist/kv/replication/prefix.js.map +1 -0
- package/dist/{scope-BmLxwyl-.d.ts → scope-BKge7zDT.d.ts} +4 -4
- package/dist/{scope-D855kRDx.d.cts → scope-C05VVAXH.d.cts} +4 -4
- package/package.json +14 -1
|
@@ -77,16 +77,17 @@ ${canonical.space}
|
|
|
77
77
|
${canonical.principal}`;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
// src/kv/replication/
|
|
80
|
+
// src/kv/replication/prefix.ts
|
|
81
81
|
function kvPrefixCovers(prefix, key) {
|
|
82
|
-
|
|
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
|
}
|
|
@@ -371,9 +372,9 @@ function cursorRestart() {
|
|
|
371
372
|
}
|
|
372
373
|
function localList(handle, space, path, limit, cursor, validateMeta) {
|
|
373
374
|
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);
|
|
375
|
+
const exact = path !== "" && !path.endsWith("/") && (after === void 0 || utf8Compare(path, after) > 0) ? awaitExact(handle, path, after, validateMeta) : Promise.resolve(false);
|
|
375
376
|
return exact.then(async (hasExact) => {
|
|
376
|
-
const child = path.endsWith("/") ? path : `${path}/`;
|
|
377
|
+
const child = path === "" ? "" : path.endsWith("/") ? path : `${path}/`;
|
|
377
378
|
const want = limit === void 0 ? void 0 : limit + 1 - (hasExact ? 1 : 0);
|
|
378
379
|
const children = await handle.list({ prefix: child, after, limit: want });
|
|
379
380
|
validateMeta(children.meta);
|
|
@@ -390,9 +391,7 @@ async function awaitExact(handle, path, after, validateMeta) {
|
|
|
390
391
|
return result.keys[0] === path;
|
|
391
392
|
}
|
|
392
393
|
function listPathCovered(prefixes, path) {
|
|
393
|
-
|
|
394
|
-
const covering = prefixes.filter((prefix) => kvPrefixCovers(prefix, path) && kvPrefixCovers(prefix, descendant));
|
|
395
|
-
return covering.length === 1 ? covering[0] : void 0;
|
|
394
|
+
return prefixes.find((prefix) => kvPrefixCovers(prefix, path));
|
|
396
395
|
}
|
|
397
396
|
|
|
398
397
|
// src/kv/replication/events.ts
|
|
@@ -421,6 +420,7 @@ function raceSignal(job, signal) {
|
|
|
421
420
|
}
|
|
422
421
|
function createKVReplication(deps) {
|
|
423
422
|
const { options, mode, storage, identity, session, authority, pending, scheduler } = deps;
|
|
423
|
+
const emit = (event) => emitEvent(deps.emit, scheduler, event);
|
|
424
424
|
const replicas = /* @__PURE__ */ new Map();
|
|
425
425
|
const statusInspections = /* @__PURE__ */ new Set();
|
|
426
426
|
let isClosed = false;
|
|
@@ -429,7 +429,6 @@ function createKVReplication(deps) {
|
|
|
429
429
|
let previous = deps.previous;
|
|
430
430
|
let pinnedStateReported = false;
|
|
431
431
|
const inProcessProof = /* @__PURE__ */ new Set();
|
|
432
|
-
const emit = (event) => emitEvent(deps.emit, scheduler, event);
|
|
433
432
|
const configuredPrefix = (path) => options.prefixes.find((prefix) => kvPrefixCovers(prefix, path));
|
|
434
433
|
function recordRevocation(prefix, error) {
|
|
435
434
|
if (errorCode(error) !== "GRANT_REVOKED") return;
|
|
@@ -439,6 +438,20 @@ function createKVReplication(deps) {
|
|
|
439
438
|
state.lastError = "GRANT_REVOKED";
|
|
440
439
|
replicas.set(prefix, state);
|
|
441
440
|
}
|
|
441
|
+
function markRootUnsupported(prefix) {
|
|
442
|
+
const state = replicas.get(prefix) ?? {};
|
|
443
|
+
const firstRefusal = state.lastError !== "REPLICATION_ROOT_UNSUPPORTED";
|
|
444
|
+
state.reason = "replica_unavailable";
|
|
445
|
+
state.lastError = "REPLICATION_ROOT_UNSUPPORTED";
|
|
446
|
+
replicas.set(prefix, state);
|
|
447
|
+
if (firstRefusal) {
|
|
448
|
+
emit({ type: "replication.state", space: session.space, replica: prefix, state: "unavailable", code: "REPLICATION_ROOT_UNSUPPORTED" });
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
function unsupportedRoot(prefix) {
|
|
452
|
+
markRootUnsupported(prefix);
|
|
453
|
+
return Object.assign(new Error("This node doesn't support whole-space sync yet."), { code: "REPLICATION_ROOT_UNSUPPORTED" });
|
|
454
|
+
}
|
|
442
455
|
async function open(prefix) {
|
|
443
456
|
if (isClosed || purgeStarted) throw Object.assign(new Error("Replication controller is closed"), { code: "REPLICA_CLOSED" });
|
|
444
457
|
let state = replicas.get(prefix);
|
|
@@ -472,6 +485,9 @@ function createKVReplication(deps) {
|
|
|
472
485
|
state.strategy = "session";
|
|
473
486
|
}
|
|
474
487
|
const spec = { identity, space: session.space, prefix, allowSecrets: options.allowSecrets, ...device ? { device } : {}, ...deps.fetch ? { fetch: deps.fetch } : {} };
|
|
488
|
+
if (prefix === "/" && deps.nodeFeatures !== void 0 && !deps.nodeFeatures.includes("kv-sync-root")) {
|
|
489
|
+
throw unsupportedRoot(prefix);
|
|
490
|
+
}
|
|
475
491
|
const handle = await storage.open(spec);
|
|
476
492
|
state.handle = handle;
|
|
477
493
|
emit({ type: "replication.state", space: session.space, replica: prefix, state: "opened" });
|
|
@@ -521,7 +537,7 @@ function createKVReplication(deps) {
|
|
|
521
537
|
state.lastError = code;
|
|
522
538
|
state.failures = (state.failures ?? 0) + 1;
|
|
523
539
|
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));
|
|
540
|
+
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
541
|
state.handle?.close().catch(() => void 0);
|
|
526
542
|
state.handle = void 0;
|
|
527
543
|
throw error;
|
|
@@ -538,6 +554,9 @@ function createKVReplication(deps) {
|
|
|
538
554
|
}, options.syncIntervalMs);
|
|
539
555
|
}
|
|
540
556
|
async function syncPrefix(prefix, trigger, signal, context) {
|
|
557
|
+
if (prefix === "/" && deps.nodeFeatures !== void 0 && !deps.nodeFeatures.includes("kv-sync-root")) {
|
|
558
|
+
throw unsupportedRoot(prefix);
|
|
559
|
+
}
|
|
541
560
|
const handle = await open(prefix);
|
|
542
561
|
const state = replicas.get(prefix);
|
|
543
562
|
if (state.sync) return state.sync;
|
|
@@ -839,7 +858,7 @@ function createKVReplication(deps) {
|
|
|
839
858
|
observeNetworkRequested({ op: "get", space: r.space, path: r.path, reason: "NETWORK_REQUESTED", outcome, latencyMs: scheduler.now() - started });
|
|
840
859
|
}
|
|
841
860
|
}
|
|
842
|
-
if (
|
|
861
|
+
if (prefix === void 0) {
|
|
843
862
|
const value = await r.network();
|
|
844
863
|
readEvent("get", r.path, null, "network", "not_covered", value.ok ? "found" : value.error.code === ErrorCodes.KV_NOT_FOUND ? "not_found" : "error", started);
|
|
845
864
|
return value;
|
|
@@ -990,10 +1009,10 @@ function createKVReplication(deps) {
|
|
|
990
1009
|
return cursorRestart();
|
|
991
1010
|
};
|
|
992
1011
|
if (cursor && decoded && (decoded.space !== r.space || decoded.path !== r.listPath)) return restart();
|
|
993
|
-
if (
|
|
1012
|
+
if (prefix === void 0 || r.options?.raw) {
|
|
994
1013
|
if (cursor && decoded) return restart();
|
|
995
1014
|
const result = await r.network();
|
|
996
|
-
readEvent("list", r.listPath, prefix ?? null, "network", prefix ? "unsupported_option" : "not_covered", result.ok ? "found" : "error", started);
|
|
1015
|
+
readEvent("list", r.listPath, prefix ?? null, "network", prefix !== void 0 ? "unsupported_option" : "not_covered", result.ok ? "found" : "error", started);
|
|
997
1016
|
return result;
|
|
998
1017
|
}
|
|
999
1018
|
let handle;
|
|
@@ -1088,7 +1107,7 @@ function createKVReplication(deps) {
|
|
|
1088
1107
|
async function write(r) {
|
|
1089
1108
|
const covered = r.entries.flatMap((entry) => {
|
|
1090
1109
|
const prefix = configuredPrefix(entry.path);
|
|
1091
|
-
return prefix ? [{ key: entry.path, op: r.op === "delete" ? "delete" : "put" }] : [];
|
|
1110
|
+
return prefix !== void 0 ? [{ key: entry.path, op: r.op === "delete" ? "delete" : "put" }] : [];
|
|
1092
1111
|
});
|
|
1093
1112
|
if (covered.length === 0) return r.network();
|
|
1094
1113
|
await reportExistingPins();
|
|
@@ -1164,6 +1183,9 @@ function createKVReplication(deps) {
|
|
|
1164
1183
|
const persistedSnapshot = pending.readExisting ? await pending.readExisting() : await pending.read();
|
|
1165
1184
|
const snapshot = persistedSnapshot ?? emptyPendingState(identity);
|
|
1166
1185
|
const entries = await Promise.all(options.prefixes.map(async (prefix) => {
|
|
1186
|
+
if (prefix === "/" && deps.nodeFeatures !== void 0 && !deps.nodeFeatures.includes("kv-sync-root")) {
|
|
1187
|
+
markRootUnsupported(prefix);
|
|
1188
|
+
}
|
|
1167
1189
|
const records = snapshot.records.filter((r) => kvPrefixCovers(prefix, r.key));
|
|
1168
1190
|
const opened = replicas.get(prefix);
|
|
1169
1191
|
let local;
|
|
@@ -1178,12 +1200,13 @@ function createKVReplication(deps) {
|
|
|
1178
1200
|
local ?? (local = inspected.local);
|
|
1179
1201
|
if (inspected.missing && !opened?.reason && !opened?.authorityRevoked && !opened?.opening && !isClosed) return void 0;
|
|
1180
1202
|
const revoked = opened?.authorityRevoked === true || local?.authority.state === "revoked";
|
|
1181
|
-
const
|
|
1203
|
+
const rootUnsupported = opened?.lastError === "REPLICATION_ROOT_UNSUPPORTED";
|
|
1204
|
+
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
1205
|
return {
|
|
1183
1206
|
prefix,
|
|
1184
1207
|
state,
|
|
1185
1208
|
...opened?.reason ? { reason: opened.reason } : inspected.errorCode ? { reason: "replica_unavailable" } : {},
|
|
1186
|
-
...inspected.errorCode ? { errorCode: inspected.errorCode } : {},
|
|
1209
|
+
...inspected.errorCode ?? opened?.lastError ? { errorCode: inspected.errorCode ?? opened?.lastError } : {},
|
|
1187
1210
|
...local ?? {},
|
|
1188
1211
|
...revoked ? { authority: { state: "revoked", expiresAt: local?.authority.expiresAt ?? null } } : {},
|
|
1189
1212
|
pending: {
|
|
@@ -1198,7 +1221,7 @@ function createKVReplication(deps) {
|
|
|
1198
1221
|
return entries.filter((entry) => entry !== void 0);
|
|
1199
1222
|
}
|
|
1200
1223
|
async function sync(o) {
|
|
1201
|
-
const prefixes = o?.prefix ? options.prefixes.filter((p) => p === o.prefix) : options.prefixes;
|
|
1224
|
+
const prefixes = o?.prefix !== void 0 ? options.prefixes.filter((p) => p === o.prefix) : options.prefixes;
|
|
1202
1225
|
await Promise.all(prefixes.map((prefix) => syncPrefix(prefix, "manual", o?.signal).then(() => void 0)));
|
|
1203
1226
|
}
|
|
1204
1227
|
async function clearPending2() {
|