@tinycloud/node-sdk 3.2.0-beta.1 → 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.
- package/dist/{core-C7-YdUWg.d.cts → WasmKeyProvider-DjfRu9aL.d.cts} +163 -3
- package/dist/{core-C7-YdUWg.d.ts → WasmKeyProvider-DjfRu9aL.d.ts} +163 -3
- package/dist/authority-mTQEgN56.d.cts +97 -0
- package/dist/authority-mTQEgN56.d.ts +97 -0
- package/dist/core.cjs +1076 -447
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +13 -3
- package/dist/core.d.ts +13 -3
- package/dist/core.js +753 -120
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +2382 -495
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -4
- package/dist/index.d.ts +34 -4
- package/dist/index.js +2032 -131
- package/dist/index.js.map +1 -1
- package/dist/replication/authority.cjs +278 -0
- package/dist/replication/authority.cjs.map +1 -0
- package/dist/replication/authority.d.cts +3 -0
- package/dist/replication/authority.d.ts +3 -0
- package/dist/replication/authority.js +257 -0
- package/dist/replication/authority.js.map +1 -0
- package/dist/replication/runtime.cjs +224 -0
- package/dist/replication/runtime.cjs.map +1 -0
- package/dist/replication/runtime.d.cts +3 -0
- package/dist/replication/runtime.d.ts +3 -0
- package/dist/replication/runtime.js +189 -0
- package/dist/replication/runtime.js.map +1 -0
- package/dist/runtime-DyYwHW8j.d.cts +64 -0
- package/dist/runtime-DyYwHW8j.d.ts +64 -0
- package/package.json +25 -3
package/dist/index.js
CHANGED
|
@@ -4,6 +4,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __getProtoOf = Object.getPrototypeOf;
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __typeError = (msg) => {
|
|
8
|
+
throw TypeError(msg);
|
|
9
|
+
};
|
|
7
10
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
8
11
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
9
12
|
}) : x)(function(x) {
|
|
@@ -37,6 +40,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
37
40
|
mod
|
|
38
41
|
));
|
|
39
42
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
43
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
44
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
45
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
46
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
47
|
+
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
40
48
|
|
|
41
49
|
// ../../node_modules/bn.js/lib/bn.js
|
|
42
50
|
var require_bn = __commonJS({
|
|
@@ -14355,8 +14363,8 @@ function searchPath(object, path) {
|
|
|
14355
14363
|
}
|
|
14356
14364
|
return currentChild;
|
|
14357
14365
|
}
|
|
14358
|
-
function uuidV4(
|
|
14359
|
-
const bytes = arrayify(
|
|
14366
|
+
function uuidV4(randomBytes3) {
|
|
14367
|
+
const bytes = arrayify(randomBytes3);
|
|
14360
14368
|
bytes[6] = bytes[6] & 15 | 64;
|
|
14361
14369
|
bytes[8] = bytes[8] & 63 | 128;
|
|
14362
14370
|
const value = hexlify(bytes);
|
|
@@ -17027,6 +17035,477 @@ var require_utils2 = __commonJS({
|
|
|
17027
17035
|
}
|
|
17028
17036
|
});
|
|
17029
17037
|
|
|
17038
|
+
// src/replication/config.ts
|
|
17039
|
+
import { kvPrefixCovers } from "@tinycloud/sdk-services";
|
|
17040
|
+
function assertValidReplicationConfig(replication) {
|
|
17041
|
+
if (replication === void 0 || replication.enabled !== true) return;
|
|
17042
|
+
if (replication.storage === void 0 || replication.storage === null) {
|
|
17043
|
+
throw new TypeError("replication.enabled requires replication.storage");
|
|
17044
|
+
}
|
|
17045
|
+
if (!Array.isArray(replication.prefixes) || replication.prefixes.length === 0) {
|
|
17046
|
+
throw new TypeError("replication.prefixes must be a non-empty array");
|
|
17047
|
+
}
|
|
17048
|
+
for (const prefix of replication.prefixes) {
|
|
17049
|
+
if (typeof prefix !== "string" || prefix === "") {
|
|
17050
|
+
throw new TypeError("replication.prefixes must not contain an empty prefix");
|
|
17051
|
+
}
|
|
17052
|
+
}
|
|
17053
|
+
for (const [index, prefix] of replication.prefixes.entries()) {
|
|
17054
|
+
for (const other of replication.prefixes.slice(index + 1)) {
|
|
17055
|
+
if (kvPrefixCovers(prefix, other) || kvPrefixCovers(other, prefix)) {
|
|
17056
|
+
throw new TypeError(
|
|
17057
|
+
`replication.prefixes must not overlap: ${JSON.stringify(prefix)} and ${JSON.stringify(other)}`
|
|
17058
|
+
);
|
|
17059
|
+
}
|
|
17060
|
+
}
|
|
17061
|
+
if (prefix.split("/", 1)[0] === "vault" && replication.allowSecrets !== true) {
|
|
17062
|
+
throw new TypeError(
|
|
17063
|
+
`replication prefix ${JSON.stringify(prefix)} overlaps the vault namespace; pass allowSecrets to opt in`
|
|
17064
|
+
);
|
|
17065
|
+
}
|
|
17066
|
+
}
|
|
17067
|
+
}
|
|
17068
|
+
var init_config = __esm({
|
|
17069
|
+
"src/replication/config.ts"() {
|
|
17070
|
+
"use strict";
|
|
17071
|
+
}
|
|
17072
|
+
});
|
|
17073
|
+
|
|
17074
|
+
// src/replication/authority-sign-in.ts
|
|
17075
|
+
import {
|
|
17076
|
+
grantPathsForSelector,
|
|
17077
|
+
KV,
|
|
17078
|
+
isCapabilitySubset
|
|
17079
|
+
} from "@tinycloud/sdk-core";
|
|
17080
|
+
import { requiresSecretsOptIn } from "@tinycloud/sdk-services";
|
|
17081
|
+
function hasUnrestrictedGetCoverage(entries, spaceId, prefix) {
|
|
17082
|
+
const requiredPaths = grantPathsForSelector(prefix);
|
|
17083
|
+
return isCapabilitySubset(
|
|
17084
|
+
requiredPaths.map((path) => ({ service: KV_SERVICE, space: spaceId, path, actions: [KV_GET] })),
|
|
17085
|
+
[...entries]
|
|
17086
|
+
).subset;
|
|
17087
|
+
}
|
|
17088
|
+
function augmentSignInEntriesWithReplication(input) {
|
|
17089
|
+
const out = [];
|
|
17090
|
+
const replication = input.replication;
|
|
17091
|
+
if (replication === void 0) return out;
|
|
17092
|
+
for (const prefix of replication.prefixes) {
|
|
17093
|
+
if (requiresSecretsOptIn(input.primarySpaceId, prefix) && replication.allowSecrets !== true) {
|
|
17094
|
+
continue;
|
|
17095
|
+
}
|
|
17096
|
+
if (!hasUnrestrictedGetCoverage(input.entries, input.primarySpaceId, prefix)) {
|
|
17097
|
+
continue;
|
|
17098
|
+
}
|
|
17099
|
+
for (const path of grantPathsForSelector(prefix)) {
|
|
17100
|
+
out.push({
|
|
17101
|
+
service: KV_SERVICE,
|
|
17102
|
+
space: input.primarySpaceId,
|
|
17103
|
+
path,
|
|
17104
|
+
actions: [KV_GET, KV_SYNC]
|
|
17105
|
+
});
|
|
17106
|
+
}
|
|
17107
|
+
}
|
|
17108
|
+
return out;
|
|
17109
|
+
}
|
|
17110
|
+
var KV_GET, KV_SYNC, KV_SERVICE;
|
|
17111
|
+
var init_authority_sign_in = __esm({
|
|
17112
|
+
"src/replication/authority-sign-in.ts"() {
|
|
17113
|
+
"use strict";
|
|
17114
|
+
KV_GET = KV.GET;
|
|
17115
|
+
KV_SYNC = KV.SYNC;
|
|
17116
|
+
KV_SERVICE = "tinycloud.kv";
|
|
17117
|
+
}
|
|
17118
|
+
});
|
|
17119
|
+
|
|
17120
|
+
// src/replication/runtime.ts
|
|
17121
|
+
var runtime_exports = {};
|
|
17122
|
+
__export(runtime_exports, {
|
|
17123
|
+
ReplicationRuntime: () => ReplicationRuntime
|
|
17124
|
+
});
|
|
17125
|
+
import { pkhDid as pkhDid3 } from "@tinycloud/sdk-core";
|
|
17126
|
+
function resolvedOptions(options) {
|
|
17127
|
+
return {
|
|
17128
|
+
...options,
|
|
17129
|
+
syncIntervalMs: options.syncIntervalMs ?? 6e4,
|
|
17130
|
+
maxStalenessMs: options.maxStalenessMs ?? 12e4,
|
|
17131
|
+
staleSyncTimeoutMs: options.staleSyncTimeoutMs ?? 5e3,
|
|
17132
|
+
allowSecrets: options.allowSecrets === true,
|
|
17133
|
+
verify: options.verify === true
|
|
17134
|
+
};
|
|
17135
|
+
}
|
|
17136
|
+
var scheduler, ReplicationRuntime;
|
|
17137
|
+
var init_runtime = __esm({
|
|
17138
|
+
"src/replication/runtime.ts"() {
|
|
17139
|
+
"use strict";
|
|
17140
|
+
scheduler = {
|
|
17141
|
+
now: () => Date.now(),
|
|
17142
|
+
setTimeout(fn, ms) {
|
|
17143
|
+
const timer = setTimeout(fn, ms);
|
|
17144
|
+
timer.unref?.();
|
|
17145
|
+
return () => clearTimeout(timer);
|
|
17146
|
+
}
|
|
17147
|
+
};
|
|
17148
|
+
ReplicationRuntime = class {
|
|
17149
|
+
constructor(options, clock = scheduler) {
|
|
17150
|
+
this.options = options;
|
|
17151
|
+
this.clock = clock;
|
|
17152
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
17153
|
+
this.attached = /* @__PURE__ */ new Map();
|
|
17154
|
+
this.previous = Promise.resolve();
|
|
17155
|
+
this.binding = Promise.resolve();
|
|
17156
|
+
this.generation = 0;
|
|
17157
|
+
this.bindingActive = false;
|
|
17158
|
+
this.pendingPurgeSet = false;
|
|
17159
|
+
this.closed = false;
|
|
17160
|
+
this.control = {
|
|
17161
|
+
status: async () => {
|
|
17162
|
+
await this.binding;
|
|
17163
|
+
return this.current ? this.current.status() : [];
|
|
17164
|
+
},
|
|
17165
|
+
sync: async (input) => {
|
|
17166
|
+
await this.binding;
|
|
17167
|
+
if (this.purgedGeneration === this.generation) return;
|
|
17168
|
+
if (this.current) await this.current.sync(input);
|
|
17169
|
+
},
|
|
17170
|
+
purge: (input) => {
|
|
17171
|
+
if (this.bindingActive) {
|
|
17172
|
+
this.pendingPurge = input;
|
|
17173
|
+
this.pendingPurgeSet = true;
|
|
17174
|
+
const existing = this.current ?? this.lastController;
|
|
17175
|
+
if (existing) {
|
|
17176
|
+
this.pendingPurgeResult = existing.purge(input);
|
|
17177
|
+
this.purgedGeneration = this.generation;
|
|
17178
|
+
}
|
|
17179
|
+
return this.binding.then(() => this.pendingPurgeResult ?? { purged: [], failed: [] });
|
|
17180
|
+
}
|
|
17181
|
+
const controller = this.current ?? this.lastController;
|
|
17182
|
+
if (!controller) return Promise.resolve({ purged: [], failed: [] });
|
|
17183
|
+
this.purgedGeneration = this.generation;
|
|
17184
|
+
return controller.purge(input);
|
|
17185
|
+
},
|
|
17186
|
+
clearPending: async () => {
|
|
17187
|
+
await this.binding;
|
|
17188
|
+
if (this.current) return this.current.clearPending();
|
|
17189
|
+
const pending = this.lastPending;
|
|
17190
|
+
if (!pending) return 0;
|
|
17191
|
+
return pending.update((state) => {
|
|
17192
|
+
const before = state.records.length;
|
|
17193
|
+
state.records = state.records.filter(
|
|
17194
|
+
(record) => record.state === "committed" || record.state === "in_flight" && this.clock.now() - Date.parse(record.at) <= 10 * 6e4
|
|
17195
|
+
);
|
|
17196
|
+
return before - state.records.length;
|
|
17197
|
+
});
|
|
17198
|
+
},
|
|
17199
|
+
close: async () => {
|
|
17200
|
+
this.closed = true;
|
|
17201
|
+
this.unbind();
|
|
17202
|
+
await this.binding.catch(() => void 0);
|
|
17203
|
+
await this.previous;
|
|
17204
|
+
}
|
|
17205
|
+
};
|
|
17206
|
+
}
|
|
17207
|
+
loadServices() {
|
|
17208
|
+
return this.services ?? (this.services = import("@tinycloud/sdk-services/kv/replication"));
|
|
17209
|
+
}
|
|
17210
|
+
detachAll() {
|
|
17211
|
+
for (const kv of this.attached.keys()) kv.setReadThrough(null);
|
|
17212
|
+
this.attached.clear();
|
|
17213
|
+
}
|
|
17214
|
+
bind(input) {
|
|
17215
|
+
if (this.closed || !this.options.enabled) return Promise.resolve();
|
|
17216
|
+
this.bindingActive = true;
|
|
17217
|
+
const generation = ++this.generation;
|
|
17218
|
+
this.purgedGeneration = void 0;
|
|
17219
|
+
const { context, session } = input;
|
|
17220
|
+
const emit = (event) => {
|
|
17221
|
+
try {
|
|
17222
|
+
this.options.onEvent?.(event);
|
|
17223
|
+
} catch {
|
|
17224
|
+
}
|
|
17225
|
+
const { type, ...data } = event;
|
|
17226
|
+
context.emit(type, data);
|
|
17227
|
+
};
|
|
17228
|
+
const previous = this.current;
|
|
17229
|
+
this.current = void 0;
|
|
17230
|
+
for (const kv of this.attached.keys()) kv.setReadThrough(null);
|
|
17231
|
+
this.primarySpace = session.spaceId;
|
|
17232
|
+
const close = previous ? previous.close().catch(() => void 0) : Promise.resolve();
|
|
17233
|
+
this.previous = this.previous.then(() => close);
|
|
17234
|
+
const previousClose = this.previous;
|
|
17235
|
+
this.binding = this.loadServices().then((services) => {
|
|
17236
|
+
if (generation !== this.generation || this.closed) return;
|
|
17237
|
+
let identity;
|
|
17238
|
+
try {
|
|
17239
|
+
identity = services.canonicalReplicationIdentity({
|
|
17240
|
+
host: context.hosts[0],
|
|
17241
|
+
space: session.spaceId,
|
|
17242
|
+
principal: input.principal ?? pkhDid3(input.address, input.chainId)
|
|
17243
|
+
});
|
|
17244
|
+
} catch {
|
|
17245
|
+
for (const prefix of this.options.prefixes) {
|
|
17246
|
+
emit({ type: "replication.state", at: new Date(this.clock.now()).toISOString(), space: session.spaceId, replica: prefix, state: "unavailable", code: "CONFIG_INVALID" });
|
|
17247
|
+
}
|
|
17248
|
+
return;
|
|
17249
|
+
}
|
|
17250
|
+
const key2 = services.replicationIdentityKey(identity);
|
|
17251
|
+
let pending = this.pending.get(key2);
|
|
17252
|
+
if (!pending) {
|
|
17253
|
+
pending = this.options.storage.pendingWrites?.(identity) ?? services.createMemoryPendingStore(identity);
|
|
17254
|
+
this.pending.set(key2, pending);
|
|
17255
|
+
}
|
|
17256
|
+
this.lastPending = pending;
|
|
17257
|
+
const controller = services.createKVReplication({
|
|
17258
|
+
options: resolvedOptions(this.options),
|
|
17259
|
+
mode: this.options.mode ?? "background",
|
|
17260
|
+
storage: this.options.storage,
|
|
17261
|
+
identity,
|
|
17262
|
+
session: { id: session.delegationCid, did: session.verificationMethod, space: session.spaceId },
|
|
17263
|
+
authority: input.authority,
|
|
17264
|
+
pending,
|
|
17265
|
+
scheduler: this.clock,
|
|
17266
|
+
emit,
|
|
17267
|
+
fetch: context.fetch,
|
|
17268
|
+
previous: previousClose
|
|
17269
|
+
});
|
|
17270
|
+
this.current = controller;
|
|
17271
|
+
this.lastController = controller;
|
|
17272
|
+
this.lastIdentity = identity;
|
|
17273
|
+
for (const [kv, space] of this.attached) {
|
|
17274
|
+
kv.setReadThrough(space === this.primarySpace ? controller : null);
|
|
17275
|
+
}
|
|
17276
|
+
for (const scoped of input.primaryKV) this.attach(scoped.space, scoped.kv);
|
|
17277
|
+
if (this.pendingPurgeSet) {
|
|
17278
|
+
this.pendingPurgeResult = controller.purge(this.pendingPurge);
|
|
17279
|
+
this.purgedGeneration = generation;
|
|
17280
|
+
this.pendingPurge = void 0;
|
|
17281
|
+
this.pendingPurgeSet = false;
|
|
17282
|
+
}
|
|
17283
|
+
}).finally(() => {
|
|
17284
|
+
if (generation === this.generation) this.bindingActive = false;
|
|
17285
|
+
});
|
|
17286
|
+
for (const kv of this.attached.keys()) kv.setReadThroughReady?.(this.binding);
|
|
17287
|
+
return this.binding;
|
|
17288
|
+
}
|
|
17289
|
+
attach(space, kv) {
|
|
17290
|
+
this.attached.set(kv, space);
|
|
17291
|
+
kv.setReadThrough(space === this.primarySpace ? this.current ?? null : null);
|
|
17292
|
+
return this.binding.then(() => {
|
|
17293
|
+
kv.setReadThrough(space === this.primarySpace ? this.current ?? null : null);
|
|
17294
|
+
});
|
|
17295
|
+
}
|
|
17296
|
+
unbind() {
|
|
17297
|
+
this.generation++;
|
|
17298
|
+
this.bindingActive = false;
|
|
17299
|
+
this.pendingPurgeSet = false;
|
|
17300
|
+
const controller = this.current;
|
|
17301
|
+
this.current = void 0;
|
|
17302
|
+
this.primarySpace = void 0;
|
|
17303
|
+
this.detachAll();
|
|
17304
|
+
const binding = this.binding;
|
|
17305
|
+
const close = binding.then(() => controller?.close()).catch(() => void 0);
|
|
17306
|
+
this.previous = this.previous.then(() => close);
|
|
17307
|
+
return this.previous;
|
|
17308
|
+
}
|
|
17309
|
+
get boundIdentity() {
|
|
17310
|
+
return this.lastIdentity;
|
|
17311
|
+
}
|
|
17312
|
+
};
|
|
17313
|
+
}
|
|
17314
|
+
});
|
|
17315
|
+
|
|
17316
|
+
// src/replication/authority.ts
|
|
17317
|
+
var authority_exports = {};
|
|
17318
|
+
__export(authority_exports, {
|
|
17319
|
+
AUTHORITY_EXPIRY_MARGIN_MS: () => AUTHORITY_EXPIRY_MARGIN_MS,
|
|
17320
|
+
assertValidReplicationConfig: () => assertValidReplicationConfig,
|
|
17321
|
+
augmentSignInEntriesWithReplication: () => augmentSignInEntriesWithReplication,
|
|
17322
|
+
createReplicationAuthority: () => createReplicationAuthority,
|
|
17323
|
+
hasUnrestrictedGetCoverage: () => hasUnrestrictedGetCoverage,
|
|
17324
|
+
replicationScopeFromSignedSession: () => replicationScopeFromSignedSession,
|
|
17325
|
+
ucanAttCovers: () => ucanAttCovers,
|
|
17326
|
+
ucanAttUnconstrainedFor: () => ucanAttUnconstrainedFor
|
|
17327
|
+
});
|
|
17328
|
+
import {
|
|
17329
|
+
grantPathsForSelector as grantPathsForSelector2,
|
|
17330
|
+
actionContains,
|
|
17331
|
+
KV as KV4,
|
|
17332
|
+
canonicalizeRecapCaveats as canonicalizeRecapCaveats3,
|
|
17333
|
+
isCapabilitySubset as isCapabilitySubset3,
|
|
17334
|
+
parseSpaceUri as parseSpaceUri2
|
|
17335
|
+
} from "@tinycloud/sdk-core";
|
|
17336
|
+
import { parseUcanGrant as replicaParseUcanGrant } from "@tinycloud/replica";
|
|
17337
|
+
function replicationEntries(spaceId, prefix) {
|
|
17338
|
+
return grantPathsForSelector2(prefix).map((path) => ({
|
|
17339
|
+
service: KV_SERVICE2,
|
|
17340
|
+
space: spaceId,
|
|
17341
|
+
path,
|
|
17342
|
+
actions: [KV_GET2, KV_SYNC2]
|
|
17343
|
+
}));
|
|
17344
|
+
}
|
|
17345
|
+
function caveatBranchesUnconstrained(value) {
|
|
17346
|
+
if (value === void 0 || value === null) return true;
|
|
17347
|
+
if (typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0) {
|
|
17348
|
+
return true;
|
|
17349
|
+
}
|
|
17350
|
+
if (!Array.isArray(value)) return false;
|
|
17351
|
+
try {
|
|
17352
|
+
return canonicalizeRecapCaveats3(value) === canonicalizeRecapCaveats3(void 0);
|
|
17353
|
+
} catch {
|
|
17354
|
+
return false;
|
|
17355
|
+
}
|
|
17356
|
+
}
|
|
17357
|
+
function kvPathInResource(resource, space) {
|
|
17358
|
+
for (const base2 of [`${space}/kv/`, `tinycloud://${space}/kv/`]) {
|
|
17359
|
+
if (resource.startsWith(base2)) return resource.slice(base2.length);
|
|
17360
|
+
}
|
|
17361
|
+
return void 0;
|
|
17362
|
+
}
|
|
17363
|
+
function ucanAttCovers(att, space, prefix, ability, options) {
|
|
17364
|
+
const requiredPaths = prefix.endsWith("/") ? [prefix] : [prefix, `${prefix}/`];
|
|
17365
|
+
return requiredPaths.every((requestedPath) => {
|
|
17366
|
+
for (const [resource, abilities] of Object.entries(att)) {
|
|
17367
|
+
const path = kvPathInResource(resource, space);
|
|
17368
|
+
if (path === void 0) continue;
|
|
17369
|
+
for (const [granted, caveats] of Object.entries(abilities)) {
|
|
17370
|
+
if (!actionContains(granted, ability)) continue;
|
|
17371
|
+
if (!isCapabilitySubset3(
|
|
17372
|
+
[{ service: KV_SERVICE2, space, path: requestedPath, actions: [ability] }],
|
|
17373
|
+
[{ service: KV_SERVICE2, space, path, actions: [granted] }]
|
|
17374
|
+
).subset) continue;
|
|
17375
|
+
if (options?.ignoreCaveats === true || caveatBranchesUnconstrained(caveats)) return true;
|
|
17376
|
+
}
|
|
17377
|
+
}
|
|
17378
|
+
return false;
|
|
17379
|
+
});
|
|
17380
|
+
}
|
|
17381
|
+
function ucanAttUnconstrainedFor(att, space, prefix) {
|
|
17382
|
+
return ucanAttCovers(att, space, prefix, KV_GET2) && ucanAttCovers(att, space, prefix, KV_SYNC2);
|
|
17383
|
+
}
|
|
17384
|
+
function replicationScopeFromSignedSession(input) {
|
|
17385
|
+
let grant;
|
|
17386
|
+
try {
|
|
17387
|
+
grant = parseUcanGrant(input.delegationHeader.Authorization);
|
|
17388
|
+
} catch {
|
|
17389
|
+
return void 0;
|
|
17390
|
+
}
|
|
17391
|
+
const bareDid = (value) => value.split("#", 1)[0];
|
|
17392
|
+
if (grant.cid !== input.delegationCid || bareDid(grant.audience) !== bareDid(input.verificationMethod)) return void 0;
|
|
17393
|
+
for (const resource of Object.keys(grant.att)) {
|
|
17394
|
+
const space = resource.match(/^(?:tinycloud:\/\/)?(.+?)\/kv\//)?.[1];
|
|
17395
|
+
if (space !== input.persistedSpace) continue;
|
|
17396
|
+
const owner = parseSpaceUri2(space);
|
|
17397
|
+
if (owner?.owner && owner.address && owner.chainId) return { space, principal: owner.owner };
|
|
17398
|
+
}
|
|
17399
|
+
return void 0;
|
|
17400
|
+
}
|
|
17401
|
+
function createReplicationAuthority(host) {
|
|
17402
|
+
const planOrRefusal = (entries) => {
|
|
17403
|
+
const plan = host.planDelegation(entries);
|
|
17404
|
+
if ("refused" in plan) return plan;
|
|
17405
|
+
return { path: plan.path, parentCid: plan.parentCid, expiresAt: plan.expiresAt };
|
|
17406
|
+
};
|
|
17407
|
+
const sessionAudience = (value) => value.split("#", 1)[0];
|
|
17408
|
+
const signedSessionGrant = (session) => {
|
|
17409
|
+
const grant = parseUcanGrant(session.delegationHeader.Authorization);
|
|
17410
|
+
if (grant.cid !== session.delegationCid || sessionAudience(grant.audience) !== sessionAudience(session.verificationMethod)) return void 0;
|
|
17411
|
+
return grant;
|
|
17412
|
+
};
|
|
17413
|
+
const authority = {
|
|
17414
|
+
get sessionOnly() {
|
|
17415
|
+
const session = host.replicationSession();
|
|
17416
|
+
return session !== void 0 && session.siwe === void 0;
|
|
17417
|
+
},
|
|
17418
|
+
sessionGrant(prefix) {
|
|
17419
|
+
const session = host.replicationSession();
|
|
17420
|
+
if (session === void 0) return { refused: "SESSION_EXPIRING" };
|
|
17421
|
+
const siweExpiry = session.siwe ? host.siweExpiration(session.siwe)?.getTime() : void 0;
|
|
17422
|
+
if (siweExpiry !== void 0 && siweExpiry <= Date.now() + AUTHORITY_EXPIRY_MARGIN_MS) {
|
|
17423
|
+
return { refused: "SESSION_EXPIRING" };
|
|
17424
|
+
}
|
|
17425
|
+
let grant;
|
|
17426
|
+
try {
|
|
17427
|
+
const verified = signedSessionGrant(session);
|
|
17428
|
+
if (!verified) return { refused: "NOT_COVERED" };
|
|
17429
|
+
grant = verified;
|
|
17430
|
+
} catch {
|
|
17431
|
+
return { refused: "NOT_COVERED" };
|
|
17432
|
+
}
|
|
17433
|
+
const tokenExpiry = grant.expiresAt === null ? void 0 : grant.expiresAt * 1e3;
|
|
17434
|
+
if (tokenExpiry === void 0 || tokenExpiry <= Date.now() + AUTHORITY_EXPIRY_MARGIN_MS) {
|
|
17435
|
+
return { refused: "SESSION_EXPIRING" };
|
|
17436
|
+
}
|
|
17437
|
+
const covered = ucanAttCovers(grant.att, session.spaceId, prefix, KV_GET2, { ignoreCaveats: true }) && ucanAttCovers(grant.att, session.spaceId, prefix, KV_SYNC2, { ignoreCaveats: true });
|
|
17438
|
+
if (!covered) return { refused: "NOT_COVERED" };
|
|
17439
|
+
if (!ucanAttUnconstrainedFor(grant.att, session.spaceId, prefix)) {
|
|
17440
|
+
return { refused: "CAVEATED_AUTHORITY" };
|
|
17441
|
+
}
|
|
17442
|
+
return {
|
|
17443
|
+
ucan: grant.jwt,
|
|
17444
|
+
device: { did: session.verificationMethod, jwk: session.jwk }
|
|
17445
|
+
};
|
|
17446
|
+
},
|
|
17447
|
+
plan(prefix) {
|
|
17448
|
+
const session = host.replicationSession();
|
|
17449
|
+
if (session === void 0) return { refused: "SESSION_EXPIRING" };
|
|
17450
|
+
const sessionGrant = this.sessionGrant(prefix);
|
|
17451
|
+
if (!("refused" in sessionGrant)) {
|
|
17452
|
+
const signed2 = signedSessionGrant(session);
|
|
17453
|
+
if (!signed2 || signed2.expiresAt === null) return { refused: "NOT_COVERED" };
|
|
17454
|
+
return {
|
|
17455
|
+
path: "session",
|
|
17456
|
+
parentCid: session.delegationCid,
|
|
17457
|
+
expiresAt: signed2.expiresAt * 1e3
|
|
17458
|
+
};
|
|
17459
|
+
}
|
|
17460
|
+
if (authority.sessionOnly) return sessionGrant;
|
|
17461
|
+
return planOrRefusal(replicationEntries(session.spaceId, prefix));
|
|
17462
|
+
},
|
|
17463
|
+
async mint(deviceDid, prefix, signal) {
|
|
17464
|
+
if (authority.sessionOnly) throw new Error("compact delegate sessions cannot mint device grants");
|
|
17465
|
+
const session = host.replicationSession();
|
|
17466
|
+
if (session === void 0) throw new Error("replication mint without a session");
|
|
17467
|
+
const entries = replicationEntries(session.spaceId, prefix);
|
|
17468
|
+
const plan = planOrRefusal(entries);
|
|
17469
|
+
if ("refused" in plan) {
|
|
17470
|
+
throw Object.assign(
|
|
17471
|
+
new Error(`replication authority refused the mint: ${plan.refused}`),
|
|
17472
|
+
{ refused: plan.refused }
|
|
17473
|
+
);
|
|
17474
|
+
}
|
|
17475
|
+
signal.throwIfAborted();
|
|
17476
|
+
const minted = await new Promise((resolve, reject2) => {
|
|
17477
|
+
const onAbort = () => reject2(signal.reason ?? new Error("replication mint aborted"));
|
|
17478
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
17479
|
+
host.mintDelegation(deviceDid, entries).then(
|
|
17480
|
+
(result) => {
|
|
17481
|
+
signal.removeEventListener("abort", onAbort);
|
|
17482
|
+
resolve(result);
|
|
17483
|
+
},
|
|
17484
|
+
(error) => {
|
|
17485
|
+
signal.removeEventListener("abort", onAbort);
|
|
17486
|
+
reject2(error instanceof Error ? error : new Error(String(error)));
|
|
17487
|
+
}
|
|
17488
|
+
);
|
|
17489
|
+
});
|
|
17490
|
+
return { ucan: minted.ucan, parentCid: plan.parentCid, expiresAt: minted.expiresAt };
|
|
17491
|
+
}
|
|
17492
|
+
};
|
|
17493
|
+
return authority;
|
|
17494
|
+
}
|
|
17495
|
+
var parseUcanGrant, KV_GET2, KV_SYNC2, KV_SERVICE2, AUTHORITY_EXPIRY_MARGIN_MS;
|
|
17496
|
+
var init_authority = __esm({
|
|
17497
|
+
"src/replication/authority.ts"() {
|
|
17498
|
+
"use strict";
|
|
17499
|
+
init_config();
|
|
17500
|
+
init_authority_sign_in();
|
|
17501
|
+
parseUcanGrant = replicaParseUcanGrant;
|
|
17502
|
+
KV_GET2 = KV4.GET;
|
|
17503
|
+
KV_SYNC2 = KV4.SYNC;
|
|
17504
|
+
KV_SERVICE2 = "tinycloud.kv";
|
|
17505
|
+
AUTHORITY_EXPIRY_MARGIN_MS = 6e4;
|
|
17506
|
+
}
|
|
17507
|
+
});
|
|
17508
|
+
|
|
17030
17509
|
// src/NodeWasmBindings.ts
|
|
17031
17510
|
import {
|
|
17032
17511
|
invoke,
|
|
@@ -17179,7 +17658,9 @@ var RUNTIME_DELEGATION_REJECTIONS = [
|
|
|
17179
17658
|
"audience_mismatch",
|
|
17180
17659
|
"resources_mismatch",
|
|
17181
17660
|
"unsupported",
|
|
17182
|
-
"activation_rejected"
|
|
17661
|
+
"activation_rejected",
|
|
17662
|
+
"activation_unavailable",
|
|
17663
|
+
"activation_unauthenticated"
|
|
17183
17664
|
];
|
|
17184
17665
|
var RuntimeDelegationRejectedError = class extends Error {
|
|
17185
17666
|
constructor(reason, message) {
|
|
@@ -17188,6 +17669,18 @@ var RuntimeDelegationRejectedError = class extends Error {
|
|
|
17188
17669
|
this.reason = reason;
|
|
17189
17670
|
}
|
|
17190
17671
|
};
|
|
17672
|
+
function activationRejectionReason(error) {
|
|
17673
|
+
if (typeof error !== "object" || error === null) return "activation_rejected";
|
|
17674
|
+
if ("code" in error && error.code === "ACTIVATION_TRANSPORT_FAILED") {
|
|
17675
|
+
return "activation_unavailable";
|
|
17676
|
+
}
|
|
17677
|
+
const cause = "cause" in error ? error.cause : void 0;
|
|
17678
|
+
const status = typeof cause === "object" && cause !== null && "status" in cause ? cause.status : void 0;
|
|
17679
|
+
if (typeof status !== "number") return "activation_rejected";
|
|
17680
|
+
if (status === 408 || status === 429 || status >= 500) return "activation_unavailable";
|
|
17681
|
+
if (status === 401) return "activation_unauthenticated";
|
|
17682
|
+
return "activation_rejected";
|
|
17683
|
+
}
|
|
17191
17684
|
function reject(reason, message) {
|
|
17192
17685
|
throw new RuntimeDelegationRejectedError(reason, message);
|
|
17193
17686
|
}
|
|
@@ -17572,7 +18065,10 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
|
|
|
17572
18065
|
await node.useRuntimeDelegation(installedCandidate);
|
|
17573
18066
|
}
|
|
17574
18067
|
} catch (error) {
|
|
17575
|
-
reject(
|
|
18068
|
+
reject(
|
|
18069
|
+
activationRejectionReason(error),
|
|
18070
|
+
error instanceof Error ? error.message : String(error)
|
|
18071
|
+
);
|
|
17576
18072
|
}
|
|
17577
18073
|
const installed = node.getRuntimePermissionDelegations().find((candidate) => candidate.cid === cid);
|
|
17578
18074
|
if (!installed) {
|
|
@@ -17671,6 +18167,21 @@ function deserializeDelegation(data) {
|
|
|
17671
18167
|
};
|
|
17672
18168
|
}
|
|
17673
18169
|
|
|
18170
|
+
// src/TinyCloudNode.ts
|
|
18171
|
+
init_config();
|
|
18172
|
+
|
|
18173
|
+
// src/replication/module-registry.ts
|
|
18174
|
+
var nodeLoaders;
|
|
18175
|
+
function registerReplicationLoaders(loaders) {
|
|
18176
|
+
nodeLoaders = loaders;
|
|
18177
|
+
}
|
|
18178
|
+
function getNodeReplicationLoaders() {
|
|
18179
|
+
if (nodeLoaders === void 0) {
|
|
18180
|
+
throw new Error("Replication requires registering replication controller loaders.");
|
|
18181
|
+
}
|
|
18182
|
+
return nodeLoaders;
|
|
18183
|
+
}
|
|
18184
|
+
|
|
17674
18185
|
// src/TinyCloudNode.ts
|
|
17675
18186
|
import {
|
|
17676
18187
|
TinyCloud,
|
|
@@ -17709,12 +18220,12 @@ import {
|
|
|
17709
18220
|
canonicalizeRecapCaveats as canonicalizeRecapCaveats2,
|
|
17710
18221
|
recapCaveatsEqual,
|
|
17711
18222
|
expandPermissionEntries as expandPermissionEntriesCore,
|
|
17712
|
-
isCapabilitySubset,
|
|
18223
|
+
isCapabilitySubset as isCapabilitySubset2,
|
|
17713
18224
|
isExplicitOnlyAction,
|
|
17714
18225
|
parseRecapCapabilities,
|
|
17715
18226
|
SERVICE_LONG_TO_SHORT,
|
|
17716
18227
|
SERVICE_SHORT_TO_LONG,
|
|
17717
|
-
KV as
|
|
18228
|
+
KV as KV3,
|
|
17718
18229
|
SQL as SQL2,
|
|
17719
18230
|
DUCKDB as DUCKDB2,
|
|
17720
18231
|
ENCRYPTION as ENCRYPTION2,
|
|
@@ -17724,6 +18235,7 @@ import {
|
|
|
17724
18235
|
verifyDidKeyEd25519Signature,
|
|
17725
18236
|
canonicalizeAddress as canonicalizeAddress2,
|
|
17726
18237
|
pkhDid as pkhDid2,
|
|
18238
|
+
principalDid,
|
|
17727
18239
|
resolveTinyCloudHosts as resolveTinyCloudHosts2,
|
|
17728
18240
|
publishLocationRecord,
|
|
17729
18241
|
principalDidEquals as principalDidEquals3,
|
|
@@ -17743,6 +18255,7 @@ import {
|
|
|
17743
18255
|
} from "@tinycloud/sdk-services";
|
|
17744
18256
|
|
|
17745
18257
|
// src/authorization/NodeUserAuthorization.ts
|
|
18258
|
+
init_authority_sign_in();
|
|
17746
18259
|
import { createHash } from "crypto";
|
|
17747
18260
|
import { SiweMessage } from "siwe";
|
|
17748
18261
|
import {
|
|
@@ -17770,7 +18283,7 @@ import {
|
|
|
17770
18283
|
parseCanonicalRecapResource,
|
|
17771
18284
|
ACCOUNT_REGISTRY_SPACE,
|
|
17772
18285
|
ACCOUNT_MANIFEST_PERMISSIONS,
|
|
17773
|
-
KV,
|
|
18286
|
+
KV as KV2,
|
|
17774
18287
|
SQL,
|
|
17775
18288
|
DUCKDB,
|
|
17776
18289
|
CAPABILITIES,
|
|
@@ -17924,7 +18437,7 @@ var NodeUserAuthorization = class {
|
|
|
17924
18437
|
this.spacePrefix = config.spacePrefix ?? "default";
|
|
17925
18438
|
this.defaultActions = config.defaultActions ?? {
|
|
17926
18439
|
kv: {
|
|
17927
|
-
"": [
|
|
18440
|
+
"": [KV2.PUT, KV2.GET, KV2.DEL, KV2.LIST, KV2.METADATA]
|
|
17928
18441
|
},
|
|
17929
18442
|
sql: {
|
|
17930
18443
|
"": [SQL.READ, SQL.WRITE, SQL.SCHEMA, SQL.ADMIN, SQL.EXPORT]
|
|
@@ -17962,6 +18475,7 @@ var NodeUserAuthorization = class {
|
|
|
17962
18475
|
this.nonce = config.nonce;
|
|
17963
18476
|
this.siweConfig = config.siweConfig;
|
|
17964
18477
|
this.includeAccountRegistryPermissions = config.includeAccountRegistryPermissions ?? true;
|
|
18478
|
+
this.replication = config.replication?.enabled === true ? config.replication : void 0;
|
|
17965
18479
|
this._manifest = config.manifest;
|
|
17966
18480
|
this._capabilityRequest = config.capabilityRequest;
|
|
17967
18481
|
this.sessionManager = config.sessionManager ?? this.wasm.createSessionManager();
|
|
@@ -18170,6 +18684,143 @@ var NodeUserAuthorization = class {
|
|
|
18170
18684
|
}
|
|
18171
18685
|
return makePkhSpaceId(address, chainId, space);
|
|
18172
18686
|
}
|
|
18687
|
+
/**
|
|
18688
|
+
* The plain-session primary abilities: `defaultActions` (the shared table)
|
|
18689
|
+
* unless the primary space IS the account space, where account-registry
|
|
18690
|
+
* permissions merge into a private clone so the shared table is never
|
|
18691
|
+
* mutated. Shared between `resolveSignInCapabilities` and
|
|
18692
|
+
* `signInPermissionInputs` so sign-in and `replicationSignInEntries`
|
|
18693
|
+
* can't disagree (§4.6).
|
|
18694
|
+
*/
|
|
18695
|
+
primaryActionsForSignIn() {
|
|
18696
|
+
const primaryActions = this.includeAccountRegistryPermissions && this.spacePrefix === ACCOUNT_REGISTRY_SPACE ? cloneAbilitiesMap(this.defaultActions) : this.defaultActions;
|
|
18697
|
+
if (this.includeAccountRegistryPermissions && this.spacePrefix === ACCOUNT_REGISTRY_SPACE) {
|
|
18698
|
+
const accountAbilities = resourceCapabilitiesToAbilitiesMap(
|
|
18699
|
+
ACCOUNT_MANIFEST_PERMISSIONS
|
|
18700
|
+
);
|
|
18701
|
+
for (const [service, paths] of Object.entries(accountAbilities)) {
|
|
18702
|
+
const existingPaths = primaryActions[service] ?? (primaryActions[service] = {});
|
|
18703
|
+
for (const [path, actions] of Object.entries(paths)) {
|
|
18704
|
+
const existingActions = existingPaths[path] ?? (existingPaths[path] = []);
|
|
18705
|
+
for (const action of actions) {
|
|
18706
|
+
if (!existingActions.includes(action)) existingActions.push(action);
|
|
18707
|
+
}
|
|
18708
|
+
}
|
|
18709
|
+
}
|
|
18710
|
+
}
|
|
18711
|
+
return primaryActions;
|
|
18712
|
+
}
|
|
18713
|
+
/**
|
|
18714
|
+
* The abilities a `spacePrefix: "secrets"` plain session actually requests
|
|
18715
|
+
* for its primary space: the vault-secrets subtree only — the
|
|
18716
|
+
* `defaultActions` root `get` is REPLACED, not merged (sign-in installs
|
|
18717
|
+
* this map under the primary space id). Shared between
|
|
18718
|
+
* `resolveSignInCapabilities` and `signInPermissionInputs` so the
|
|
18719
|
+
* replication augmentation measures coverage against the effective
|
|
18720
|
+
* pre-replication request, never the uninstalled `primaryActions` (§4.1).
|
|
18721
|
+
*/
|
|
18722
|
+
secretsPrimaryAbilities() {
|
|
18723
|
+
return {
|
|
18724
|
+
kv: {
|
|
18725
|
+
"vault/secrets/": [
|
|
18726
|
+
KV2.GET,
|
|
18727
|
+
KV2.PUT,
|
|
18728
|
+
KV2.DEL,
|
|
18729
|
+
KV2.LIST,
|
|
18730
|
+
KV2.METADATA
|
|
18731
|
+
]
|
|
18732
|
+
}
|
|
18733
|
+
};
|
|
18734
|
+
}
|
|
18735
|
+
/**
|
|
18736
|
+
* The effective sign-in request as `PermissionEntry`s plus the resolved
|
|
18737
|
+
* primary space id — the inputs to the replication augmentation (§4.1).
|
|
18738
|
+
* `resolveSignInCapabilities` merges the augmented entries into its
|
|
18739
|
+
* abilities; `replicationSignInEntries()` returns them verbatim, so the
|
|
18740
|
+
* entries the SIWE recap carries and the restore check sees are the same.
|
|
18741
|
+
*/
|
|
18742
|
+
signInPermissionInputs(address, chainId) {
|
|
18743
|
+
const request = this.getCapabilityRequest();
|
|
18744
|
+
if (request === void 0) {
|
|
18745
|
+
const primarySpaceId2 = makePkhSpaceId(address, chainId, this.spacePrefix);
|
|
18746
|
+
const effectivePrimary = this.spacePrefix === "secrets" ? this.secretsPrimaryAbilities() : this.primaryActionsForSignIn();
|
|
18747
|
+
const kvPaths = effectivePrimary["kv"] ?? {};
|
|
18748
|
+
return {
|
|
18749
|
+
primarySpaceId: primarySpaceId2,
|
|
18750
|
+
entries: Object.entries(kvPaths).map(([path, actions]) => ({
|
|
18751
|
+
service: "tinycloud.kv",
|
|
18752
|
+
space: primarySpaceId2,
|
|
18753
|
+
path,
|
|
18754
|
+
actions: [...actions]
|
|
18755
|
+
}))
|
|
18756
|
+
};
|
|
18757
|
+
}
|
|
18758
|
+
const spaceResources = request.resources.filter(
|
|
18759
|
+
(entry) => entry.service !== ENCRYPTION_PERMISSION_SERVICE
|
|
18760
|
+
);
|
|
18761
|
+
const primarySpaceName = spaceResources.find((entry) => entry.space !== "account")?.space ?? DEFAULT_MANIFEST_SPACE;
|
|
18762
|
+
const primarySpaceId = this.resolveSpaceName(
|
|
18763
|
+
primarySpaceName,
|
|
18764
|
+
address,
|
|
18765
|
+
chainId
|
|
18766
|
+
);
|
|
18767
|
+
return {
|
|
18768
|
+
primarySpaceId,
|
|
18769
|
+
entries: spaceResources.map((entry) => ({
|
|
18770
|
+
service: entry.service,
|
|
18771
|
+
space: this.resolveSpaceName(entry.space, address, chainId),
|
|
18772
|
+
path: entry.path,
|
|
18773
|
+
actions: [...entry.actions]
|
|
18774
|
+
}))
|
|
18775
|
+
};
|
|
18776
|
+
}
|
|
18777
|
+
/**
|
|
18778
|
+
* Merge replication augmentation entries into a space's abilities map
|
|
18779
|
+
* without mutating shared tables (`defaultActions` may back the base map):
|
|
18780
|
+
* the primary entry in `spaceAbilities` is replaced with a copy whose `kv`
|
|
18781
|
+
* paths gain `get`/`sync`. Returns the merged map for `abilities`.
|
|
18782
|
+
*/
|
|
18783
|
+
mergeReplicationEntries(spaceAbilities, primarySpaceId, abilities, entries) {
|
|
18784
|
+
var _a;
|
|
18785
|
+
if (entries.length === 0) {
|
|
18786
|
+
return abilities;
|
|
18787
|
+
}
|
|
18788
|
+
const base2 = spaceAbilities[primarySpaceId] ?? abilities;
|
|
18789
|
+
const kv = {};
|
|
18790
|
+
for (const [path, actions] of Object.entries(base2["kv"] ?? {})) {
|
|
18791
|
+
kv[path] = [...actions];
|
|
18792
|
+
}
|
|
18793
|
+
for (const entry of entries) {
|
|
18794
|
+
const list = kv[_a = entry.path] ?? (kv[_a] = []);
|
|
18795
|
+
for (const action of entry.actions) {
|
|
18796
|
+
if (!list.includes(action)) list.push(action);
|
|
18797
|
+
}
|
|
18798
|
+
}
|
|
18799
|
+
const merged = { ...base2, kv };
|
|
18800
|
+
spaceAbilities[primarySpaceId] = merged;
|
|
18801
|
+
return merged;
|
|
18802
|
+
}
|
|
18803
|
+
/**
|
|
18804
|
+
* The replication entries this authorization's sign-in request carries
|
|
18805
|
+
* for the signed-in or restored address (TC-858 §4.1/§4.6): the output of
|
|
18806
|
+
* the same augmentation `resolveSignInCapabilities` runs — one
|
|
18807
|
+
* `{tinycloud.kv, primarySpace, prefix, [get, sync]}` per configured
|
|
18808
|
+
* prefix that already has an unrestricted covering `get` and passes the
|
|
18809
|
+
* secrets opt-in gate. Empty when replication is off or no address is
|
|
18810
|
+
* known. Pure.
|
|
18811
|
+
*/
|
|
18812
|
+
replicationSignInEntries() {
|
|
18813
|
+
if (this.replication === void 0) return [];
|
|
18814
|
+
const address = this._address ?? this._tinyCloudSession?.address;
|
|
18815
|
+
const chainId = this._chainId ?? this._tinyCloudSession?.chainId;
|
|
18816
|
+
if (address === void 0 || chainId === void 0) return [];
|
|
18817
|
+
const { entries, primarySpaceId } = this.signInPermissionInputs(address, chainId);
|
|
18818
|
+
return augmentSignInEntriesWithReplication({
|
|
18819
|
+
entries,
|
|
18820
|
+
primarySpaceId,
|
|
18821
|
+
replication: this.replication
|
|
18822
|
+
});
|
|
18823
|
+
}
|
|
18173
18824
|
defaultEncryptionNetworkId(address, chainId) {
|
|
18174
18825
|
return `urn:tinycloud:encryption:${pkhDid(address, chainId)}:default`;
|
|
18175
18826
|
}
|
|
@@ -18179,39 +18830,25 @@ var NodeUserAuthorization = class {
|
|
|
18179
18830
|
const defaultNetworkId = this.defaultEncryptionNetworkId(address, chainId);
|
|
18180
18831
|
const primarySpaceId2 = makePkhSpaceId(address, chainId, this.spacePrefix);
|
|
18181
18832
|
const secretsSpaceId = makePkhSpaceId(address, chainId, "secrets");
|
|
18182
|
-
const primaryActions = this.
|
|
18833
|
+
const primaryActions = this.primaryActionsForSignIn();
|
|
18183
18834
|
const spaceAbilities2 = {
|
|
18184
18835
|
[primarySpaceId2]: primaryActions,
|
|
18185
|
-
[secretsSpaceId]:
|
|
18186
|
-
kv: {
|
|
18187
|
-
"vault/secrets/": [
|
|
18188
|
-
KV.GET,
|
|
18189
|
-
KV.PUT,
|
|
18190
|
-
KV.DEL,
|
|
18191
|
-
KV.LIST,
|
|
18192
|
-
KV.METADATA
|
|
18193
|
-
]
|
|
18194
|
-
}
|
|
18195
|
-
}
|
|
18836
|
+
[secretsSpaceId]: this.secretsPrimaryAbilities()
|
|
18196
18837
|
};
|
|
18197
18838
|
if (this.includeAccountRegistryPermissions && this.spacePrefix !== ACCOUNT_REGISTRY_SPACE) {
|
|
18198
18839
|
spaceAbilities2[makePkhSpaceId(address, chainId, ACCOUNT_REGISTRY_SPACE)] = resourceCapabilitiesToAbilitiesMap(ACCOUNT_MANIFEST_PERMISSIONS);
|
|
18199
|
-
} else if (this.includeAccountRegistryPermissions) {
|
|
18200
|
-
const accountAbilities = resourceCapabilitiesToAbilitiesMap(
|
|
18201
|
-
ACCOUNT_MANIFEST_PERMISSIONS
|
|
18202
|
-
);
|
|
18203
|
-
for (const [service, paths] of Object.entries(accountAbilities)) {
|
|
18204
|
-
const existingPaths = primaryActions[service] ?? (primaryActions[service] = {});
|
|
18205
|
-
for (const [path, actions] of Object.entries(paths)) {
|
|
18206
|
-
const existingActions = existingPaths[path] ?? (existingPaths[path] = []);
|
|
18207
|
-
for (const action of actions) {
|
|
18208
|
-
if (!existingActions.includes(action)) existingActions.push(action);
|
|
18209
|
-
}
|
|
18210
|
-
}
|
|
18211
|
-
}
|
|
18212
18840
|
}
|
|
18841
|
+
const mergedPrimary = this.mergeReplicationEntries(
|
|
18842
|
+
spaceAbilities2,
|
|
18843
|
+
primarySpaceId2,
|
|
18844
|
+
primaryActions,
|
|
18845
|
+
augmentSignInEntriesWithReplication({
|
|
18846
|
+
...this.signInPermissionInputs(address, chainId),
|
|
18847
|
+
replication: this.replication
|
|
18848
|
+
})
|
|
18849
|
+
);
|
|
18213
18850
|
return {
|
|
18214
|
-
abilities:
|
|
18851
|
+
abilities: mergedPrimary,
|
|
18215
18852
|
spaceId: primarySpaceId2,
|
|
18216
18853
|
spaceAbilities: spaceAbilities2,
|
|
18217
18854
|
rawAbilities: {
|
|
@@ -18247,8 +18884,17 @@ var NodeUserAuthorization = class {
|
|
|
18247
18884
|
for (const [space, abilities] of Object.entries(bySpace)) {
|
|
18248
18885
|
spaceAbilities[this.resolveSpaceName(space, address, chainId)] = abilities;
|
|
18249
18886
|
}
|
|
18887
|
+
const mergedPrimaryAbilities = this.mergeReplicationEntries(
|
|
18888
|
+
spaceAbilities,
|
|
18889
|
+
primarySpaceId,
|
|
18890
|
+
spaceAbilities[primarySpaceId] ?? resourceCapabilitiesToAbilitiesMap([]),
|
|
18891
|
+
augmentSignInEntriesWithReplication({
|
|
18892
|
+
...this.signInPermissionInputs(address, chainId),
|
|
18893
|
+
replication: this.replication
|
|
18894
|
+
})
|
|
18895
|
+
);
|
|
18250
18896
|
return {
|
|
18251
|
-
abilities:
|
|
18897
|
+
abilities: mergedPrimaryAbilities,
|
|
18252
18898
|
spaceId: primarySpaceId,
|
|
18253
18899
|
spaceAbilities,
|
|
18254
18900
|
rawAbilities: Object.keys(rawAbilities).length > 0 ? rawAbilities : void 0
|
|
@@ -18306,14 +18952,14 @@ var NodeUserAuthorization = class {
|
|
|
18306
18952
|
* Create the space on the TinyCloud server (host delegation).
|
|
18307
18953
|
* This registers the user as the owner of the space.
|
|
18308
18954
|
*/
|
|
18309
|
-
async hostSpace(targetSpaceId, purpose) {
|
|
18955
|
+
async hostSpace(targetSpaceId, purpose, signal) {
|
|
18310
18956
|
if (!this._tinyCloudSession || !this._address || !this._chainId) {
|
|
18311
18957
|
throw new Error("Must be signed in to host space");
|
|
18312
18958
|
}
|
|
18313
18959
|
await this.ensureTinyCloudHosts();
|
|
18314
18960
|
const host = this.primaryTinyCloudHost;
|
|
18315
18961
|
const spaceId = targetSpaceId ?? this._tinyCloudSession.spaceId;
|
|
18316
|
-
const peerId = await fetchPeerId(host, spaceId);
|
|
18962
|
+
const peerId = await fetchPeerId(host, spaceId, signal);
|
|
18317
18963
|
const siwe = this.wasm.generateHostSIWEMessage({
|
|
18318
18964
|
address: this._address,
|
|
18319
18965
|
chainId: this._chainId,
|
|
@@ -18322,9 +18968,28 @@ var NodeUserAuthorization = class {
|
|
|
18322
18968
|
spaceId,
|
|
18323
18969
|
peerId
|
|
18324
18970
|
});
|
|
18325
|
-
|
|
18971
|
+
if (signal?.aborted) throw signal.reason;
|
|
18972
|
+
const signing = this.signMessage(siwe, purpose);
|
|
18973
|
+
let signature2;
|
|
18974
|
+
if (!signal) {
|
|
18975
|
+
signature2 = await signing;
|
|
18976
|
+
} else {
|
|
18977
|
+
let rejectOnAbort;
|
|
18978
|
+
const aborted = new Promise((_resolve, reject2) => {
|
|
18979
|
+
rejectOnAbort = reject2;
|
|
18980
|
+
});
|
|
18981
|
+
const onAbort = () => rejectOnAbort(signal.reason);
|
|
18982
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
18983
|
+
if (signal.aborted) onAbort();
|
|
18984
|
+
try {
|
|
18985
|
+
signature2 = await Promise.race([signing, aborted]);
|
|
18986
|
+
} finally {
|
|
18987
|
+
signal.removeEventListener("abort", onAbort);
|
|
18988
|
+
}
|
|
18989
|
+
}
|
|
18990
|
+
if (signal?.aborted) throw signal.reason;
|
|
18326
18991
|
const headers = this.wasm.siweToDelegationHeaders({ siwe, signature: signature2 });
|
|
18327
|
-
return submitHostDelegation(host, headers);
|
|
18992
|
+
return submitHostDelegation(host, headers, signal);
|
|
18328
18993
|
}
|
|
18329
18994
|
/**
|
|
18330
18995
|
* Create a specific space on the server via host delegation.
|
|
@@ -18345,8 +19010,8 @@ var NodeUserAuthorization = class {
|
|
|
18345
19010
|
* keeps its HTTP status (e.g. a typed 401/403) instead of collapsing to
|
|
18346
19011
|
* `false`.
|
|
18347
19012
|
*/
|
|
18348
|
-
async hostOwnedSpaceResult(spaceId, purpose) {
|
|
18349
|
-
return this.hostSpace(spaceId, purpose);
|
|
19013
|
+
async hostOwnedSpaceResult(spaceId, purpose, signal) {
|
|
19014
|
+
return this.hostSpace(spaceId, purpose, signal);
|
|
18350
19015
|
}
|
|
18351
19016
|
/**
|
|
18352
19017
|
* Ensure the user's space exists on the TinyCloud server.
|
|
@@ -19725,11 +20390,11 @@ function isSecretPayload(value) {
|
|
|
19725
20390
|
return typeof value === "object" && value !== null && "value" in value && typeof value.value === "string" && "createdAt" in value && typeof value.createdAt === "string" && "updatedAt" in value && typeof value.updatedAt === "string";
|
|
19726
20391
|
}
|
|
19727
20392
|
var ROOT_DELEGATION_ACTIONS = [
|
|
19728
|
-
|
|
19729
|
-
|
|
19730
|
-
|
|
19731
|
-
|
|
19732
|
-
|
|
20393
|
+
KV3.PUT,
|
|
20394
|
+
KV3.GET,
|
|
20395
|
+
KV3.DEL,
|
|
20396
|
+
KV3.LIST,
|
|
20397
|
+
KV3.METADATA,
|
|
19733
20398
|
SQL2.READ,
|
|
19734
20399
|
SQL2.WRITE,
|
|
19735
20400
|
SQL2.ADMIN,
|
|
@@ -20180,6 +20845,8 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
20180
20845
|
this.confirmedHostedSpaceIds = /* @__PURE__ */ new Set();
|
|
20181
20846
|
/** Serializes account-registry writes for the active node instance. */
|
|
20182
20847
|
this.accountRegistryTail = Promise.resolve();
|
|
20848
|
+
/** Bounds best-effort registry work on the sign-in critical path. */
|
|
20849
|
+
this.accountRegistryDeadlineMs = 4e3;
|
|
20183
20850
|
/**
|
|
20184
20851
|
* True when the last signIn() detected an interactive signer and skipped
|
|
20185
20852
|
* client-side bootstrap. Apps can read this to know whether bootstrap was
|
|
@@ -20258,11 +20925,46 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
20258
20925
|
});
|
|
20259
20926
|
return this.wasmBindings.invokeAny(invocationSession, caveatPreservingEntries, facts);
|
|
20260
20927
|
};
|
|
20928
|
+
assertValidReplicationConfig(config.replication);
|
|
20261
20929
|
this.explicitHost = config.host;
|
|
20262
20930
|
this.config = {
|
|
20263
20931
|
...config,
|
|
20264
20932
|
host: config.host ?? DEFAULT_HOST
|
|
20265
20933
|
};
|
|
20934
|
+
if (this.config.replication?.enabled) {
|
|
20935
|
+
const runtime = getNodeReplicationLoaders().runtime().then(({ ReplicationRuntime: ReplicationRuntime2 }) => {
|
|
20936
|
+
const instance = new ReplicationRuntime2(this.config.replication);
|
|
20937
|
+
this.replicationRuntimeInstance = instance;
|
|
20938
|
+
return instance;
|
|
20939
|
+
});
|
|
20940
|
+
this.replicationRuntime = runtime;
|
|
20941
|
+
this.replicationControl = {
|
|
20942
|
+
status: async () => {
|
|
20943
|
+
const status = await runtime.then((ready) => ready.control.status());
|
|
20944
|
+
const scopeError = this.replicationScopeError;
|
|
20945
|
+
if (!scopeError) return status;
|
|
20946
|
+
return this.config.replication.prefixes.map((prefix) => ({
|
|
20947
|
+
prefix,
|
|
20948
|
+
state: "unavailable",
|
|
20949
|
+
lastError: {
|
|
20950
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20951
|
+
code: "SESSION_SCOPE_MISMATCH",
|
|
20952
|
+
message: scopeError
|
|
20953
|
+
},
|
|
20954
|
+
pending: { inFlight: 0, committed: 0, ambiguous: 0 },
|
|
20955
|
+
pinned: [],
|
|
20956
|
+
lagMs: null
|
|
20957
|
+
}));
|
|
20958
|
+
},
|
|
20959
|
+
sync: async (input) => (await runtime).control.sync(input),
|
|
20960
|
+
purge: (input) => {
|
|
20961
|
+
const instance = this.replicationRuntimeInstance;
|
|
20962
|
+
return instance ? instance.control.purge(input) : runtime.then((ready) => ready.control.purge(input));
|
|
20963
|
+
},
|
|
20964
|
+
clearPending: async () => (await runtime).control.clearPending(),
|
|
20965
|
+
close: async () => (await runtime).control.close()
|
|
20966
|
+
};
|
|
20967
|
+
}
|
|
20266
20968
|
if (config.wasmBindings) {
|
|
20267
20969
|
this.wasmBindings = config.wasmBindings;
|
|
20268
20970
|
} else if (_TinyCloudNode.nodeDefaults) {
|
|
@@ -20383,7 +21085,14 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
20383
21085
|
siweConfig: config.siweConfig,
|
|
20384
21086
|
manifest: useBootstrapSignInRequest ? void 0 : config.manifest,
|
|
20385
21087
|
capabilityRequest: useBootstrapSignInRequest ? config.includeAccountRegistryPermissions === false ? BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST : BOOTSTRAP_SESSION_REQUESTS.default : config.capabilityRequest,
|
|
20386
|
-
includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : config.includeAccountRegistryPermissions
|
|
21088
|
+
includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : config.includeAccountRegistryPermissions,
|
|
21089
|
+
// Sign-in needs only the entry inputs (prefixes + secrets opt-in);
|
|
21090
|
+
// storage and scheduling never leave TinyCloudNode.
|
|
21091
|
+
replication: config.replication?.enabled === true ? {
|
|
21092
|
+
enabled: true,
|
|
21093
|
+
prefixes: config.replication.prefixes,
|
|
21094
|
+
allowSecrets: config.replication.allowSecrets === true
|
|
21095
|
+
} : void 0
|
|
20387
21096
|
});
|
|
20388
21097
|
this.tc = new TinyCloud(this.auth, {
|
|
20389
21098
|
invokeAny: this.invokeAnyWithRuntimePermissions,
|
|
@@ -20634,6 +21343,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
20634
21343
|
this.confirmedHostedSpaceIds.clear();
|
|
20635
21344
|
await this.tc.signIn(options);
|
|
20636
21345
|
this.syncResolvedHostFromAuth();
|
|
21346
|
+
this.replicationDelegateSession = void 0;
|
|
21347
|
+
this.replicationScopeError = void 0;
|
|
21348
|
+
if (this.replicationRuntime) await (await this.replicationRuntime).unbind();
|
|
20637
21349
|
const oldGraph = this._serviceGraph;
|
|
20638
21350
|
this._serviceGraph = this.createServiceGraphLifetime();
|
|
20639
21351
|
oldGraph.retire();
|
|
@@ -20643,7 +21355,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
20643
21355
|
this.sessionKeyId = signedInSession.sessionKey;
|
|
20644
21356
|
this.sessionKeyJwk = signedInSession.jwk;
|
|
20645
21357
|
}
|
|
20646
|
-
this.initializeServices();
|
|
21358
|
+
await this.initializeServices();
|
|
20647
21359
|
const primarySession = this.currentTinyCloudSession();
|
|
20648
21360
|
if (primarySession) {
|
|
20649
21361
|
this.registerPrimarySessionGrant(primarySession);
|
|
@@ -20655,6 +21367,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
20655
21367
|
}
|
|
20656
21368
|
if (!bootstrapped) {
|
|
20657
21369
|
this.scheduleAccountRegistrySync();
|
|
21370
|
+
await this.accountRegistryTail;
|
|
20658
21371
|
}
|
|
20659
21372
|
this.notificationHandler.success("Successfully signed in");
|
|
20660
21373
|
}
|
|
@@ -21107,7 +21820,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
21107
21820
|
provenance: "primary"
|
|
21108
21821
|
});
|
|
21109
21822
|
}
|
|
21110
|
-
async writeManifestRegistryRecords() {
|
|
21823
|
+
async writeManifestRegistryRecords(account, signal) {
|
|
21111
21824
|
const request = this.capabilityRequest;
|
|
21112
21825
|
if (!request || request.registryRecords.length === 0) {
|
|
21113
21826
|
return;
|
|
@@ -21116,8 +21829,9 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
21116
21829
|
throw new Error("Manifest registry write requires wallet mode");
|
|
21117
21830
|
}
|
|
21118
21831
|
const accountSpaceId = this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE2);
|
|
21119
|
-
await this.ensureOwnedSpaceHostedById(accountSpaceId);
|
|
21120
|
-
|
|
21832
|
+
await this.ensureOwnedSpaceHostedById(accountSpaceId, signal);
|
|
21833
|
+
if (signal.aborted) throw signal.reason;
|
|
21834
|
+
const result = await account.applications.register(request.manifests);
|
|
21121
21835
|
if (!result.ok) {
|
|
21122
21836
|
throw Object.assign(
|
|
21123
21837
|
new Error(`Failed to write manifest registry records: ${result.error.message}`),
|
|
@@ -21125,34 +21839,113 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
21125
21839
|
);
|
|
21126
21840
|
}
|
|
21127
21841
|
}
|
|
21842
|
+
createAccountRegistryServices(context, signal) {
|
|
21843
|
+
const session = context.session;
|
|
21844
|
+
if (!session) throw new Error("Account registry requires an active service session");
|
|
21845
|
+
const contexts = [context];
|
|
21846
|
+
const accountSpaceId = this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE2);
|
|
21847
|
+
const createScopedContext = (spaceId) => {
|
|
21848
|
+
const scoped = this._serviceGraph.track(new ServiceContext2({
|
|
21849
|
+
invoke: context.invoke,
|
|
21850
|
+
invokeAny: context.invokeAny,
|
|
21851
|
+
fetch: context.fetch,
|
|
21852
|
+
hosts: context.hosts,
|
|
21853
|
+
telemetry: this.config.telemetry
|
|
21854
|
+
}));
|
|
21855
|
+
scoped.setOperationAbortSignal(signal);
|
|
21856
|
+
scoped.setSession({ ...session, spaceId });
|
|
21857
|
+
contexts.push(scoped);
|
|
21858
|
+
return scoped;
|
|
21859
|
+
};
|
|
21860
|
+
const sql = new SQLService2({});
|
|
21861
|
+
const sqlContext = createScopedContext(accountSpaceId);
|
|
21862
|
+
sql.initialize(sqlContext);
|
|
21863
|
+
const spaces = new SpaceService({
|
|
21864
|
+
hosts: context.hosts,
|
|
21865
|
+
session,
|
|
21866
|
+
invoke: context.invoke,
|
|
21867
|
+
fetch: (url, init2) => context.fetch(url, {
|
|
21868
|
+
...init2,
|
|
21869
|
+
signal: init2?.signal ? AbortSignal.any([init2.signal, signal]) : signal
|
|
21870
|
+
}),
|
|
21871
|
+
userDid: this.did,
|
|
21872
|
+
createKVService: (spaceId) => {
|
|
21873
|
+
const kv = new KVService2({});
|
|
21874
|
+
kv.initialize(createScopedContext(spaceId));
|
|
21875
|
+
return kv;
|
|
21876
|
+
}
|
|
21877
|
+
});
|
|
21878
|
+
return {
|
|
21879
|
+
contexts,
|
|
21880
|
+
account: new AccountService({
|
|
21881
|
+
getDid: () => this.did,
|
|
21882
|
+
getHost: () => context.hosts[0] ?? this.config.host,
|
|
21883
|
+
getPrimarySpaceId: () => session.spaceId,
|
|
21884
|
+
getAccountSpaceId: () => accountSpaceId,
|
|
21885
|
+
getSpaces: () => spaces,
|
|
21886
|
+
getAccountDb: () => sql.db("account"),
|
|
21887
|
+
ensureAccountSpaceHosted: () => this.ensureOwnedSpaceHostedById(accountSpaceId, signal)
|
|
21888
|
+
})
|
|
21889
|
+
};
|
|
21890
|
+
}
|
|
21128
21891
|
scheduleAccountRegistrySync() {
|
|
21129
21892
|
const session = this.currentTinyCloudSession();
|
|
21130
21893
|
if (!session || this.pendingAccountRegistrySync?.session === session) {
|
|
21131
21894
|
return;
|
|
21132
21895
|
}
|
|
21896
|
+
const controller = new AbortController();
|
|
21897
|
+
const deadline = setTimeout(
|
|
21898
|
+
() => controller.abort(new DOMException("Account registry deadline exceeded", "TimeoutError")),
|
|
21899
|
+
this.accountRegistryDeadlineMs
|
|
21900
|
+
);
|
|
21133
21901
|
const promise = this.enqueueAccountRegistryOperation(session, async () => {
|
|
21134
|
-
|
|
21135
|
-
|
|
21136
|
-
|
|
21137
|
-
|
|
21138
|
-
|
|
21139
|
-
|
|
21140
|
-
|
|
21141
|
-
|
|
21142
|
-
|
|
21143
|
-
|
|
21144
|
-
|
|
21145
|
-
|
|
21146
|
-
|
|
21147
|
-
|
|
21148
|
-
|
|
21149
|
-
|
|
21150
|
-
|
|
21151
|
-
|
|
21152
|
-
|
|
21902
|
+
const sharedContext = this._serviceContext;
|
|
21903
|
+
if (!sharedContext) return;
|
|
21904
|
+
const context = this._serviceGraph.track(new ServiceContext2({
|
|
21905
|
+
invoke: sharedContext.invoke,
|
|
21906
|
+
invokeAny: sharedContext.invokeAny,
|
|
21907
|
+
fetch: sharedContext.fetch,
|
|
21908
|
+
hosts: sharedContext.hosts,
|
|
21909
|
+
telemetry: this.config.telemetry
|
|
21910
|
+
}));
|
|
21911
|
+
context.setOperationAbortSignal(controller.signal);
|
|
21912
|
+
const current = this.currentTinyCloudSession();
|
|
21913
|
+
if (!current) return;
|
|
21914
|
+
context.setSession({
|
|
21915
|
+
delegationHeader: current.delegationHeader,
|
|
21916
|
+
delegationCid: current.delegationCid,
|
|
21917
|
+
spaceId: current.spaceId,
|
|
21918
|
+
verificationMethod: current.verificationMethod,
|
|
21919
|
+
jwk: current.jwk
|
|
21920
|
+
});
|
|
21921
|
+
const registry = this.createAccountRegistryServices(context, controller.signal);
|
|
21922
|
+
try {
|
|
21923
|
+
await this.withAccountRegistryRetry(async () => {
|
|
21924
|
+
if (controller.signal.aborted) throw controller.signal.reason;
|
|
21925
|
+
if (this.currentTinyCloudSession() !== session) return;
|
|
21926
|
+
await registry.account.index.ensure();
|
|
21927
|
+
if (controller.signal.aborted) throw controller.signal.reason;
|
|
21928
|
+
if (this.currentTinyCloudSession() !== session) return;
|
|
21929
|
+
await this.writeManifestRegistryRecords(registry.account, controller.signal);
|
|
21930
|
+
if (controller.signal.aborted) throw controller.signal.reason;
|
|
21931
|
+
if (this.currentTinyCloudSession() !== session) return;
|
|
21932
|
+
if (this.currentSessionCanListSpaces()) {
|
|
21933
|
+
const result = await registry.account.spaces.syncAccessible();
|
|
21934
|
+
if (controller.signal.aborted) throw controller.signal.reason;
|
|
21935
|
+
if (!result.ok) {
|
|
21936
|
+
throw Object.assign(
|
|
21937
|
+
new Error(`Failed to sync account spaces: ${result.error.message}`),
|
|
21938
|
+
{ cause: result.error }
|
|
21939
|
+
);
|
|
21940
|
+
}
|
|
21153
21941
|
}
|
|
21942
|
+
}, controller.signal);
|
|
21943
|
+
} finally {
|
|
21944
|
+
for (const operationContext of registry.contexts) {
|
|
21945
|
+
operationContext.retire();
|
|
21154
21946
|
}
|
|
21155
|
-
|
|
21947
|
+
clearTimeout(deadline);
|
|
21948
|
+
}
|
|
21156
21949
|
});
|
|
21157
21950
|
this.pendingAccountRegistrySync = { session, promise };
|
|
21158
21951
|
const clearPendingSync = () => {
|
|
@@ -21161,6 +21954,10 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
21161
21954
|
}
|
|
21162
21955
|
};
|
|
21163
21956
|
void promise.then(clearPendingSync, clearPendingSync);
|
|
21957
|
+
void promise.then(
|
|
21958
|
+
() => clearTimeout(deadline),
|
|
21959
|
+
() => clearTimeout(deadline)
|
|
21960
|
+
);
|
|
21164
21961
|
}
|
|
21165
21962
|
enqueueAccountRegistryOperation(session, task) {
|
|
21166
21963
|
const operation = this.accountRegistryTail.then(async () => {
|
|
@@ -21196,14 +21993,22 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
21196
21993
|
(operation) => operation.service === "space" && this.actionContains(operation.action, "tinycloud.space/list")
|
|
21197
21994
|
);
|
|
21198
21995
|
}
|
|
21199
|
-
async withAccountRegistryRetry(task) {
|
|
21996
|
+
async withAccountRegistryRetry(task, signal) {
|
|
21200
21997
|
const delays = [250, 1e3, 3e3];
|
|
21201
21998
|
let lastError;
|
|
21202
21999
|
for (let attempt = 0; attempt < delays.length; attempt += 1) {
|
|
22000
|
+
if (signal.aborted) {
|
|
22001
|
+
console.warn("TinyCloud account registry sync stopped: deadline exceeded", signal.reason);
|
|
22002
|
+
return;
|
|
22003
|
+
}
|
|
21203
22004
|
try {
|
|
21204
22005
|
await task();
|
|
21205
22006
|
return;
|
|
21206
22007
|
} catch (error) {
|
|
22008
|
+
if (signal.aborted) {
|
|
22009
|
+
console.warn("TinyCloud account registry sync stopped: deadline exceeded", error);
|
|
22010
|
+
return;
|
|
22011
|
+
}
|
|
21207
22012
|
const verdict = authorizationVerdictOf(error);
|
|
21208
22013
|
const message = error instanceof Error ? error.message : String(error);
|
|
21209
22014
|
const isAuthorizationVerdict = verdict === void 0 ? /Unauthorized Action|\b(?:401|403)\b/.test(message) : verdict !== "other";
|
|
@@ -21215,12 +22020,28 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
21215
22020
|
return;
|
|
21216
22021
|
}
|
|
21217
22022
|
if (isStorageFullError(error) || isStorageFullError(error?.cause) || message.includes(STORAGE_FULL_MESSAGE) || message.includes(STORAGE_WRITE_TOO_LARGE_MESSAGE)) {
|
|
21218
|
-
console.warn(
|
|
22023
|
+
console.warn(
|
|
22024
|
+
"TinyCloud account registry sync stopped: storage is full",
|
|
22025
|
+
error
|
|
22026
|
+
);
|
|
21219
22027
|
return;
|
|
21220
22028
|
}
|
|
21221
22029
|
lastError = error;
|
|
21222
22030
|
if (attempt < delays.length - 1) {
|
|
21223
|
-
await new Promise((resolve) =>
|
|
22031
|
+
await new Promise((resolve, reject2) => {
|
|
22032
|
+
const timer = setTimeout(() => {
|
|
22033
|
+
signal.removeEventListener("abort", onAbort);
|
|
22034
|
+
resolve();
|
|
22035
|
+
}, delays[attempt]);
|
|
22036
|
+
const onAbort = () => {
|
|
22037
|
+
clearTimeout(timer);
|
|
22038
|
+
signal.removeEventListener("abort", onAbort);
|
|
22039
|
+
reject2(signal.reason);
|
|
22040
|
+
};
|
|
22041
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
22042
|
+
}).catch((error2) => {
|
|
22043
|
+
if (!signal.aborted) throw error2;
|
|
22044
|
+
});
|
|
21224
22045
|
}
|
|
21225
22046
|
}
|
|
21226
22047
|
}
|
|
@@ -21270,7 +22091,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
21270
22091
|
* self-revealing: the next write to the space fails immediately and loudly
|
|
21271
22092
|
* with `404 Space not found`.
|
|
21272
22093
|
*/
|
|
21273
|
-
async ensureOwnedSpaceHostedById(spaceId) {
|
|
22094
|
+
async ensureOwnedSpaceHostedById(spaceId, signal) {
|
|
21274
22095
|
if (!this.auth) {
|
|
21275
22096
|
throw new Error("Owned space hosting requires wallet mode");
|
|
21276
22097
|
}
|
|
@@ -21285,7 +22106,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
21285
22106
|
if (!host) {
|
|
21286
22107
|
throw new Error("Owned space hosting requires a TinyCloud host");
|
|
21287
22108
|
}
|
|
21288
|
-
const activation = await activateSessionWithHost2(host, session.delegationHeader);
|
|
22109
|
+
const activation = await activateSessionWithHost2(host, session.delegationHeader, signal);
|
|
21289
22110
|
if (activation.success && !activation.skipped?.includes(spaceId)) {
|
|
21290
22111
|
this.confirmedHostedSpaceIds.add(spaceId);
|
|
21291
22112
|
return;
|
|
@@ -21296,15 +22117,16 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
21296
22117
|
{ cause: activation }
|
|
21297
22118
|
);
|
|
21298
22119
|
}
|
|
21299
|
-
const
|
|
22120
|
+
const authorization = this.auth;
|
|
22121
|
+
const created = signal ? await authorization.hostOwnedSpaceResult(spaceId, void 0, signal) : await authorization.hostOwnedSpaceResult(spaceId);
|
|
21300
22122
|
if (!created.success) {
|
|
21301
22123
|
throw Object.assign(
|
|
21302
22124
|
new Error(`Failed to create owned space ${spaceId}: ${describeHostFailure2(created)}`),
|
|
21303
22125
|
{ cause: created }
|
|
21304
22126
|
);
|
|
21305
22127
|
}
|
|
21306
|
-
|
|
21307
|
-
const retry = await activateSessionWithHost2(host, session.delegationHeader);
|
|
22128
|
+
if (signal?.aborted) throw signal.reason;
|
|
22129
|
+
const retry = await activateSessionWithHost2(host, session.delegationHeader, signal);
|
|
21308
22130
|
if (!retry.success || retry.skipped?.includes(spaceId)) {
|
|
21309
22131
|
throw Object.assign(
|
|
21310
22132
|
new Error(
|
|
@@ -21582,8 +22404,36 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
21582
22404
|
const stagedHost = resolvedHost ?? this.config.host;
|
|
21583
22405
|
const stagedAddress = restoredAddress;
|
|
21584
22406
|
const stagedChainId = sessionData.chainId ?? 1;
|
|
21585
|
-
|
|
21586
|
-
|
|
22407
|
+
let replicationPrincipal;
|
|
22408
|
+
let compactReplicationSession;
|
|
22409
|
+
let replicationScopeError;
|
|
22410
|
+
if (this.config.replication?.enabled) {
|
|
22411
|
+
if (stagedAddress !== void 0) {
|
|
22412
|
+
replicationPrincipal = pkhDid2(stagedAddress, stagedChainId);
|
|
22413
|
+
} else {
|
|
22414
|
+
const { replicationScopeFromSignedSession: replicationScopeFromSignedSession2 } = await getNodeReplicationLoaders().authority();
|
|
22415
|
+
const signedScope = replicationScopeFromSignedSession2({
|
|
22416
|
+
delegationHeader: sessionData.delegationHeader,
|
|
22417
|
+
delegationCid: sessionData.delegationCid,
|
|
22418
|
+
verificationMethod: restoredVerificationMethod,
|
|
22419
|
+
persistedSpace: sessionData.spaceId
|
|
22420
|
+
});
|
|
22421
|
+
if (!signedScope) {
|
|
22422
|
+
replicationScopeError = "Persisted space does not match the locally verified delegation scope.";
|
|
22423
|
+
} else {
|
|
22424
|
+
replicationPrincipal = signedScope.principal;
|
|
22425
|
+
compactReplicationSession = {
|
|
22426
|
+
delegationHeader: sessionData.delegationHeader,
|
|
22427
|
+
delegationCid: sessionData.delegationCid,
|
|
22428
|
+
spaceId: signedScope.space,
|
|
22429
|
+
verificationMethod: restoredVerificationMethod,
|
|
22430
|
+
jwk: stagedJwk
|
|
22431
|
+
};
|
|
22432
|
+
}
|
|
22433
|
+
}
|
|
22434
|
+
}
|
|
22435
|
+
const stagedNodeDid = stagedAddress ? pkhDid2(stagedAddress, stagedChainId) : canonicalVerificationMethod;
|
|
22436
|
+
const serviceSession = {
|
|
21587
22437
|
delegationHeader: sessionData.delegationHeader,
|
|
21588
22438
|
delegationCid: sessionData.delegationCid,
|
|
21589
22439
|
spaceId: sessionData.spaceId,
|
|
@@ -21660,7 +22510,18 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
21660
22510
|
} else {
|
|
21661
22511
|
this._restoredTcSession = stagedTcSession;
|
|
21662
22512
|
}
|
|
22513
|
+
this.replicationDelegateSession = compactReplicationSession;
|
|
22514
|
+
this.replicationScopeError = replicationScopeError;
|
|
22515
|
+
if (this.replicationRuntime) await (await this.replicationRuntime).unbind();
|
|
21663
22516
|
oldGraph.retire();
|
|
22517
|
+
if (replicationScopeError) {
|
|
22518
|
+
stagedGraph.serviceContext.emit("replication.state", {
|
|
22519
|
+
state: "unavailable",
|
|
22520
|
+
code: "SESSION_SCOPE_MISMATCH",
|
|
22521
|
+
detail: replicationScopeError
|
|
22522
|
+
});
|
|
22523
|
+
}
|
|
22524
|
+
await this.bindReplicationRuntime(stagedGraph.serviceContext, serviceSession, stagedGraph.kv, replicationPrincipal);
|
|
21664
22525
|
oldCore?.retireServices?.();
|
|
21665
22526
|
}
|
|
21666
22527
|
stagePrimarySessionState(session, verificationMethod, host, sessionExpiry, recap) {
|
|
@@ -21814,6 +22675,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
21814
22675
|
}));
|
|
21815
22676
|
context.setSession(config.session);
|
|
21816
22677
|
service.initialize(context);
|
|
22678
|
+
this.attachReplicationKV(config.session.spaceId, service);
|
|
21817
22679
|
return service;
|
|
21818
22680
|
}
|
|
21819
22681
|
});
|
|
@@ -21854,6 +22716,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
21854
22716
|
}));
|
|
21855
22717
|
context.setSession({ ...input.serviceSession, spaceId });
|
|
21856
22718
|
scopedKv.initialize(context);
|
|
22719
|
+
this.attachReplicationKV(spaceId, scopedKv);
|
|
21857
22720
|
return scopedKv;
|
|
21858
22721
|
},
|
|
21859
22722
|
createVaultService: (spaceId) => {
|
|
@@ -21868,6 +22731,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
21868
22731
|
}));
|
|
21869
22732
|
context.setSession({ ...input.serviceSession, spaceId });
|
|
21870
22733
|
scopedKv.initialize(context);
|
|
22734
|
+
this.attachReplicationKV(spaceId, scopedKv);
|
|
21871
22735
|
const scopedVault = this.createVaultService(spaceId, scopedKv, encryption, {
|
|
21872
22736
|
host: input.host,
|
|
21873
22737
|
did: input.nodeDid,
|
|
@@ -22148,7 +23012,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
22148
23012
|
siweConfig: this.config.siweConfig,
|
|
22149
23013
|
manifest: useBootstrapSignInRequest ? void 0 : this.config.manifest,
|
|
22150
23014
|
capabilityRequest: useBootstrapSignInRequest ? this.config.includeAccountRegistryPermissions === false ? BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST : BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
|
|
22151
|
-
includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : this.config.includeAccountRegistryPermissions
|
|
23015
|
+
includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : this.config.includeAccountRegistryPermissions,
|
|
23016
|
+
replication: this.config.replication?.enabled === true ? {
|
|
23017
|
+
enabled: true,
|
|
23018
|
+
prefixes: this.config.replication.prefixes,
|
|
23019
|
+
allowSecrets: this.config.replication.allowSecrets === true
|
|
23020
|
+
} : void 0
|
|
22152
23021
|
});
|
|
22153
23022
|
this.tc = new TinyCloud(this.auth, {
|
|
22154
23023
|
invokeAny: this.invokeAnyWithRuntimePermissions,
|
|
@@ -22201,7 +23070,12 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
22201
23070
|
siweConfig: this.config.siweConfig,
|
|
22202
23071
|
manifest: useBootstrapSignInRequest ? void 0 : this.config.manifest,
|
|
22203
23072
|
capabilityRequest: useBootstrapSignInRequest ? this.config.includeAccountRegistryPermissions === false ? BOOTSTRAP_DEFAULT_ONLY_SESSION_REQUEST : BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
|
|
22204
|
-
includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : this.config.includeAccountRegistryPermissions
|
|
23073
|
+
includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : this.config.includeAccountRegistryPermissions,
|
|
23074
|
+
replication: this.config.replication?.enabled === true ? {
|
|
23075
|
+
enabled: true,
|
|
23076
|
+
prefixes: this.config.replication.prefixes,
|
|
23077
|
+
allowSecrets: this.config.replication.allowSecrets === true
|
|
23078
|
+
} : void 0
|
|
22205
23079
|
});
|
|
22206
23080
|
this.tc = new TinyCloud(this.auth, {
|
|
22207
23081
|
invokeAny: this.invokeAnyWithRuntimePermissions,
|
|
@@ -22213,7 +23087,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
22213
23087
|
* Initialize the service context and KV service after sign-in.
|
|
22214
23088
|
* @internal
|
|
22215
23089
|
*/
|
|
22216
|
-
initializeServices() {
|
|
23090
|
+
async initializeServices() {
|
|
22217
23091
|
const session = this.currentTinyCloudSession();
|
|
22218
23092
|
if (!session) {
|
|
22219
23093
|
return;
|
|
@@ -22256,12 +23130,18 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
22256
23130
|
jwk: session.jwk
|
|
22257
23131
|
};
|
|
22258
23132
|
this._serviceContext.setSession(serviceSession);
|
|
23133
|
+
await this.bindReplicationRuntime(this._serviceContext, serviceSession, this._kv);
|
|
22259
23134
|
this.tc.serviceContext.setSession(serviceSession);
|
|
22260
23135
|
this._vault = this.createVaultService(session.spaceId, this._kv);
|
|
22261
23136
|
this._vault.initialize(this._serviceContext);
|
|
22262
23137
|
this._serviceContext.registerService("vault", this._vault);
|
|
22263
23138
|
this.initializeV2Services(serviceSession);
|
|
22264
23139
|
}
|
|
23140
|
+
attachReplicationKV(spaceId, kvService) {
|
|
23141
|
+
if (!this.replicationRuntime) return;
|
|
23142
|
+
const attached = this.replicationRuntime.then((runtime) => runtime.attach(spaceId, kvService));
|
|
23143
|
+
kvService.setReadThroughReady(attached);
|
|
23144
|
+
}
|
|
22265
23145
|
createSpaceScopedKVService(spaceId) {
|
|
22266
23146
|
const kvService = new KVService2({});
|
|
22267
23147
|
if (this._serviceContext) {
|
|
@@ -22277,6 +23157,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
22277
23157
|
spaceScopedContext.setSession({ ...session, spaceId });
|
|
22278
23158
|
}
|
|
22279
23159
|
kvService.initialize(spaceScopedContext);
|
|
23160
|
+
this.attachReplicationKV(spaceId, kvService);
|
|
22280
23161
|
}
|
|
22281
23162
|
return kvService;
|
|
22282
23163
|
}
|
|
@@ -22739,6 +23620,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
22739
23620
|
}));
|
|
22740
23621
|
context.setSession(config.session);
|
|
22741
23622
|
service.initialize(context);
|
|
23623
|
+
this.attachReplicationKV(config.session.spaceId, service);
|
|
22742
23624
|
return service;
|
|
22743
23625
|
},
|
|
22744
23626
|
onRootDelegationNeeded: this.signer ? async (params) => {
|
|
@@ -23063,6 +23945,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
23063
23945
|
}));
|
|
23064
23946
|
spaceScopedContext.setSession({ ...this._serviceContext.session, spaceId });
|
|
23065
23947
|
kv.initialize(spaceScopedContext);
|
|
23948
|
+
this.attachReplicationKV(spaceId, kv);
|
|
23066
23949
|
return kv;
|
|
23067
23950
|
}
|
|
23068
23951
|
/**
|
|
@@ -23485,10 +24368,18 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
23485
24368
|
);
|
|
23486
24369
|
}
|
|
23487
24370
|
const targetHost = delegation.host ?? this.config.host;
|
|
23488
|
-
|
|
23489
|
-
|
|
23490
|
-
|
|
23491
|
-
|
|
24371
|
+
let activateResult;
|
|
24372
|
+
try {
|
|
24373
|
+
activateResult = await activateSessionWithHost2(
|
|
24374
|
+
targetHost,
|
|
24375
|
+
delegation.delegationHeader
|
|
24376
|
+
);
|
|
24377
|
+
} catch (cause) {
|
|
24378
|
+
throw Object.assign(
|
|
24379
|
+
new Error("The TinyCloud host could not be reached to activate the delegation."),
|
|
24380
|
+
{ code: "ACTIVATION_TRANSPORT_FAILED", cause }
|
|
24381
|
+
);
|
|
24382
|
+
}
|
|
23492
24383
|
if (!activateResult.success) {
|
|
23493
24384
|
if (this.operationsFromDelegation(delegation).some(
|
|
23494
24385
|
(operation) => operation.resource?.startsWith("urn:cid:") === true && operation.service === "delegation" && operation.action === "tinycloud.delegation/revoke"
|
|
@@ -23776,7 +24667,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
23776
24667
|
throw new Error("Public space not enabled. Set enablePublicSpace: true in config.");
|
|
23777
24668
|
}
|
|
23778
24669
|
await this.auth.hostPublicSpace(publicSpaceId);
|
|
23779
|
-
const kvActions = [
|
|
24670
|
+
const kvActions = [KV3.PUT, KV3.GET, KV3.DEL, KV3.LIST, KV3.METADATA];
|
|
23780
24671
|
const abilities = { kv: { "": kvActions } };
|
|
23781
24672
|
const now = /* @__PURE__ */ new Date();
|
|
23782
24673
|
const expiryMs = EXPIRY3.EPHEMERAL_MS;
|
|
@@ -24017,31 +24908,31 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
24017
24908
|
);
|
|
24018
24909
|
return { delegation: delegation2, prompted: true };
|
|
24019
24910
|
}
|
|
24020
|
-
const
|
|
24021
|
-
|
|
24022
|
-
|
|
24023
|
-
|
|
24024
|
-
|
|
24025
|
-
|
|
24026
|
-
|
|
24027
|
-
)
|
|
24028
|
-
|
|
24029
|
-
|
|
24030
|
-
|
|
24031
|
-
|
|
24032
|
-
}
|
|
24033
|
-
const runtimeExpiration = runtimeGrant.expiresAt < effectiveExpiration ? runtimeGrant.expiresAt : effectiveExpiration;
|
|
24034
|
-
const delegation2 = await this.createDelegationViaRuntimeGrant(
|
|
24035
|
-
did,
|
|
24036
|
-
expandedEntries,
|
|
24037
|
-
runtimeExpiration,
|
|
24038
|
-
runtimeGrant,
|
|
24039
|
-
runtimeOperations,
|
|
24040
|
-
onPrepared
|
|
24041
|
-
);
|
|
24042
|
-
return { delegation: delegation2, prompted: false };
|
|
24911
|
+
const plan = this.planDerivation(
|
|
24912
|
+
expandedEntries,
|
|
24913
|
+
session,
|
|
24914
|
+
effectiveExpiration,
|
|
24915
|
+
Date.now()
|
|
24916
|
+
);
|
|
24917
|
+
if ("refused" in plan) {
|
|
24918
|
+
if (plan.refused === "SESSION_EXPIRING") {
|
|
24919
|
+
throw new SessionExpiredError(plan.expiredAt ?? sessionExpiry ?? /* @__PURE__ */ new Date(0));
|
|
24920
|
+
}
|
|
24921
|
+
if (plan.refused === "CAVEATED_AUTHORITY") {
|
|
24922
|
+
throw new CaveatedDelegationUnsupportedError(plan.caveated ?? []);
|
|
24043
24923
|
}
|
|
24044
|
-
throw new PermissionNotInManifestError(missing, granted);
|
|
24924
|
+
throw new PermissionNotInManifestError(plan.missing ?? [], plan.granted ?? []);
|
|
24925
|
+
}
|
|
24926
|
+
if (plan.path === "runtime") {
|
|
24927
|
+
const delegation2 = await this.createDelegationViaRuntimeGrant(
|
|
24928
|
+
did,
|
|
24929
|
+
expandedEntries,
|
|
24930
|
+
plan.effectiveExpiration,
|
|
24931
|
+
plan.grant,
|
|
24932
|
+
plan.operations,
|
|
24933
|
+
onPrepared
|
|
24934
|
+
);
|
|
24935
|
+
return { delegation: delegation2, prompted: false };
|
|
24045
24936
|
}
|
|
24046
24937
|
const delegation = await this.createDelegationViaWasmPath(
|
|
24047
24938
|
did,
|
|
@@ -24052,6 +24943,149 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
24052
24943
|
);
|
|
24053
24944
|
return { delegation, prompted: false };
|
|
24054
24945
|
}
|
|
24946
|
+
/**
|
|
24947
|
+
* What `delegateTo` would select for `entries`, without signing or any
|
|
24948
|
+
* network call (TC-858 plan v3 §4.3): the session path when the signed
|
|
24949
|
+
* recap covers every entry, the runtime-grant path otherwise, or a
|
|
24950
|
+
* refusal. A refusal of `CAVEATED_AUTHORITY` means coverage exists only
|
|
24951
|
+
* through caveated branches, which the action-only WASM child-delegation
|
|
24952
|
+
* boundary cannot preserve — replication never derives authority through
|
|
24953
|
+
* them. `expiredAt`/`missing`/`granted`/`caveated` carry the payload
|
|
24954
|
+
* `delegateTo` turns back into its typed errors.
|
|
24955
|
+
* @internal
|
|
24956
|
+
*/
|
|
24957
|
+
planDerivation(expandedEntries, session, effectiveExpiration, nowMs) {
|
|
24958
|
+
const caveatedRequest = expandedEntries.filter(
|
|
24959
|
+
(entry) => !recapCaveatsEqual(entry.caveats, void 0)
|
|
24960
|
+
);
|
|
24961
|
+
if (caveatedRequest.length > 0) {
|
|
24962
|
+
return { refused: "CAVEATED_AUTHORITY", caveated: caveatedRequest };
|
|
24963
|
+
}
|
|
24964
|
+
const granted = this.projectSignedRecapCapabilities(session.siwe);
|
|
24965
|
+
const { subset, missing } = this.signedCapabilitySubset(expandedEntries, granted);
|
|
24966
|
+
if (subset) {
|
|
24967
|
+
return {
|
|
24968
|
+
path: "session",
|
|
24969
|
+
parentCid: session.delegationCid,
|
|
24970
|
+
expiresAt: effectiveExpiration.getTime(),
|
|
24971
|
+
effectiveExpiration
|
|
24972
|
+
};
|
|
24973
|
+
}
|
|
24974
|
+
const runtimeOperations = this.permissionEntriesToOperations(expandedEntries, session);
|
|
24975
|
+
const runtimeGrant = this.findGrantForOperations(
|
|
24976
|
+
runtimeOperations,
|
|
24977
|
+
{ excludePrimary: true }
|
|
24978
|
+
);
|
|
24979
|
+
if (runtimeGrant) {
|
|
24980
|
+
const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
|
|
24981
|
+
if (runtimeGrant.expiresAt.getTime() <= nowMs + marginMs) {
|
|
24982
|
+
return { refused: "SESSION_EXPIRING", expiredAt: runtimeGrant.expiresAt };
|
|
24983
|
+
}
|
|
24984
|
+
const caveated = this.caveatedRuntimeGrantEntries(
|
|
24985
|
+
expandedEntries,
|
|
24986
|
+
runtimeOperations,
|
|
24987
|
+
runtimeGrant
|
|
24988
|
+
);
|
|
24989
|
+
if (caveated.length > 0) {
|
|
24990
|
+
return { refused: "CAVEATED_AUTHORITY", caveated };
|
|
24991
|
+
}
|
|
24992
|
+
const runtimeExpiration = runtimeGrant.expiresAt < effectiveExpiration ? runtimeGrant.expiresAt : effectiveExpiration;
|
|
24993
|
+
return {
|
|
24994
|
+
path: "runtime",
|
|
24995
|
+
parentCid: runtimeGrant.session.delegationCid,
|
|
24996
|
+
expiresAt: runtimeExpiration.getTime(),
|
|
24997
|
+
effectiveExpiration: runtimeExpiration,
|
|
24998
|
+
grant: runtimeGrant,
|
|
24999
|
+
operations: runtimeOperations
|
|
25000
|
+
};
|
|
25001
|
+
}
|
|
25002
|
+
const relaxedGranted = granted.map((entry) => ({ ...entry, caveats: void 0 }));
|
|
25003
|
+
const relaxedRequested = expandedEntries.map((entry) => ({ ...entry, caveats: void 0 }));
|
|
25004
|
+
const relaxed = this.signedCapabilitySubset(relaxedRequested, relaxedGranted);
|
|
25005
|
+
if (relaxed.subset) {
|
|
25006
|
+
return {
|
|
25007
|
+
refused: "CAVEATED_AUTHORITY",
|
|
25008
|
+
caveated: missing.map((entry) => ({ ...entry, caveats: [{ via: "session-recap" }] }))
|
|
25009
|
+
};
|
|
25010
|
+
}
|
|
25011
|
+
return { refused: "NOT_COVERED", missing, granted };
|
|
25012
|
+
}
|
|
25013
|
+
/**
|
|
25014
|
+
* The plan `delegateTo(device, entries)` would follow, as a pure lookup:
|
|
25015
|
+
* no WASM sign and no `POST /delegate`. Backs
|
|
25016
|
+
* `ReplicationAuthority.plan` (and `mint` inside it) so a predicted
|
|
25017
|
+
* parent and expiry can never disagree with what a mint would use (§4.3,
|
|
25018
|
+
* §4.4). Session expiry inside the 60 s margin refuses with
|
|
25019
|
+
* `SESSION_EXPIRING`; the same check delegateTo performs.
|
|
25020
|
+
* @internal
|
|
25021
|
+
*/
|
|
25022
|
+
planDelegation(entries, options) {
|
|
25023
|
+
if (!Array.isArray(entries) || entries.length === 0) {
|
|
25024
|
+
throw new Error("planDelegation requires a non-empty permissions array");
|
|
25025
|
+
}
|
|
25026
|
+
const session = this.currentTinyCloudSession();
|
|
25027
|
+
if (!session) {
|
|
25028
|
+
return { refused: "SESSION_EXPIRING", expiredAt: /* @__PURE__ */ new Date(0) };
|
|
25029
|
+
}
|
|
25030
|
+
const nowMs = Date.now();
|
|
25031
|
+
const sessionExpiry = extractSiweExpiration(session.siwe);
|
|
25032
|
+
if (sessionExpiry !== void 0 && sessionExpiry.getTime() <= nowMs + _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS) {
|
|
25033
|
+
return { refused: "SESSION_EXPIRING", expiredAt: sessionExpiry };
|
|
25034
|
+
}
|
|
25035
|
+
const expandedEntries = this.expandPermissionEntries(entries);
|
|
25036
|
+
const expiryMs = resolveExpiryMs(options?.expiry);
|
|
25037
|
+
const expirationTime = new Date(nowMs + expiryMs);
|
|
25038
|
+
const effectiveExpiration = sessionExpiry !== void 0 && sessionExpiry < expirationTime ? sessionExpiry : expirationTime;
|
|
25039
|
+
return this.planDerivation(expandedEntries, session, effectiveExpiration, nowMs);
|
|
25040
|
+
}
|
|
25041
|
+
/**
|
|
25042
|
+
* The replication entries this node's sign-in request carries for the
|
|
25043
|
+
* current replication config and the signed-in or restored address: the
|
|
25044
|
+
* output of the same augmentation step `resolveSignInCapabilities` runs,
|
|
25045
|
+
* with the unrestricted-get and secrets gates applied (§4.1, §4.6).
|
|
25046
|
+
* Empty when replication is off or no address is known. Pure.
|
|
25047
|
+
*/
|
|
25048
|
+
replicationSignInEntries() {
|
|
25049
|
+
if (this.config.replication?.enabled !== true) return [];
|
|
25050
|
+
const auth = this.auth;
|
|
25051
|
+
if (auth === void 0 || typeof auth.replicationSignInEntries !== "function") {
|
|
25052
|
+
return [];
|
|
25053
|
+
}
|
|
25054
|
+
return auth.replicationSignInEntries();
|
|
25055
|
+
}
|
|
25056
|
+
async replicationAuthority() {
|
|
25057
|
+
const { createReplicationAuthority: createReplicationAuthority2 } = await getNodeReplicationLoaders().authority();
|
|
25058
|
+
return createReplicationAuthority2({
|
|
25059
|
+
replicationSession: () => this.currentTinyCloudSession() ?? this.replicationDelegateSession,
|
|
25060
|
+
siweExpiration: (siwe) => extractSiweExpiration(siwe),
|
|
25061
|
+
planDelegation: (entries, options) => this.planDelegation(entries, options),
|
|
25062
|
+
mintDelegation: async (deviceDid, entries) => {
|
|
25063
|
+
const result = await this.delegateTo(principalDid(deviceDid), entries);
|
|
25064
|
+
return {
|
|
25065
|
+
ucan: result.delegation.delegationHeader.Authorization,
|
|
25066
|
+
expiresAt: result.delegation.expiry.getTime()
|
|
25067
|
+
};
|
|
25068
|
+
}
|
|
25069
|
+
});
|
|
25070
|
+
}
|
|
25071
|
+
async bindReplicationRuntime(context, session, kv, principal) {
|
|
25072
|
+
if (!this.replicationRuntime) return;
|
|
25073
|
+
const identityPrincipal = principal ?? (this._address === void 0 ? void 0 : pkhDid2(this._address, this._chainId));
|
|
25074
|
+
if (identityPrincipal === void 0) return;
|
|
25075
|
+
const runtime = await this.replicationRuntime;
|
|
25076
|
+
const authority = await this.replicationAuthority();
|
|
25077
|
+
await runtime.bind({
|
|
25078
|
+
context,
|
|
25079
|
+
session,
|
|
25080
|
+
principal: identityPrincipal,
|
|
25081
|
+
authority,
|
|
25082
|
+
primaryKV: [{ space: session.spaceId, kv }]
|
|
25083
|
+
});
|
|
25084
|
+
}
|
|
25085
|
+
/** Stable control facade for this node's optional replication runtime. */
|
|
25086
|
+
get replication() {
|
|
25087
|
+
return this.replicationControl;
|
|
25088
|
+
}
|
|
24055
25089
|
/**
|
|
24056
25090
|
* Materialize one manifest-declared delegation using the current session key.
|
|
24057
25091
|
* Delivery is intentionally out of band; callers decide how to transmit the
|
|
@@ -24274,6 +25308,18 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
24274
25308
|
* it only accepts action maps, so reject the selected constrained branch.
|
|
24275
25309
|
*/
|
|
24276
25310
|
assertRuntimeGrantCaveatsPreservable(entries, requestedOperations, grant) {
|
|
25311
|
+
const caveated = this.caveatedRuntimeGrantEntries(entries, requestedOperations, grant);
|
|
25312
|
+
if (caveated.length > 0) {
|
|
25313
|
+
throw new CaveatedDelegationUnsupportedError(caveated);
|
|
25314
|
+
}
|
|
25315
|
+
}
|
|
25316
|
+
/**
|
|
25317
|
+
* The entries of `grant` that cover a requested operation while carrying
|
|
25318
|
+
* caveats — the branches {@link assertRuntimeGrantCaveatsPreservable}
|
|
25319
|
+
* rejects and {@link planDerivation} classifies as `CAVEATED_AUTHORITY`.
|
|
25320
|
+
* Shared so the pure plan and the mint path can't disagree.
|
|
25321
|
+
*/
|
|
25322
|
+
caveatedRuntimeGrantEntries(entries, requestedOperations, grant) {
|
|
24277
25323
|
let operationIndex = 0;
|
|
24278
25324
|
const caveated = [];
|
|
24279
25325
|
for (const entry of entries) {
|
|
@@ -24292,9 +25338,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
24292
25338
|
}
|
|
24293
25339
|
}
|
|
24294
25340
|
}
|
|
24295
|
-
|
|
24296
|
-
throw new CaveatedDelegationUnsupportedError(caveated);
|
|
24297
|
-
}
|
|
25341
|
+
return caveated;
|
|
24298
25342
|
}
|
|
24299
25343
|
/** Reject caveated parent branches before action-only child signing. */
|
|
24300
25344
|
assertPortableDelegationCaveatsPreservable(delegation) {
|
|
@@ -24399,7 +25443,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
24399
25443
|
const missing = requested.filter((entry) => {
|
|
24400
25444
|
const raw = this.isEncryptionPermissionEntry(entry) || this.isRawRevocationPermissionEntry(entry);
|
|
24401
25445
|
const comparable = raw && this.isEncryptionPermissionEntry(entry) ? { ...entry, space: "encryption" } : entry;
|
|
24402
|
-
return !
|
|
25446
|
+
return !isCapabilitySubset2([comparable], raw ? rawGranted : spaceGranted).subset;
|
|
24403
25447
|
});
|
|
24404
25448
|
return { subset: missing.length === 0, missing };
|
|
24405
25449
|
}
|
|
@@ -24871,7 +25915,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
24871
25915
|
session.chainId
|
|
24872
25916
|
);
|
|
24873
25917
|
const publicAbilities = {
|
|
24874
|
-
kv: { "": [
|
|
25918
|
+
kv: { "": [KV3.GET, KV3.PUT, KV3.METADATA] }
|
|
24875
25919
|
};
|
|
24876
25920
|
const publicPrepared = this.wasmBindings.prepareSession({
|
|
24877
25921
|
abilities: publicAbilities,
|
|
@@ -24899,7 +25943,7 @@ var _TinyCloudNode = class _TinyCloudNode {
|
|
|
24899
25943
|
delegationHeader: publicSession.delegationHeader,
|
|
24900
25944
|
spaceId: publicSpaceId,
|
|
24901
25945
|
path: "",
|
|
24902
|
-
actions: [
|
|
25946
|
+
actions: [KV3.GET, KV3.PUT, KV3.METADATA],
|
|
24903
25947
|
disableSubDelegation: params.disableSubDelegation ?? false,
|
|
24904
25948
|
expiry: expirationTime,
|
|
24905
25949
|
delegateDID: params.delegateDID,
|
|
@@ -25164,6 +26208,12 @@ TinyCloudNode.registerNodeDefaults({
|
|
|
25164
26208
|
createSigner: (privateKey, chainId) => new PrivateKeySigner(privateKey, chainId)
|
|
25165
26209
|
});
|
|
25166
26210
|
|
|
26211
|
+
// src/replication/node-loader.ts
|
|
26212
|
+
registerReplicationLoaders({
|
|
26213
|
+
runtime: () => Promise.resolve().then(() => (init_runtime(), runtime_exports)),
|
|
26214
|
+
authority: () => Promise.resolve().then(() => (init_authority(), authority_exports))
|
|
26215
|
+
});
|
|
26216
|
+
|
|
25167
26217
|
// src/index.ts
|
|
25168
26218
|
import { TinyCloud as TinyCloud2 } from "@tinycloud/sdk-core";
|
|
25169
26219
|
import {
|
|
@@ -25181,8 +26231,8 @@ import {
|
|
|
25181
26231
|
isEvmAddress,
|
|
25182
26232
|
makePkhSpaceId as makePkhSpaceId2,
|
|
25183
26233
|
parsePkhDid,
|
|
25184
|
-
pkhDid as
|
|
25185
|
-
principalDid,
|
|
26234
|
+
pkhDid as pkhDid4,
|
|
26235
|
+
principalDid as principalDid3,
|
|
25186
26236
|
principalDidEquals as principalDidEquals4,
|
|
25187
26237
|
parseCanonicalNetworkId,
|
|
25188
26238
|
TinyCloudDebugLogger,
|
|
@@ -25376,7 +26426,7 @@ import {
|
|
|
25376
26426
|
resolveManifest as resolveManifest2,
|
|
25377
26427
|
validateManifest,
|
|
25378
26428
|
loadManifest,
|
|
25379
|
-
isCapabilitySubset as
|
|
26429
|
+
isCapabilitySubset as isCapabilitySubset4,
|
|
25380
26430
|
expandActionShortNames,
|
|
25381
26431
|
expandPermissionEntries as expandPermissionEntries2,
|
|
25382
26432
|
expandPermissionEntry,
|
|
@@ -25385,7 +26435,7 @@ import {
|
|
|
25385
26435
|
} from "@tinycloud/sdk-core";
|
|
25386
26436
|
import {
|
|
25387
26437
|
DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
|
|
25388
|
-
KVService as
|
|
26438
|
+
KVService as KVService4,
|
|
25389
26439
|
PrefixedKVService
|
|
25390
26440
|
} from "@tinycloud/sdk-core";
|
|
25391
26441
|
import { SQLService as SQLService3, SQLAction, DatabaseHandle } from "@tinycloud/sdk-core";
|
|
@@ -25402,6 +26452,10 @@ import {
|
|
|
25402
26452
|
SecretsService as SecretsService2,
|
|
25403
26453
|
SECRET_NAME_RE,
|
|
25404
26454
|
canonicalizeSecretScope,
|
|
26455
|
+
appSecretScope,
|
|
26456
|
+
SECRET_SCOPE_RE,
|
|
26457
|
+
MAX_SECRET_SCOPE_LENGTH,
|
|
26458
|
+
TC832_RESERVED_SCOPES,
|
|
25405
26459
|
parseSecretCatalogKey,
|
|
25406
26460
|
resolveSecretListPrefix as resolveSecretListPrefix2,
|
|
25407
26461
|
resolveSecretPath as resolveSecretPath3
|
|
@@ -25465,7 +26519,7 @@ import {
|
|
|
25465
26519
|
SpaceService as SpaceService2,
|
|
25466
26520
|
SpaceErrorCodes,
|
|
25467
26521
|
createSpaceService,
|
|
25468
|
-
parseSpaceUri,
|
|
26522
|
+
parseSpaceUri as parseSpaceUri3,
|
|
25469
26523
|
buildSpaceUri,
|
|
25470
26524
|
makePublicSpaceId as makePublicSpaceId2,
|
|
25471
26525
|
Space
|
|
@@ -25476,7 +26530,849 @@ import {
|
|
|
25476
26530
|
UnsupportedFeatureError as UnsupportedFeatureError2,
|
|
25477
26531
|
checkNodeInfo as checkNodeInfo2
|
|
25478
26532
|
} from "@tinycloud/sdk-core";
|
|
25479
|
-
import { ServiceContext as
|
|
26533
|
+
import { ServiceContext as ServiceContext4 } from "@tinycloud/sdk-core";
|
|
26534
|
+
|
|
26535
|
+
// src/replication/sqlite.ts
|
|
26536
|
+
init_authority();
|
|
26537
|
+
import { createHash as createHash2, randomBytes as randomBytes2 } from "crypto";
|
|
26538
|
+
import { chmod, mkdir, open as openFile, readFile, realpath, rename, rm, stat } from "fs/promises";
|
|
26539
|
+
import { basename, dirname as dirname2, join as join2 } from "path";
|
|
26540
|
+
import { KVService as KVService3, ServiceContext as ServiceContext3, principalDid as principalDid2 } from "@tinycloud/sdk-core";
|
|
26541
|
+
import {
|
|
26542
|
+
canonicalReplicationIdentity,
|
|
26543
|
+
replicationIdentityKey,
|
|
26544
|
+
requiresSecretsOptIn as requiresSecretsOptIn2
|
|
26545
|
+
} from "@tinycloud/sdk-services";
|
|
26546
|
+
var ReplicaStorageError = class extends Error {
|
|
26547
|
+
constructor(code, message, options) {
|
|
26548
|
+
super(message);
|
|
26549
|
+
this.name = "ReplicaStorageError";
|
|
26550
|
+
this.code = code;
|
|
26551
|
+
this.cause = options?.cause;
|
|
26552
|
+
}
|
|
26553
|
+
};
|
|
26554
|
+
function storageError(code, action, error) {
|
|
26555
|
+
if (error instanceof ReplicaStorageError) return error;
|
|
26556
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
26557
|
+
return new ReplicaStorageError(code, `${action} failed: ${detail}`, { cause: error });
|
|
26558
|
+
}
|
|
26559
|
+
function isNotFound(error) {
|
|
26560
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
26561
|
+
}
|
|
26562
|
+
async function pathExists(path) {
|
|
26563
|
+
try {
|
|
26564
|
+
await stat(path);
|
|
26565
|
+
return true;
|
|
26566
|
+
} catch (error) {
|
|
26567
|
+
if (isNotFound(error)) return false;
|
|
26568
|
+
throw storageError("STORAGE_ERROR", `Reading ${path}`, error);
|
|
26569
|
+
}
|
|
26570
|
+
}
|
|
26571
|
+
function principalOf(did) {
|
|
26572
|
+
return principalDid2(did);
|
|
26573
|
+
}
|
|
26574
|
+
var BASE32 = "abcdefghijklmnopqrstuvwxyz234567";
|
|
26575
|
+
function sha256Base32(material) {
|
|
26576
|
+
const digest = createHash2("sha256").update(material, "utf8").digest();
|
|
26577
|
+
let out = "";
|
|
26578
|
+
let buffer = 0;
|
|
26579
|
+
let bits = 0;
|
|
26580
|
+
for (const byte of digest) {
|
|
26581
|
+
buffer = buffer << 8 | byte;
|
|
26582
|
+
bits += 8;
|
|
26583
|
+
while (bits >= 5) {
|
|
26584
|
+
out += BASE32[buffer >>> bits - 5 & 31];
|
|
26585
|
+
bits -= 5;
|
|
26586
|
+
}
|
|
26587
|
+
}
|
|
26588
|
+
if (bits > 0) out += BASE32[buffer << 5 - bits & 31];
|
|
26589
|
+
return out.slice(0, 26);
|
|
26590
|
+
}
|
|
26591
|
+
function partitionDirOf(root, identity) {
|
|
26592
|
+
return join2(root, sha256Base32(replicationIdentityKey(identity)));
|
|
26593
|
+
}
|
|
26594
|
+
function replicaHashOf(prefix, deviceDid) {
|
|
26595
|
+
return sha256Base32(`${prefix}
|
|
26596
|
+
${principalOf(deviceDid)}`);
|
|
26597
|
+
}
|
|
26598
|
+
async function writeJsonAtomic(path, value, dirSync = fsyncDir) {
|
|
26599
|
+
const tmp = `${path}.${randomBytes2(6).toString("hex")}.tmp`;
|
|
26600
|
+
let handle;
|
|
26601
|
+
try {
|
|
26602
|
+
handle = await openFile(tmp, "w", 384);
|
|
26603
|
+
await handle.writeFile(JSON.stringify(value), "utf8");
|
|
26604
|
+
await handle.sync();
|
|
26605
|
+
await handle.close();
|
|
26606
|
+
await rename(tmp, path);
|
|
26607
|
+
await dirSync(dirname2(path));
|
|
26608
|
+
await chmod(path, 384).catch(() => void 0);
|
|
26609
|
+
} catch (error) {
|
|
26610
|
+
try {
|
|
26611
|
+
await handle?.close();
|
|
26612
|
+
} catch {
|
|
26613
|
+
}
|
|
26614
|
+
await rm(tmp, { force: true }).catch(() => void 0);
|
|
26615
|
+
throw storageError("STORAGE_ERROR", `Writing ${path}`, error);
|
|
26616
|
+
}
|
|
26617
|
+
}
|
|
26618
|
+
var DIR_SYNC_UNSUPPORTED = {
|
|
26619
|
+
ENOTSUP: true,
|
|
26620
|
+
ENOSYS: true,
|
|
26621
|
+
EINVAL: true,
|
|
26622
|
+
EISDIR: true,
|
|
26623
|
+
EPERM: true
|
|
26624
|
+
};
|
|
26625
|
+
async function fsyncDir(dir) {
|
|
26626
|
+
let handle;
|
|
26627
|
+
try {
|
|
26628
|
+
handle = await openFile(dir, "r");
|
|
26629
|
+
await handle.sync();
|
|
26630
|
+
} catch (error) {
|
|
26631
|
+
const code = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
|
|
26632
|
+
if (typeof code === "string" && DIR_SYNC_UNSUPPORTED[code] === true) return;
|
|
26633
|
+
throw storageError("STORAGE_ERROR", `Syncing ${dir}`, error);
|
|
26634
|
+
} finally {
|
|
26635
|
+
await handle?.close().catch(() => void 0);
|
|
26636
|
+
}
|
|
26637
|
+
}
|
|
26638
|
+
async function readJsonFile(path) {
|
|
26639
|
+
try {
|
|
26640
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
26641
|
+
} catch (error) {
|
|
26642
|
+
if (isNotFound(error)) return void 0;
|
|
26643
|
+
throw storageError("STORAGE_ERROR", `Reading ${path}`, error);
|
|
26644
|
+
}
|
|
26645
|
+
}
|
|
26646
|
+
function identityRecordOf(value) {
|
|
26647
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
26648
|
+
return void 0;
|
|
26649
|
+
}
|
|
26650
|
+
if (!("host" in value) || !("space" in value) || !("principal" in value) || typeof value.host !== "string" || typeof value.space !== "string" || typeof value.principal !== "string") {
|
|
26651
|
+
return void 0;
|
|
26652
|
+
}
|
|
26653
|
+
return { host: value.host, space: value.space, principal: value.principal };
|
|
26654
|
+
}
|
|
26655
|
+
function deviceRecordOf(value) {
|
|
26656
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
26657
|
+
return void 0;
|
|
26658
|
+
}
|
|
26659
|
+
if (!("did" in value) || !("jwk" in value) || typeof value.did !== "string" || value.jwk === null || typeof value.jwk !== "object") {
|
|
26660
|
+
return void 0;
|
|
26661
|
+
}
|
|
26662
|
+
return { did: value.did, jwk: value.jwk };
|
|
26663
|
+
}
|
|
26664
|
+
function sameIdentity(a, b) {
|
|
26665
|
+
try {
|
|
26666
|
+
const left = canonicalReplicationIdentity(a);
|
|
26667
|
+
const right = canonicalReplicationIdentity(b);
|
|
26668
|
+
return left.host === right.host && left.space === right.space && left.principal === right.principal;
|
|
26669
|
+
} catch {
|
|
26670
|
+
return false;
|
|
26671
|
+
}
|
|
26672
|
+
}
|
|
26673
|
+
async function ensurePartitionIdentity(dir, identity, dirSync) {
|
|
26674
|
+
try {
|
|
26675
|
+
await mkdir(dir, { recursive: true, mode: 448 });
|
|
26676
|
+
await chmod(dir, 448);
|
|
26677
|
+
} catch (error) {
|
|
26678
|
+
throw storageError("STORAGE_ERROR", `Creating ${dir}`, error);
|
|
26679
|
+
}
|
|
26680
|
+
const file = join2(dir, "identity.json");
|
|
26681
|
+
const existing = await readJsonFile(file);
|
|
26682
|
+
if (existing === void 0) {
|
|
26683
|
+
await writeJsonAtomic(file, { v: 1, ...canonicalReplicationIdentity(identity) }, dirSync);
|
|
26684
|
+
return;
|
|
26685
|
+
}
|
|
26686
|
+
const record = identityRecordOf(existing);
|
|
26687
|
+
if (record === void 0 || !sameIdentity(record, identity)) {
|
|
26688
|
+
throw new ReplicaStorageError(
|
|
26689
|
+
"STORAGE_ERROR",
|
|
26690
|
+
`The partition at ${dir} belongs to a different identity.`
|
|
26691
|
+
);
|
|
26692
|
+
}
|
|
26693
|
+
}
|
|
26694
|
+
var PENDING_WRITE_LOCKS = /* @__PURE__ */ new Map();
|
|
26695
|
+
function serializePendingWrite(file, section) {
|
|
26696
|
+
const tail = PENDING_WRITE_LOCKS.get(file) ?? Promise.resolve();
|
|
26697
|
+
const entered = tail.then(section);
|
|
26698
|
+
const next = entered.then(
|
|
26699
|
+
() => void 0,
|
|
26700
|
+
() => void 0
|
|
26701
|
+
);
|
|
26702
|
+
PENDING_WRITE_LOCKS.set(file, next);
|
|
26703
|
+
void next.then(() => {
|
|
26704
|
+
if (PENDING_WRITE_LOCKS.get(file) === next) PENDING_WRITE_LOCKS.delete(file);
|
|
26705
|
+
});
|
|
26706
|
+
return entered;
|
|
26707
|
+
}
|
|
26708
|
+
function parsePendingState(text, file, identity) {
|
|
26709
|
+
let state;
|
|
26710
|
+
try {
|
|
26711
|
+
state = JSON.parse(text);
|
|
26712
|
+
} catch (error) {
|
|
26713
|
+
throw storageError("STORAGE_ERROR", `Parsing ${file}`, error);
|
|
26714
|
+
}
|
|
26715
|
+
if (state === null || typeof state !== "object" || !("v" in state) || state.v !== 2 || !("identity" in state) || !("committedEpoch" in state) || typeof state.committedEpoch !== "number" || !("seq" in state) || typeof state.seq !== "number" || !("records" in state) || !Array.isArray(state.records)) {
|
|
26716
|
+
throw new ReplicaStorageError("STORAGE_ERROR", `Pending-write state at ${file} is corrupt.`);
|
|
26717
|
+
}
|
|
26718
|
+
const storedIdentity = identityRecordOf(state.identity);
|
|
26719
|
+
if (storedIdentity === void 0 || !sameIdentity(storedIdentity, identity)) {
|
|
26720
|
+
throw new ReplicaStorageError("STORAGE_ERROR", `Pending-write state at ${file} belongs to a different identity.`);
|
|
26721
|
+
}
|
|
26722
|
+
return state;
|
|
26723
|
+
}
|
|
26724
|
+
var _dir, _guard, _dirSync, _file, _FilePendingWriteStore_instances, canonicalFile_fn;
|
|
26725
|
+
var FilePendingWriteStore = class {
|
|
26726
|
+
constructor(identity, dir, guard, dirSync) {
|
|
26727
|
+
__privateAdd(this, _FilePendingWriteStore_instances);
|
|
26728
|
+
this.durable = true;
|
|
26729
|
+
__privateAdd(this, _dir);
|
|
26730
|
+
__privateAdd(this, _guard);
|
|
26731
|
+
__privateAdd(this, _dirSync);
|
|
26732
|
+
__privateAdd(this, _file);
|
|
26733
|
+
this.identity = identity;
|
|
26734
|
+
__privateSet(this, _dir, dir);
|
|
26735
|
+
__privateSet(this, _guard, guard);
|
|
26736
|
+
__privateSet(this, _dirSync, dirSync);
|
|
26737
|
+
}
|
|
26738
|
+
async read() {
|
|
26739
|
+
const file = await __privateMethod(this, _FilePendingWriteStore_instances, canonicalFile_fn).call(this);
|
|
26740
|
+
let text;
|
|
26741
|
+
try {
|
|
26742
|
+
text = await readFile(file, "utf8");
|
|
26743
|
+
} catch (error) {
|
|
26744
|
+
if (isNotFound(error)) {
|
|
26745
|
+
return { v: 2, identity: this.identity, committedEpoch: 0, seq: 0, records: [] };
|
|
26746
|
+
}
|
|
26747
|
+
throw storageError("STORAGE_ERROR", `Reading ${file}`, error);
|
|
26748
|
+
}
|
|
26749
|
+
return parsePendingState(text, file, this.identity);
|
|
26750
|
+
}
|
|
26751
|
+
async readExisting() {
|
|
26752
|
+
let file;
|
|
26753
|
+
try {
|
|
26754
|
+
file = join2(await realpath(__privateGet(this, _dir)), "pending.json");
|
|
26755
|
+
} catch (error) {
|
|
26756
|
+
if (isNotFound(error)) return void 0;
|
|
26757
|
+
throw storageError("STORAGE_ERROR", `Resolving pending-write state in ${__privateGet(this, _dir)}`, error);
|
|
26758
|
+
}
|
|
26759
|
+
try {
|
|
26760
|
+
const text = await readFile(file, "utf8");
|
|
26761
|
+
return parsePendingState(text, file, this.identity);
|
|
26762
|
+
} catch (error) {
|
|
26763
|
+
if (isNotFound(error)) return void 0;
|
|
26764
|
+
if (error instanceof ReplicaStorageError) throw error;
|
|
26765
|
+
throw storageError("STORAGE_ERROR", `Reading ${file}`, error);
|
|
26766
|
+
}
|
|
26767
|
+
}
|
|
26768
|
+
async update(mutate) {
|
|
26769
|
+
const file = await __privateMethod(this, _FilePendingWriteStore_instances, canonicalFile_fn).call(this);
|
|
26770
|
+
return serializePendingWrite(file, async () => {
|
|
26771
|
+
let currentFile;
|
|
26772
|
+
try {
|
|
26773
|
+
currentFile = join2(await realpath(dirname2(file)), basename(file));
|
|
26774
|
+
} catch (error) {
|
|
26775
|
+
throw storageError("STORAGE_ERROR", "Resolving the open pending-write partition", error);
|
|
26776
|
+
}
|
|
26777
|
+
if (currentFile !== file) {
|
|
26778
|
+
throw new ReplicaStorageError(
|
|
26779
|
+
"STORAGE_ERROR",
|
|
26780
|
+
`The pending-write partition changed while open: ${dirname2(file)}.`
|
|
26781
|
+
);
|
|
26782
|
+
}
|
|
26783
|
+
return __privateGet(this, _guard).call(this, async () => {
|
|
26784
|
+
const state = await this.read();
|
|
26785
|
+
const result = mutate(state);
|
|
26786
|
+
await writeJsonAtomic(file, state, __privateGet(this, _dirSync));
|
|
26787
|
+
return result;
|
|
26788
|
+
});
|
|
26789
|
+
});
|
|
26790
|
+
}
|
|
26791
|
+
};
|
|
26792
|
+
_dir = new WeakMap();
|
|
26793
|
+
_guard = new WeakMap();
|
|
26794
|
+
_dirSync = new WeakMap();
|
|
26795
|
+
_file = new WeakMap();
|
|
26796
|
+
_FilePendingWriteStore_instances = new WeakSet();
|
|
26797
|
+
canonicalFile_fn = async function() {
|
|
26798
|
+
__privateGet(this, _file) ?? __privateSet(this, _file, (async () => {
|
|
26799
|
+
await ensurePartitionIdentity(__privateGet(this, _dir), this.identity, __privateGet(this, _dirSync));
|
|
26800
|
+
const file = join2(__privateGet(this, _dir), "pending.json");
|
|
26801
|
+
try {
|
|
26802
|
+
return join2(await realpath(dirname2(file)), basename(file));
|
|
26803
|
+
} catch (error) {
|
|
26804
|
+
throw storageError("STORAGE_ERROR", `Resolving ${file}`, error);
|
|
26805
|
+
}
|
|
26806
|
+
})());
|
|
26807
|
+
return __privateGet(this, _file);
|
|
26808
|
+
};
|
|
26809
|
+
async function loadOrCreateDevice(idDir, guard, createDevice, dirSync) {
|
|
26810
|
+
return guard(async () => {
|
|
26811
|
+
try {
|
|
26812
|
+
await mkdir(idDir, { recursive: true, mode: 448 });
|
|
26813
|
+
await chmod(idDir, 448);
|
|
26814
|
+
} catch (error) {
|
|
26815
|
+
throw storageError("STORAGE_ERROR", `Creating ${idDir}`, error);
|
|
26816
|
+
}
|
|
26817
|
+
const file = join2(idDir, "device.jwk");
|
|
26818
|
+
const existing = await readJsonFile(file);
|
|
26819
|
+
if (existing !== void 0) {
|
|
26820
|
+
const device2 = deviceRecordOf(existing);
|
|
26821
|
+
if (device2 === void 0) {
|
|
26822
|
+
throw new ReplicaStorageError(
|
|
26823
|
+
"STORAGE_ERROR",
|
|
26824
|
+
`The replica device key at ${file} is unreadable; remove the partition or restore it.`
|
|
26825
|
+
);
|
|
26826
|
+
}
|
|
26827
|
+
return device2;
|
|
26828
|
+
}
|
|
26829
|
+
const device = await createDevice();
|
|
26830
|
+
await writeJsonAtomic(file, { did: device.did, jwk: device.jwk }, dirSync);
|
|
26831
|
+
return device;
|
|
26832
|
+
});
|
|
26833
|
+
}
|
|
26834
|
+
async function createDeviceKey() {
|
|
26835
|
+
const wasm = await import("@tinycloud/node-sdk-wasm");
|
|
26836
|
+
const manager = new wasm.TCWSessionManager();
|
|
26837
|
+
const keyId = manager.createSessionKey("replica-device") ?? "replica-device";
|
|
26838
|
+
const jwkText = manager.jwk(keyId);
|
|
26839
|
+
if (jwkText === null || jwkText === void 0) {
|
|
26840
|
+
throw new ReplicaStorageError(
|
|
26841
|
+
"STORAGE_ERROR",
|
|
26842
|
+
"The session manager returned no JWK for the replica device key."
|
|
26843
|
+
);
|
|
26844
|
+
}
|
|
26845
|
+
return { did: manager.getDID(keyId), jwk: JSON.parse(jwkText) };
|
|
26846
|
+
}
|
|
26847
|
+
var SYNC_LEASE_TTL_MS = 12e4;
|
|
26848
|
+
var FENCE_FILE = "fence.json";
|
|
26849
|
+
async function readFence(replicaDir) {
|
|
26850
|
+
const fence = await readJsonFile(join2(replicaDir, FENCE_FILE));
|
|
26851
|
+
return typeof fence === "object" && fence !== null && "syncedThroughEpoch" in fence && typeof fence.syncedThroughEpoch === "number" ? fence.syncedThroughEpoch : 0;
|
|
26852
|
+
}
|
|
26853
|
+
function newestGrantInfo(replica, record, pending, spec) {
|
|
26854
|
+
if (record === null) return null;
|
|
26855
|
+
const parsed = replica.parseUcanGrant(record.bytes);
|
|
26856
|
+
return {
|
|
26857
|
+
cid: record.cid,
|
|
26858
|
+
parentCid: parsed.prf[0] ?? null,
|
|
26859
|
+
expiresAt: record.expiresAt === null ? null : record.expiresAt * 1e3,
|
|
26860
|
+
state: pending !== null && record === pending ? "pending" : "active",
|
|
26861
|
+
unconstrained: ucanAttUnconstrainedFor(parsed.att, spec.space, spec.prefix)
|
|
26862
|
+
};
|
|
26863
|
+
}
|
|
26864
|
+
var _device, _store, _replicaDir, _replica, _guard2, _dirSync2, _generation, _ino, _transportOverride, _closed, _inFlight, _transportFactory, _SqliteReplicaHandle_instances, assertOpen_fn, assertLive_fn, transports_fn;
|
|
26865
|
+
var SqliteReplicaHandle = class {
|
|
26866
|
+
constructor(input) {
|
|
26867
|
+
__privateAdd(this, _SqliteReplicaHandle_instances);
|
|
26868
|
+
__privateAdd(this, _device);
|
|
26869
|
+
__privateAdd(this, _store);
|
|
26870
|
+
__privateAdd(this, _replicaDir);
|
|
26871
|
+
__privateAdd(this, _replica);
|
|
26872
|
+
__privateAdd(this, _guard2);
|
|
26873
|
+
__privateAdd(this, _dirSync2);
|
|
26874
|
+
/** The replica row generation this handle opened under — the durable fence a purge commits past (browser-parity wipe). */
|
|
26875
|
+
__privateAdd(this, _generation);
|
|
26876
|
+
/** The inode of `replica.db` at open — a directory recreated in place is a different replica. */
|
|
26877
|
+
__privateAdd(this, _ino);
|
|
26878
|
+
__privateAdd(this, _transportOverride);
|
|
26879
|
+
__privateAdd(this, _closed, false);
|
|
26880
|
+
__privateAdd(this, _inFlight, /* @__PURE__ */ new Set());
|
|
26881
|
+
__privateAdd(this, _transportFactory);
|
|
26882
|
+
this.spec = input.spec;
|
|
26883
|
+
__privateSet(this, _device, input.device);
|
|
26884
|
+
this.deviceDid = input.device.did;
|
|
26885
|
+
__privateSet(this, _store, input.store);
|
|
26886
|
+
__privateSet(this, _replicaDir, input.replicaDir);
|
|
26887
|
+
__privateSet(this, _replica, input.replica);
|
|
26888
|
+
__privateSet(this, _guard2, input.guard);
|
|
26889
|
+
__privateSet(this, _transportOverride, input.transportFor);
|
|
26890
|
+
__privateSet(this, _dirSync2, input.dirSync);
|
|
26891
|
+
__privateSet(this, _generation, input.generation);
|
|
26892
|
+
__privateSet(this, _ino, input.ino);
|
|
26893
|
+
}
|
|
26894
|
+
async get(key2) {
|
|
26895
|
+
await __privateMethod(this, _SqliteReplicaHandle_instances, assertLive_fn).call(this);
|
|
26896
|
+
const result = await new (__privateGet(this, _replica)).Replica({ store: __privateGet(this, _store) }).get(key2);
|
|
26897
|
+
const meta = {
|
|
26898
|
+
asOf: result.meta.asOf,
|
|
26899
|
+
coverage: result.meta.coverage,
|
|
26900
|
+
authority: result.meta.authority,
|
|
26901
|
+
syncedThroughEpoch: await readFence(__privateGet(this, _replicaDir))
|
|
26902
|
+
};
|
|
26903
|
+
switch (result.status) {
|
|
26904
|
+
case "present":
|
|
26905
|
+
return {
|
|
26906
|
+
status: "present",
|
|
26907
|
+
key: result.key,
|
|
26908
|
+
value: result.value,
|
|
26909
|
+
etag: result.etag,
|
|
26910
|
+
metadata: result.metadata,
|
|
26911
|
+
meta
|
|
26912
|
+
};
|
|
26913
|
+
case "content_missing":
|
|
26914
|
+
return {
|
|
26915
|
+
status: "content_missing",
|
|
26916
|
+
key: result.key,
|
|
26917
|
+
etag: result.etag,
|
|
26918
|
+
metadata: result.metadata,
|
|
26919
|
+
meta
|
|
26920
|
+
};
|
|
26921
|
+
default:
|
|
26922
|
+
return { status: result.status, key: result.key, meta };
|
|
26923
|
+
}
|
|
26924
|
+
}
|
|
26925
|
+
async list(options) {
|
|
26926
|
+
await __privateMethod(this, _SqliteReplicaHandle_instances, assertLive_fn).call(this);
|
|
26927
|
+
const result = await new (__privateGet(this, _replica)).Replica({ store: __privateGet(this, _store) }).list({
|
|
26928
|
+
prefix: options.prefix,
|
|
26929
|
+
...options.after === void 0 ? {} : { after: options.after },
|
|
26930
|
+
...options.limit === void 0 ? {} : { limit: options.limit }
|
|
26931
|
+
});
|
|
26932
|
+
return {
|
|
26933
|
+
keys: result.entries.map((entry) => entry.key),
|
|
26934
|
+
meta: {
|
|
26935
|
+
asOf: result.meta.asOf,
|
|
26936
|
+
coverage: result.meta.coverage,
|
|
26937
|
+
authority: result.meta.authority,
|
|
26938
|
+
syncedThroughEpoch: await readFence(__privateGet(this, _replicaDir))
|
|
26939
|
+
}
|
|
26940
|
+
};
|
|
26941
|
+
}
|
|
26942
|
+
/**
|
|
26943
|
+
* The newest installed grant — the pending one when a not-yet-promoted
|
|
26944
|
+
* install exists — with `parentCid` = signed `prf[0]` and `unconstrained`
|
|
26945
|
+
* computed from the signed `att` (replica's own grantCovers ignores
|
|
26946
|
+
* caveats, so the adapter computes it, §2.1). A constrained installed
|
|
26947
|
+
* grant is still reported (unconstrained: false) and never reused.
|
|
26948
|
+
*/
|
|
26949
|
+
async grant() {
|
|
26950
|
+
await __privateMethod(this, _SqliteReplicaHandle_instances, assertLive_fn).call(this);
|
|
26951
|
+
const state = await __privateGet(this, _store).open();
|
|
26952
|
+
if (state === null) return null;
|
|
26953
|
+
return newestGrantInfo(
|
|
26954
|
+
__privateGet(this, _replica),
|
|
26955
|
+
state.pendingGrant ?? state.grant,
|
|
26956
|
+
state.pendingGrant,
|
|
26957
|
+
this.spec
|
|
26958
|
+
);
|
|
26959
|
+
}
|
|
26960
|
+
async installGrant(ucan) {
|
|
26961
|
+
await __privateMethod(this, _SqliteReplicaHandle_instances, assertLive_fn).call(this);
|
|
26962
|
+
const parsed = __privateGet(this, _replica).parseUcanGrant(ucan);
|
|
26963
|
+
const state = await __privateGet(this, _store).open();
|
|
26964
|
+
if (state === null) {
|
|
26965
|
+
throw new (__privateGet(this, _replica)).ReplicaError(
|
|
26966
|
+
__privateGet(this, _replica).ReplicaErrorCode.NOT_FOUND,
|
|
26967
|
+
"The replica has not been created."
|
|
26968
|
+
);
|
|
26969
|
+
}
|
|
26970
|
+
__privateGet(this, _replica).assertGrantInstallable(parsed, {
|
|
26971
|
+
deviceDid: principalOf(__privateGet(this, _device).did),
|
|
26972
|
+
space: this.spec.space,
|
|
26973
|
+
prefix: this.spec.prefix,
|
|
26974
|
+
now: Date.now()
|
|
26975
|
+
});
|
|
26976
|
+
if (!ucanAttUnconstrainedFor(parsed.att, this.spec.space, this.spec.prefix)) {
|
|
26977
|
+
throw new (__privateGet(this, _replica)).ReplicaError(
|
|
26978
|
+
__privateGet(this, _replica).ReplicaErrorCode.GRANT_NOT_COVERING,
|
|
26979
|
+
"The grant's get or sync coverage carries caveats; re-mint instead of installing it."
|
|
26980
|
+
);
|
|
26981
|
+
}
|
|
26982
|
+
await __privateGet(this, _store).installGrant(parsed);
|
|
26983
|
+
return newestGrantInfo(__privateGet(this, _replica), parsed, parsed, this.spec);
|
|
26984
|
+
}
|
|
26985
|
+
async sync(options) {
|
|
26986
|
+
await __privateMethod(this, _SqliteReplicaHandle_instances, assertLive_fn).call(this);
|
|
26987
|
+
const transportFor = await __privateMethod(this, _SqliteReplicaHandle_instances, transports_fn).call(this);
|
|
26988
|
+
const abort = new AbortController();
|
|
26989
|
+
options.signal.throwIfAborted();
|
|
26990
|
+
const onAbort = () => abort.abort(options.signal.reason);
|
|
26991
|
+
options.signal.addEventListener("abort", onAbort, { once: true });
|
|
26992
|
+
const engine = new (__privateGet(this, _replica)).Replica({
|
|
26993
|
+
store: __privateGet(this, _store),
|
|
26994
|
+
transportFor
|
|
26995
|
+
});
|
|
26996
|
+
const running = engine.sync({ signal: abort.signal });
|
|
26997
|
+
const registration = { promise: running, abort: () => abort.abort() };
|
|
26998
|
+
__privateGet(this, _inFlight).add(registration);
|
|
26999
|
+
try {
|
|
27000
|
+
const report = await running;
|
|
27001
|
+
await __privateGet(this, _guard2).call(this, () => writeJsonAtomic(
|
|
27002
|
+
join2(__privateGet(this, _replicaDir), FENCE_FILE),
|
|
27003
|
+
{ v: 1, syncedThroughEpoch: options.syncStartEpoch },
|
|
27004
|
+
__privateGet(this, _dirSync2)
|
|
27005
|
+
));
|
|
27006
|
+
return {
|
|
27007
|
+
status: "synced",
|
|
27008
|
+
pages: report.pages,
|
|
27009
|
+
changes: report.changes,
|
|
27010
|
+
deleted: report.deleted,
|
|
27011
|
+
fetched: report.fetched,
|
|
27012
|
+
contentMissing: report.contentMissing,
|
|
27013
|
+
coverage: report.coverage,
|
|
27014
|
+
syncedThroughEpoch: options.syncStartEpoch
|
|
27015
|
+
};
|
|
27016
|
+
} catch (error) {
|
|
27017
|
+
if (__privateGet(this, _replica).isReplicaError(error, __privateGet(this, _replica).ReplicaErrorCode.BUSY)) {
|
|
27018
|
+
return { status: "busy" };
|
|
27019
|
+
}
|
|
27020
|
+
throw error;
|
|
27021
|
+
} finally {
|
|
27022
|
+
__privateGet(this, _inFlight).delete(registration);
|
|
27023
|
+
options.signal.removeEventListener("abort", onAbort);
|
|
27024
|
+
}
|
|
27025
|
+
}
|
|
27026
|
+
async status() {
|
|
27027
|
+
await __privateMethod(this, _SqliteReplicaHandle_instances, assertLive_fn).call(this);
|
|
27028
|
+
const engine = new (__privateGet(this, _replica)).Replica({ store: __privateGet(this, _store) });
|
|
27029
|
+
const status = await engine.status();
|
|
27030
|
+
const syncedThroughEpoch = await readFence(__privateGet(this, _replicaDir));
|
|
27031
|
+
const state = await __privateGet(this, _store).open();
|
|
27032
|
+
const grant = state === null ? null : newestGrantInfo(
|
|
27033
|
+
__privateGet(this, _replica),
|
|
27034
|
+
state.pendingGrant ?? state.grant,
|
|
27035
|
+
state.pendingGrant,
|
|
27036
|
+
this.spec
|
|
27037
|
+
);
|
|
27038
|
+
return {
|
|
27039
|
+
coverage: status.coverage,
|
|
27040
|
+
lastSyncAt: status.lastSyncAt,
|
|
27041
|
+
syncedThroughEpoch,
|
|
27042
|
+
authority: { state: status.authority.state, expiresAt: status.authority.expiresAt },
|
|
27043
|
+
grant,
|
|
27044
|
+
counts: status.counts,
|
|
27045
|
+
bytes: status.bytes,
|
|
27046
|
+
lastError: status.lastError
|
|
27047
|
+
};
|
|
27048
|
+
}
|
|
27049
|
+
async close() {
|
|
27050
|
+
if (__privateGet(this, _closed)) return;
|
|
27051
|
+
__privateSet(this, _closed, true);
|
|
27052
|
+
for (const sync of __privateGet(this, _inFlight)) sync.abort();
|
|
27053
|
+
await Promise.allSettled([...__privateGet(this, _inFlight)].map((sync) => sync.promise));
|
|
27054
|
+
await __privateGet(this, _store).close();
|
|
27055
|
+
}
|
|
27056
|
+
};
|
|
27057
|
+
_device = new WeakMap();
|
|
27058
|
+
_store = new WeakMap();
|
|
27059
|
+
_replicaDir = new WeakMap();
|
|
27060
|
+
_replica = new WeakMap();
|
|
27061
|
+
_guard2 = new WeakMap();
|
|
27062
|
+
_dirSync2 = new WeakMap();
|
|
27063
|
+
_generation = new WeakMap();
|
|
27064
|
+
_ino = new WeakMap();
|
|
27065
|
+
_transportOverride = new WeakMap();
|
|
27066
|
+
_closed = new WeakMap();
|
|
27067
|
+
_inFlight = new WeakMap();
|
|
27068
|
+
_transportFactory = new WeakMap();
|
|
27069
|
+
_SqliteReplicaHandle_instances = new WeakSet();
|
|
27070
|
+
assertOpen_fn = function() {
|
|
27071
|
+
if (__privateGet(this, _closed)) {
|
|
27072
|
+
throw new (__privateGet(this, _replica)).ReplicaError(
|
|
27073
|
+
__privateGet(this, _replica).ReplicaErrorCode.CLOSED,
|
|
27074
|
+
"The replica handle is closed."
|
|
27075
|
+
);
|
|
27076
|
+
}
|
|
27077
|
+
};
|
|
27078
|
+
assertLive_fn = async function() {
|
|
27079
|
+
__privateMethod(this, _SqliteReplicaHandle_instances, assertOpen_fn).call(this);
|
|
27080
|
+
try {
|
|
27081
|
+
const stats = await stat(join2(__privateGet(this, _replicaDir), "replica.db"));
|
|
27082
|
+
if (__privateGet(this, _ino) !== void 0 && stats.ino !== __privateGet(this, _ino)) {
|
|
27083
|
+
throw new (__privateGet(this, _replica)).ReplicaError(
|
|
27084
|
+
__privateGet(this, _replica).ReplicaErrorCode.RESET_REQUIRED,
|
|
27085
|
+
"The replica was recreated; open it again to continue."
|
|
27086
|
+
);
|
|
27087
|
+
}
|
|
27088
|
+
} catch (error) {
|
|
27089
|
+
if (isNotFound(error)) {
|
|
27090
|
+
throw new (__privateGet(this, _replica)).ReplicaError(
|
|
27091
|
+
__privateGet(this, _replica).ReplicaErrorCode.RESET_REQUIRED,
|
|
27092
|
+
"The replica was purged; open it again to continue."
|
|
27093
|
+
);
|
|
27094
|
+
}
|
|
27095
|
+
throw error;
|
|
27096
|
+
}
|
|
27097
|
+
const state = await __privateGet(this, _store).open();
|
|
27098
|
+
if (state !== null && state.generation !== __privateGet(this, _generation)) {
|
|
27099
|
+
throw new (__privateGet(this, _replica)).ReplicaError(
|
|
27100
|
+
__privateGet(this, _replica).ReplicaErrorCode.RESET_REQUIRED,
|
|
27101
|
+
"The replica was purged or reopened; open it again to continue."
|
|
27102
|
+
);
|
|
27103
|
+
}
|
|
27104
|
+
};
|
|
27105
|
+
transports_fn = async function() {
|
|
27106
|
+
if (__privateGet(this, _transportOverride) !== void 0) {
|
|
27107
|
+
const override = __privateGet(this, _transportOverride);
|
|
27108
|
+
const spec = this.spec;
|
|
27109
|
+
const device = __privateGet(this, _device);
|
|
27110
|
+
return (grant) => override(grant, spec, device);
|
|
27111
|
+
}
|
|
27112
|
+
__privateGet(this, _transportFactory) ?? __privateSet(this, _transportFactory, (async () => {
|
|
27113
|
+
const wasm = await import("@tinycloud/node-sdk-wasm");
|
|
27114
|
+
const kvSyncTransport = __privateGet(this, _replica).kvSyncTransport;
|
|
27115
|
+
const spec = this.spec;
|
|
27116
|
+
const device = __privateGet(this, _device);
|
|
27117
|
+
const fetch2 = spec.fetch ?? globalThis.fetch.bind(globalThis);
|
|
27118
|
+
return (grant) => {
|
|
27119
|
+
const context = new ServiceContext3({
|
|
27120
|
+
invoke: wasm.invoke,
|
|
27121
|
+
invokeAny: wasm.invokeAny,
|
|
27122
|
+
fetch: fetch2,
|
|
27123
|
+
hosts: [spec.identity.host]
|
|
27124
|
+
});
|
|
27125
|
+
const kv = new KVService3({});
|
|
27126
|
+
kv.initialize(context);
|
|
27127
|
+
context.registerService("kv", kv);
|
|
27128
|
+
context.setSession({
|
|
27129
|
+
delegationHeader: { Authorization: new TextDecoder().decode(grant.bytes) },
|
|
27130
|
+
delegationCid: grant.cid,
|
|
27131
|
+
spaceId: spec.space,
|
|
27132
|
+
verificationMethod: device.did,
|
|
27133
|
+
jwk: device.jwk
|
|
27134
|
+
});
|
|
27135
|
+
return kvSyncTransport(kv);
|
|
27136
|
+
};
|
|
27137
|
+
})());
|
|
27138
|
+
return __privateGet(this, _transportFactory);
|
|
27139
|
+
};
|
|
27140
|
+
function createSqliteReplicaStorage(options) {
|
|
27141
|
+
const root = options.dir;
|
|
27142
|
+
const guard = options.guard ?? ((section) => section());
|
|
27143
|
+
const createDevice = options.createDevice ?? createDeviceKey;
|
|
27144
|
+
async function openHandle(spec) {
|
|
27145
|
+
const [replica, sqlite] = await Promise.all([
|
|
27146
|
+
// @ts-ignore — TS2307 when replica's dist is absent (CI builds node-sdk first).
|
|
27147
|
+
import("@tinycloud/replica"),
|
|
27148
|
+
// @ts-ignore — same as above.
|
|
27149
|
+
import("@tinycloud/replica/sqlite")
|
|
27150
|
+
]);
|
|
27151
|
+
if (requiresSecretsOptIn2(spec.space, spec.prefix) && spec.allowSecrets !== true) {
|
|
27152
|
+
throw new replica.ReplicaError(
|
|
27153
|
+
replica.ReplicaErrorCode.SECRETS_OPT_IN_REQUIRED,
|
|
27154
|
+
`Replicating ${spec.space}/kv/${spec.prefix} copies secret material to disk; pass allowSecrets to opt in.`
|
|
27155
|
+
);
|
|
27156
|
+
}
|
|
27157
|
+
const idDir = partitionDirOf(root, spec.identity);
|
|
27158
|
+
await ensurePartitionIdentity(idDir, spec.identity, options.dirSync);
|
|
27159
|
+
const createdDevice = spec.device ?? await loadOrCreateDevice(idDir, guard, createDevice, options.dirSync);
|
|
27160
|
+
const device = { ...createdDevice, did: principalOf(createdDevice.did) };
|
|
27161
|
+
const replicaHash = replicaHashOf(spec.prefix, device.did);
|
|
27162
|
+
const replicaDir = join2(idDir, "replicas", replicaHash);
|
|
27163
|
+
const store = await sqlite.SqliteReplicaStore.open(replicaDir, {
|
|
27164
|
+
create: true,
|
|
27165
|
+
guard
|
|
27166
|
+
});
|
|
27167
|
+
let generation;
|
|
27168
|
+
try {
|
|
27169
|
+
try {
|
|
27170
|
+
await store.init({
|
|
27171
|
+
name: spec.prefix.replace(/\/+$/, "") || "replica",
|
|
27172
|
+
replicaId: replicaHash,
|
|
27173
|
+
host: spec.identity.host,
|
|
27174
|
+
space: spec.space,
|
|
27175
|
+
prefix: spec.prefix,
|
|
27176
|
+
deviceDid: device.did,
|
|
27177
|
+
allowSecrets: spec.allowSecrets,
|
|
27178
|
+
localReadPolicy: "whileGrantValid",
|
|
27179
|
+
retentionGrantCid: null
|
|
27180
|
+
});
|
|
27181
|
+
} catch (error) {
|
|
27182
|
+
if (!replica.isReplicaError(error, replica.ReplicaErrorCode.CONFIG_MISMATCH)) {
|
|
27183
|
+
throw error;
|
|
27184
|
+
}
|
|
27185
|
+
}
|
|
27186
|
+
const state = await store.open();
|
|
27187
|
+
if (state === null) {
|
|
27188
|
+
throw new replica.ReplicaError(
|
|
27189
|
+
replica.ReplicaErrorCode.NOT_FOUND,
|
|
27190
|
+
"The replica row vanished during init."
|
|
27191
|
+
);
|
|
27192
|
+
}
|
|
27193
|
+
const config = state.config;
|
|
27194
|
+
if (config.host !== spec.identity.host || config.space !== spec.space || config.prefix !== spec.prefix) {
|
|
27195
|
+
throw new replica.ReplicaError(
|
|
27196
|
+
replica.ReplicaErrorCode.CONFIG_MISMATCH,
|
|
27197
|
+
`This replica follows ${config.host} ${config.space}/kv/${config.prefix}, not ${spec.identity.host} ${spec.space}/kv/${spec.prefix}.`
|
|
27198
|
+
);
|
|
27199
|
+
}
|
|
27200
|
+
if (principalOf(config.deviceDid) !== principalOf(device.did)) {
|
|
27201
|
+
throw new replica.ReplicaError(
|
|
27202
|
+
replica.ReplicaErrorCode.CONFIG_MISMATCH,
|
|
27203
|
+
"The stored replica belongs to a different device key; purge the partition to rekey."
|
|
27204
|
+
);
|
|
27205
|
+
}
|
|
27206
|
+
generation = state.generation;
|
|
27207
|
+
} catch (error) {
|
|
27208
|
+
await store.close().catch(() => void 0);
|
|
27209
|
+
throw error;
|
|
27210
|
+
}
|
|
27211
|
+
let ino;
|
|
27212
|
+
try {
|
|
27213
|
+
ino = (await stat(join2(replicaDir, "replica.db"))).ino;
|
|
27214
|
+
} catch (error) {
|
|
27215
|
+
if (!isNotFound(error)) throw error;
|
|
27216
|
+
}
|
|
27217
|
+
return new SqliteReplicaHandle({
|
|
27218
|
+
spec,
|
|
27219
|
+
device,
|
|
27220
|
+
store,
|
|
27221
|
+
replicaDir,
|
|
27222
|
+
replica,
|
|
27223
|
+
guard,
|
|
27224
|
+
transportFor: options.transportFor,
|
|
27225
|
+
dirSync: options.dirSync,
|
|
27226
|
+
generation,
|
|
27227
|
+
ino
|
|
27228
|
+
});
|
|
27229
|
+
}
|
|
27230
|
+
async function inspectStatus(spec, { signal }) {
|
|
27231
|
+
const checkAbort = () => {
|
|
27232
|
+
if (signal.aborted) throw signal.reason ?? Object.assign(new Error("Replica status inspection was aborted"), { code: "ABORTED" });
|
|
27233
|
+
};
|
|
27234
|
+
checkAbort();
|
|
27235
|
+
const idDir = partitionDirOf(root, spec.identity);
|
|
27236
|
+
if (!await pathExists(idDir)) return void 0;
|
|
27237
|
+
checkAbort();
|
|
27238
|
+
const identityFile = join2(idDir, "identity.json");
|
|
27239
|
+
const storedIdentity = identityRecordOf(await readJsonFile(identityFile));
|
|
27240
|
+
if (storedIdentity === void 0 || !sameIdentity(storedIdentity, spec.identity)) {
|
|
27241
|
+
throw new ReplicaStorageError("STORAGE_ERROR", `The partition at ${idDir} belongs to a different identity or has no identity record.`);
|
|
27242
|
+
}
|
|
27243
|
+
checkAbort();
|
|
27244
|
+
let device = spec.device;
|
|
27245
|
+
if (device === void 0) {
|
|
27246
|
+
const deviceFile = join2(idDir, "device.jwk");
|
|
27247
|
+
const storedDevice = await readJsonFile(deviceFile);
|
|
27248
|
+
if (storedDevice === void 0) return void 0;
|
|
27249
|
+
device = deviceRecordOf(storedDevice);
|
|
27250
|
+
if (device === void 0) throw new ReplicaStorageError("STORAGE_ERROR", `The replica device key at ${deviceFile} is unreadable.`);
|
|
27251
|
+
}
|
|
27252
|
+
checkAbort();
|
|
27253
|
+
const replicaDir = join2(idDir, "replicas", replicaHashOf(spec.prefix, device.did));
|
|
27254
|
+
if (!await pathExists(replicaDir)) return void 0;
|
|
27255
|
+
checkAbort();
|
|
27256
|
+
const [replica, sqlite] = await Promise.all([
|
|
27257
|
+
// @ts-ignore — TS2307 when replica's dist is absent (CI builds node-sdk first).
|
|
27258
|
+
import("@tinycloud/replica"),
|
|
27259
|
+
// @ts-ignore — same as above.
|
|
27260
|
+
import("@tinycloud/replica/sqlite")
|
|
27261
|
+
]);
|
|
27262
|
+
checkAbort();
|
|
27263
|
+
const inspected = await sqlite.SqliteReplicaStore.inspect(replicaDir, { signal });
|
|
27264
|
+
checkAbort();
|
|
27265
|
+
if (inspected === null) return void 0;
|
|
27266
|
+
const { state, status } = inspected;
|
|
27267
|
+
if (state.config.host !== spec.identity.host || state.config.space !== spec.space || state.config.prefix !== spec.prefix || principalOf(state.config.deviceDid) !== principalOf(device.did)) {
|
|
27268
|
+
throw new ReplicaStorageError("STORAGE_ERROR", `The replica at ${replicaDir} does not match the requested identity, space, prefix, or device.`);
|
|
27269
|
+
}
|
|
27270
|
+
const syncedThroughEpoch = await readFence(replicaDir);
|
|
27271
|
+
checkAbort();
|
|
27272
|
+
return {
|
|
27273
|
+
coverage: status.coverage,
|
|
27274
|
+
lastSyncAt: status.lastSyncAt,
|
|
27275
|
+
syncedThroughEpoch,
|
|
27276
|
+
authority: { state: status.authority.state, expiresAt: status.authority.expiresAt },
|
|
27277
|
+
grant: newestGrantInfo(replica, state.pendingGrant ?? state.grant, state.pendingGrant, spec),
|
|
27278
|
+
counts: status.counts,
|
|
27279
|
+
bytes: status.bytes,
|
|
27280
|
+
lastError: status.lastError
|
|
27281
|
+
};
|
|
27282
|
+
}
|
|
27283
|
+
async function purgePartition(target) {
|
|
27284
|
+
const [replica, sqlite] = await Promise.all([
|
|
27285
|
+
// @ts-ignore — TS2307 when replica's dist is absent (CI builds node-sdk first).
|
|
27286
|
+
import("@tinycloud/replica"),
|
|
27287
|
+
// @ts-ignore — same as above.
|
|
27288
|
+
import("@tinycloud/replica/sqlite")
|
|
27289
|
+
]);
|
|
27290
|
+
const idDir = partitionDirOf(root, target.identity);
|
|
27291
|
+
if (!await pathExists(idDir)) return;
|
|
27292
|
+
const deviceDids = [];
|
|
27293
|
+
const deviceFile = join2(idDir, "device.jwk");
|
|
27294
|
+
const stored = await readJsonFile(deviceFile);
|
|
27295
|
+
if (stored !== void 0) {
|
|
27296
|
+
const device = deviceRecordOf(stored);
|
|
27297
|
+
if (device === void 0) {
|
|
27298
|
+
throw new ReplicaStorageError(
|
|
27299
|
+
"STORAGE_ERROR",
|
|
27300
|
+
`The replica device key at ${deviceFile} is unreadable.`
|
|
27301
|
+
);
|
|
27302
|
+
}
|
|
27303
|
+
deviceDids.push(device.did);
|
|
27304
|
+
}
|
|
27305
|
+
if (target.sessionDeviceDid !== void 0) deviceDids.push(target.sessionDeviceDid);
|
|
27306
|
+
const devices = [...new Map(deviceDids.map((did) => [principalOf(did), did])).values()];
|
|
27307
|
+
let firstError;
|
|
27308
|
+
for (const deviceDid of devices) {
|
|
27309
|
+
const replicaDir = join2(idDir, "replicas", replicaHashOf(target.prefix, deviceDid));
|
|
27310
|
+
if (!await pathExists(replicaDir)) continue;
|
|
27311
|
+
try {
|
|
27312
|
+
let store;
|
|
27313
|
+
try {
|
|
27314
|
+
store = await sqlite.SqliteReplicaStore.open(replicaDir, { create: false, guard });
|
|
27315
|
+
} catch (error) {
|
|
27316
|
+
if (replica.isReplicaError(error, replica.ReplicaErrorCode.NOT_FOUND)) {
|
|
27317
|
+
await guard(() => rm(replicaDir, { recursive: true, force: true }));
|
|
27318
|
+
continue;
|
|
27319
|
+
}
|
|
27320
|
+
throw error;
|
|
27321
|
+
}
|
|
27322
|
+
try {
|
|
27323
|
+
const state = await store.open();
|
|
27324
|
+
if (state === null) {
|
|
27325
|
+
await store.close().catch(() => void 0);
|
|
27326
|
+
await guard(() => rm(replicaDir, { recursive: true, force: true }));
|
|
27327
|
+
continue;
|
|
27328
|
+
}
|
|
27329
|
+
const lease = await store.acquireSyncLease(SYNC_LEASE_TTL_MS);
|
|
27330
|
+
if (lease === null) {
|
|
27331
|
+
throw new replica.ReplicaError(
|
|
27332
|
+
replica.ReplicaErrorCode.BUSY,
|
|
27333
|
+
"Another process holds this replica's sync lease."
|
|
27334
|
+
);
|
|
27335
|
+
}
|
|
27336
|
+
try {
|
|
27337
|
+
if (state.revoked === null) {
|
|
27338
|
+
await store.reset(lease, "purged");
|
|
27339
|
+
}
|
|
27340
|
+
await store.destroy(lease);
|
|
27341
|
+
} finally {
|
|
27342
|
+
await store.releaseLease(lease).catch(() => void 0);
|
|
27343
|
+
}
|
|
27344
|
+
} finally {
|
|
27345
|
+
await store.close().catch(() => void 0);
|
|
27346
|
+
}
|
|
27347
|
+
} catch (error) {
|
|
27348
|
+
firstError ?? (firstError = error);
|
|
27349
|
+
}
|
|
27350
|
+
}
|
|
27351
|
+
if (firstError !== void 0) throw firstError;
|
|
27352
|
+
}
|
|
27353
|
+
return {
|
|
27354
|
+
kind: "sqlite",
|
|
27355
|
+
open: openHandle,
|
|
27356
|
+
inspectStatus,
|
|
27357
|
+
purge: purgePartition,
|
|
27358
|
+
// Always the durable partitioned file store (review): the runtime
|
|
27359
|
+
// must never fall back to volatile in-memory pending state — a
|
|
27360
|
+
// restart that lost ambiguous write records would serve stale data.
|
|
27361
|
+
// Without a guard the file store still durably serializes each update;
|
|
27362
|
+
// the guard only adds cross-process read-modify-write exclusion.
|
|
27363
|
+
pendingWrites(identity) {
|
|
27364
|
+
return new FilePendingWriteStore(
|
|
27365
|
+
identity,
|
|
27366
|
+
partitionDirOf(root, identity),
|
|
27367
|
+
guard,
|
|
27368
|
+
options.dirSync
|
|
27369
|
+
);
|
|
27370
|
+
}
|
|
27371
|
+
};
|
|
27372
|
+
}
|
|
27373
|
+
function sqliteReplicaStorage(options) {
|
|
27374
|
+
return createSqliteReplicaStorage(options);
|
|
27375
|
+
}
|
|
25480
27376
|
export {
|
|
25481
27377
|
ACCEPTED_MARKER_VERSIONS,
|
|
25482
27378
|
ACCOUNT_REGISTRY_PATH,
|
|
@@ -25514,7 +27410,8 @@ export {
|
|
|
25514
27410
|
HooksService3 as HooksService,
|
|
25515
27411
|
IdentityParseError,
|
|
25516
27412
|
InvalidRestoredSessionError,
|
|
25517
|
-
|
|
27413
|
+
KVService4 as KVService,
|
|
27414
|
+
MAX_SECRET_SCOPE_LENGTH,
|
|
25518
27415
|
ManifestValidationError,
|
|
25519
27416
|
MemorySessionStorage,
|
|
25520
27417
|
NETWORK_NAME_PATTERN,
|
|
@@ -25528,18 +27425,20 @@ export {
|
|
|
25528
27425
|
RUNTIME_DELEGATION_REJECTIONS,
|
|
25529
27426
|
RuntimeDelegationRejectedError,
|
|
25530
27427
|
SECRET_NAME_RE,
|
|
27428
|
+
SECRET_SCOPE_RE,
|
|
25531
27429
|
SQLAction,
|
|
25532
27430
|
SQLService3 as SQLService,
|
|
25533
27431
|
STORAGE_FULL_MESSAGE2 as STORAGE_FULL_MESSAGE,
|
|
25534
27432
|
STORAGE_WRITE_TOO_LARGE_MESSAGE2 as STORAGE_WRITE_TOO_LARGE_MESSAGE,
|
|
25535
27433
|
SecretsService2 as SecretsService,
|
|
25536
|
-
|
|
27434
|
+
ServiceContext4 as ServiceContext,
|
|
25537
27435
|
SessionExpiredError2 as SessionExpiredError,
|
|
25538
27436
|
SharingService2 as SharingService,
|
|
25539
27437
|
SilentNotificationHandler2 as SilentNotificationHandler,
|
|
25540
27438
|
Space,
|
|
25541
27439
|
SpaceErrorCodes,
|
|
25542
27440
|
SpaceService2 as SpaceService,
|
|
27441
|
+
TC832_RESERVED_SCOPES,
|
|
25543
27442
|
TinyCloud2 as TinyCloud,
|
|
25544
27443
|
TinyCloudDebugLogger,
|
|
25545
27444
|
TinyCloudNode,
|
|
@@ -25553,6 +27452,7 @@ export {
|
|
|
25553
27452
|
activateCompactRuntimeDelegation,
|
|
25554
27453
|
activateValidatedRuntimeDelegation,
|
|
25555
27454
|
addressStorageKey,
|
|
27455
|
+
appSecretScope,
|
|
25556
27456
|
buildCanonicalDecryptRequest,
|
|
25557
27457
|
buildDecryptAttenuation,
|
|
25558
27458
|
buildDecryptFacts,
|
|
@@ -25604,7 +27504,7 @@ export {
|
|
|
25604
27504
|
hexDecode,
|
|
25605
27505
|
hexEncode,
|
|
25606
27506
|
installTinyCloudDebugGlobals,
|
|
25607
|
-
|
|
27507
|
+
isCapabilitySubset4 as isCapabilitySubset,
|
|
25608
27508
|
isEvmAddress,
|
|
25609
27509
|
isNetworkId,
|
|
25610
27510
|
isStorageFullError2 as isStorageFullError,
|
|
@@ -25618,9 +27518,9 @@ export {
|
|
|
25618
27518
|
parseNetworkId3 as parseNetworkId,
|
|
25619
27519
|
parsePkhDid,
|
|
25620
27520
|
parseSecretCatalogKey,
|
|
25621
|
-
parseSpaceUri,
|
|
25622
|
-
|
|
25623
|
-
principalDid,
|
|
27521
|
+
parseSpaceUri3 as parseSpaceUri,
|
|
27522
|
+
pkhDid4 as pkhDid,
|
|
27523
|
+
principalDid3 as principalDid,
|
|
25624
27524
|
principalDidEquals4 as principalDidEquals,
|
|
25625
27525
|
resolveManifest2 as resolveManifest,
|
|
25626
27526
|
resolveSecretListPrefix2 as resolveSecretListPrefix,
|
|
@@ -25628,6 +27528,7 @@ export {
|
|
|
25628
27528
|
resourceCapabilitiesToSpaceAbilitiesMap2 as resourceCapabilitiesToSpaceAbilitiesMap,
|
|
25629
27529
|
serializeDelegation,
|
|
25630
27530
|
signedCompactUcanExpiry,
|
|
27531
|
+
sqliteReplicaStorage,
|
|
25631
27532
|
tinyCloudDebugLogger,
|
|
25632
27533
|
validateEnvelope,
|
|
25633
27534
|
validateManifest,
|