@tinycloud/sdk-core 2.4.0-beta.1 → 2.4.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -188,7 +188,15 @@ var PersistedSessionDataSchema = z2.object({
188
188
  /** Schema version for migrations */
189
189
  version: z2.string(),
190
190
  /** Optional ENS data */
191
- ens: EnsDataSchema2.optional()
191
+ ens: EnsDataSchema2.optional(),
192
+ /**
193
+ * TinyCloud hosts this session was created against. Persisted so a
194
+ * restored session resolves to the same node without re-running the
195
+ * registry/fallback resolution (or the wallet sign-in flow). Optional
196
+ * for backward compatibility with sessions persisted before this field
197
+ * existed — those restore and lazily re-resolve their hosts.
198
+ */
199
+ tinycloudHosts: z2.array(z2.string()).optional()
192
200
  });
193
201
  var TinyCloudSessionSchema = z2.object({
194
202
  /** User's Ethereum address */
@@ -970,6 +978,7 @@ var SpaceService = class {
970
978
  this._userDid = config.userDid;
971
979
  this.sharingService = config.sharingService;
972
980
  this.createDelegationFn = config.createDelegation;
981
+ this.onSpaceRegisteredFn = config.onSpaceRegistered;
973
982
  }
974
983
  /**
975
984
  * Update the service configuration.
@@ -985,6 +994,7 @@ var SpaceService = class {
985
994
  if (config.userDid !== void 0) this._userDid = config.userDid;
986
995
  if (config.sharingService) this.sharingService = config.sharingService;
987
996
  if (config.createDelegation) this.createDelegationFn = config.createDelegation;
997
+ if (config.onSpaceRegistered) this.onSpaceRegisteredFn = config.onSpaceRegistered;
988
998
  this.spaceCache.clear();
989
999
  this.infoCache.clear();
990
1000
  }
@@ -1035,6 +1045,9 @@ var SpaceService = class {
1035
1045
  spaces.push(...delegatedSpaces);
1036
1046
  }
1037
1047
  const uniqueSpaces = this.deduplicateSpaces(spaces);
1048
+ for (const space of uniqueSpaces) {
1049
+ this.notifySpaceRegistered(space);
1050
+ }
1038
1051
  return ok(uniqueSpaces);
1039
1052
  } catch (error) {
1040
1053
  return err(
@@ -1232,6 +1245,7 @@ var SpaceService = class {
1232
1245
  permissions: ["*"]
1233
1246
  };
1234
1247
  this.infoCache.set(spaceInfo.id, { info: spaceInfo, cachedAt: Date.now() });
1248
+ this.notifySpaceRegistered(spaceInfo);
1235
1249
  return ok(spaceInfo);
1236
1250
  } catch (error) {
1237
1251
  return err(
@@ -1244,6 +1258,11 @@ var SpaceService = class {
1244
1258
  );
1245
1259
  }
1246
1260
  }
1261
+ notifySpaceRegistered(space) {
1262
+ if (!this.onSpaceRegisteredFn) return;
1263
+ void Promise.resolve(this.onSpaceRegisteredFn(space)).catch(() => {
1264
+ });
1265
+ }
1247
1266
  // ===========================================================================
1248
1267
  // Get Space
1249
1268
  // ===========================================================================
@@ -2151,1474 +2170,2326 @@ var TinyCloud = class _TinyCloud {
2151
2170
  }
2152
2171
  };
2153
2172
 
2154
- // src/index.ts
2173
+ // src/account/AccountService.ts
2155
2174
  import {
2156
- ServiceContext as ServiceContext2,
2157
- KVService as KVService2,
2158
- PrefixedKVService,
2159
- ok as ok4,
2160
- err as err4,
2161
- serviceError as serviceError4,
2162
- ErrorCodes as ErrorCodes2,
2163
- defaultRetryPolicy as defaultRetryPolicy2,
2164
- SQLService as SQLService2,
2165
- DatabaseHandle,
2166
- SQLAction,
2167
- DuckDbService as DuckDbService2,
2168
- DuckDbDatabaseHandle,
2169
- DuckDbAction,
2170
- HooksService as HooksService2,
2171
- DataVaultService,
2172
- VaultHeaders,
2173
- VaultPublicSpaceKVActions,
2174
- createVaultCrypto,
2175
- SecretsService,
2176
- SECRET_NAME_RE as SECRET_NAME_RE2,
2177
- canonicalizeSecretScope,
2178
- resolveSecretListPrefix,
2179
- resolveSecretPath as resolveSecretPath2,
2180
- EncryptionService,
2181
- parseNetworkId as parseNetworkId2,
2182
- buildNetworkId as buildNetworkId2,
2183
- isNetworkId,
2184
- networkDiscoveryKey,
2185
- NetworkIdError,
2186
- ENCRYPTION_NETWORK_URN_PREFIX,
2187
- NETWORK_NAME_PATTERN,
2188
- canonicalizeEncryptionJson,
2189
- canonicalHashHex,
2190
- hexEncode,
2191
- hexDecode,
2192
- base64Encode,
2193
- base64Decode,
2194
- utf8Encode,
2195
- utf8Decode,
2196
- encryptToNetwork,
2197
- decryptEnvelopeWithKey,
2198
- validateEnvelope,
2199
- generateRandomReceiverKey,
2200
- deriveSignedReceiverKey,
2201
- buildCanonicalDecryptRequest,
2202
- buildDecryptFacts,
2203
- buildDecryptAttenuation,
2204
- buildDecryptInvocation,
2205
- checkDecryptInvocationInput,
2206
- verifyDecryptResponse,
2207
- canonicalSignedResponse,
2208
- openWrappedKey,
2209
- discoverNetwork,
2210
- ensureNetworkUsableForDecrypt,
2211
- DEFAULT_ENCRYPTION_ALG,
2212
- ENVELOPE_VERSION,
2213
- DEFAULT_KEY_VERSION,
2214
- DECRYPT_FACT_TYPE,
2215
- DECRYPT_RESULT_TYPE,
2216
- DECRYPT_ACTION,
2217
- ENCRYPTION_SERVICE,
2218
- ENCRYPTION_SERVICE_SHORT,
2219
- encryptionError
2175
+ err as err3,
2176
+ ok as ok3,
2177
+ serviceError as serviceError3
2220
2178
  } from "@tinycloud/sdk-services";
2221
2179
 
2222
- // src/space.ts
2223
- async function fetchPeerId(host, spaceId) {
2224
- const res = await fetch(
2225
- `${host}/peer/generate/${encodeURIComponent(spaceId)}`
2226
- );
2227
- if (!res.ok) {
2228
- const error = await res.text().catch(() => res.statusText);
2229
- throw new Error(`Failed to get peer ID: ${res.status} - ${error}`);
2180
+ // src/manifest.ts
2181
+ import ms from "ms";
2182
+ import { resolveSecretPath, SECRET_NAME_RE } from "@tinycloud/sdk-services";
2183
+ var ManifestValidationError = class extends Error {
2184
+ constructor(message) {
2185
+ super(`Manifest validation failed: ${message}`);
2186
+ this.name = "ManifestValidationError";
2230
2187
  }
2231
- return res.text();
2188
+ };
2189
+ var DEFAULT_EXPIRY = "30d";
2190
+ var DEFAULT_DEFAULTS = true;
2191
+ var DEFAULT_MANIFEST_VERSION = 1;
2192
+ var DEFAULT_MANIFEST_SPACE = "applications";
2193
+ var ACCOUNT_REGISTRY_SPACE = "account";
2194
+ var ACCOUNT_REGISTRY_PATH = "applications/";
2195
+ var SECRETS_SPACE = "secrets";
2196
+ var VAULT_PERMISSION_SERVICE = "tinycloud.vault";
2197
+ var SERVICE_SHORT_TO_LONG = Object.freeze({
2198
+ kv: "tinycloud.kv",
2199
+ sql: "tinycloud.sql",
2200
+ duckdb: "tinycloud.duckdb",
2201
+ capabilities: "tinycloud.capabilities",
2202
+ hooks: "tinycloud.hooks",
2203
+ encryption: "tinycloud.encryption"
2204
+ });
2205
+ var ENCRYPTION_PERMISSION_SERVICE = "tinycloud.encryption";
2206
+ var ENCRYPTION_MANIFEST_SPACE = "encryption";
2207
+ var SERVICE_LONG_TO_SHORT = Object.freeze(
2208
+ Object.fromEntries(
2209
+ Object.entries(SERVICE_SHORT_TO_LONG).map(([s, l]) => [l, s])
2210
+ )
2211
+ );
2212
+ var DEFAULT_STANDARD_ENTRIES = [
2213
+ {
2214
+ service: "tinycloud.kv",
2215
+ space: DEFAULT_MANIFEST_SPACE,
2216
+ path: "/",
2217
+ actions: ["get", "put", "del", "list", "metadata"]
2218
+ },
2219
+ {
2220
+ service: "tinycloud.sql",
2221
+ space: DEFAULT_MANIFEST_SPACE,
2222
+ path: "/",
2223
+ actions: ["read", "write"]
2224
+ }
2225
+ ];
2226
+ var DEFAULT_ADMIN_ENTRIES = [
2227
+ {
2228
+ service: "tinycloud.kv",
2229
+ space: DEFAULT_MANIFEST_SPACE,
2230
+ path: "/",
2231
+ actions: ["get", "put", "del", "list", "metadata"]
2232
+ },
2233
+ {
2234
+ service: "tinycloud.sql",
2235
+ space: DEFAULT_MANIFEST_SPACE,
2236
+ path: "/",
2237
+ actions: ["read", "write", "ddl"]
2238
+ }
2239
+ ];
2240
+ var DEFAULT_ALL_ENTRIES = [
2241
+ {
2242
+ service: "tinycloud.kv",
2243
+ space: DEFAULT_MANIFEST_SPACE,
2244
+ path: "/",
2245
+ actions: ["get", "put", "del", "list", "metadata"]
2246
+ },
2247
+ {
2248
+ service: "tinycloud.sql",
2249
+ space: DEFAULT_MANIFEST_SPACE,
2250
+ path: "/",
2251
+ actions: ["read", "write", "ddl"]
2252
+ },
2253
+ {
2254
+ service: "tinycloud.duckdb",
2255
+ space: DEFAULT_MANIFEST_SPACE,
2256
+ path: "/",
2257
+ actions: ["read", "write"]
2258
+ }
2259
+ ];
2260
+ function parseExpiry(duration) {
2261
+ if (typeof duration !== "string" || duration.length === 0) {
2262
+ throw new ManifestValidationError(
2263
+ `expiry must be a non-empty duration string (got ${JSON.stringify(duration)})`
2264
+ );
2265
+ }
2266
+ const parsed = ms(duration);
2267
+ if (typeof parsed !== "number" || !Number.isFinite(parsed) || parsed <= 0) {
2268
+ throw new ManifestValidationError(
2269
+ `invalid expiry duration: ${JSON.stringify(duration)}`
2270
+ );
2271
+ }
2272
+ return parsed;
2232
2273
  }
2233
- async function submitHostDelegation(host, headers) {
2234
- const res = await fetch(`${host}/delegate`, {
2235
- method: "POST",
2236
- headers
2274
+ function expandActionShortNames(service, actions) {
2275
+ return actions.map((a) => {
2276
+ if (a.includes("/")) {
2277
+ return a;
2278
+ }
2279
+ return `${service}/${a}`;
2237
2280
  });
2238
- return {
2239
- success: res.ok,
2240
- status: res.status,
2241
- error: res.ok ? void 0 : await res.text().catch(() => res.statusText)
2242
- };
2243
2281
  }
2244
- async function activateSessionWithHost(host, delegationHeader) {
2245
- const res = await fetch(`${host}/delegate`, {
2246
- method: "POST",
2247
- headers: delegationHeader
2248
- });
2249
- if (res.ok) {
2250
- try {
2251
- const body = await res.json();
2252
- return {
2253
- success: true,
2254
- status: res.status,
2255
- activated: body.activated ?? [],
2256
- skipped: body.skipped ?? []
2257
- };
2258
- } catch {
2259
- return {
2260
- success: true,
2261
- status: res.status,
2262
- activated: [],
2263
- skipped: []
2264
- };
2265
- }
2282
+ function expandPermissionEntry(entry) {
2283
+ if (entry.service === ENCRYPTION_PERMISSION_SERVICE) {
2284
+ return expandEncryptionPermissionEntry(entry);
2266
2285
  }
2267
- return {
2268
- success: false,
2269
- status: res.status,
2270
- error: await res.text().catch(() => res.statusText)
2271
- };
2272
- }
2273
-
2274
- // src/delegations/DelegationManager.ts
2275
- var DelegationAction = {
2276
- CREATE: "tinycloud.delegation/create",
2277
- REVOKE: "tinycloud.delegation/revoke",
2278
- LIST: "tinycloud.delegation/list",
2279
- GET: "tinycloud.delegation/get",
2280
- CHECK: "tinycloud.delegation/check"
2281
- };
2282
- function createError(code, message, cause, meta) {
2283
- return {
2284
- code,
2285
- message,
2286
- service: "delegation",
2287
- cause,
2288
- meta
2289
- };
2286
+ if (entry.service !== VAULT_PERMISSION_SERVICE) {
2287
+ return [
2288
+ {
2289
+ ...entry,
2290
+ actions: expandActionShortNames(entry.service, entry.actions)
2291
+ }
2292
+ ];
2293
+ }
2294
+ return expandVaultPermissionEntry(entry);
2290
2295
  }
2291
- var DelegationManager = class {
2292
- /**
2293
- * Creates a new DelegationManager instance.
2294
- *
2295
- * @param config - Configuration including hosts, session, and invoke function
2296
- */
2297
- constructor(config) {
2298
- this.hosts = config.hosts;
2299
- this.session = config.session;
2300
- this.invoke = config.invoke;
2301
- this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
2296
+ function expandEncryptionPermissionEntry(entry) {
2297
+ if (typeof entry.path !== "string" || !entry.path.startsWith("urn:tinycloud:encryption:")) {
2298
+ throw new ManifestValidationError(
2299
+ `tinycloud.encryption entries require path to be a networkId URN (got ${JSON.stringify(entry.path)})`
2300
+ );
2302
2301
  }
2303
- /**
2304
- * Updates the session (e.g., after re-authentication).
2305
- *
2306
- * @param session - New session to use for operations
2307
- */
2308
- updateSession(session) {
2309
- this.session = session;
2310
- }
2311
- /**
2312
- * Gets the primary host URL.
2313
- */
2314
- get host() {
2315
- return this.hosts[0];
2316
- }
2317
- /**
2318
- * Executes an invoke operation against the delegation API.
2319
- */
2320
- async invokeOperation(path, action, body) {
2321
- const headers = this.invoke(this.session, "delegation", path, action);
2322
- return this.fetchFn(`${this.host}/invoke`, {
2323
- method: "POST",
2324
- headers,
2325
- body
2326
- });
2327
- }
2328
- /**
2329
- * Creates a new delegation.
2330
- *
2331
- * Delegates specific permissions to another DID for a given path.
2332
- * The delegatee can then use these permissions to access resources
2333
- * within the specified scope.
2334
- *
2335
- * @param params - Parameters for the delegation
2336
- * @returns Result containing the created Delegation or an error
2337
- *
2338
- * @example
2339
- * ```typescript
2340
- * const result = await manager.create({
2341
- * delegateDID: bob.did,
2342
- * path: "documents/shared/",
2343
- * actions: ["tinycloud.kv/get", "tinycloud.kv/put"],
2344
- * expiry: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
2345
- * });
2346
- * ```
2347
- */
2348
- async create(params) {
2349
- if (!params.delegateDID) {
2350
- return {
2351
- ok: false,
2352
- error: createError(
2353
- DelegationErrorCodes.INVALID_INPUT,
2354
- "delegateDID is required"
2355
- )
2356
- };
2302
+ const normalizedActions = [];
2303
+ for (const action of entry.actions) {
2304
+ if (action === "decrypt" || action === "tinycloud.encryption/decrypt") {
2305
+ normalizedActions.push("tinycloud.encryption/decrypt");
2306
+ continue;
2357
2307
  }
2358
- if (!params.path) {
2359
- return {
2360
- ok: false,
2361
- error: createError(
2362
- DelegationErrorCodes.INVALID_INPUT,
2363
- "path is required"
2364
- )
2365
- };
2308
+ if (action === "network.create" || action === "tinycloud.encryption/network.create") {
2309
+ normalizedActions.push("tinycloud.encryption/network.create");
2310
+ continue;
2366
2311
  }
2367
- if (!params.actions || params.actions.length === 0) {
2368
- return {
2369
- ok: false,
2370
- error: createError(
2371
- DelegationErrorCodes.INVALID_INPUT,
2372
- "at least one action is required"
2373
- )
2374
- };
2312
+ if (action === "network.revoke" || action === "tinycloud.encryption/network.revoke") {
2313
+ normalizedActions.push("tinycloud.encryption/network.revoke");
2314
+ continue;
2375
2315
  }
2376
- try {
2377
- const body = JSON.stringify({
2378
- delegateDID: params.delegateDID,
2379
- path: params.path,
2380
- actions: params.actions,
2381
- expiry: params.expiry?.toISOString(),
2382
- disableSubDelegation: params.disableSubDelegation ?? false,
2383
- statement: params.statement
2384
- });
2385
- const response = await this.invokeOperation(
2386
- params.path,
2387
- DelegationAction.CREATE,
2388
- body
2316
+ if (action.includes("/")) {
2317
+ throw new ManifestValidationError(
2318
+ `unknown encryption action ${JSON.stringify(action)}; expected decrypt, network.create, or network.revoke`
2389
2319
  );
2390
- if (!response.ok) {
2391
- const errorText = await response.text();
2392
- return {
2393
- ok: false,
2394
- error: createError(
2395
- DelegationErrorCodes.CREATION_FAILED,
2396
- `Failed to create delegation: ${response.status} - ${errorText}`,
2397
- void 0,
2398
- { status: response.status, path: params.path }
2399
- )
2400
- };
2401
- }
2402
- const apiResponse = await response.json();
2403
- const delegation = {
2404
- cid: apiResponse.cid ?? "",
2405
- delegateDID: params.delegateDID,
2406
- spaceId: this.session.spaceId,
2407
- path: params.path,
2408
- actions: params.actions,
2409
- expiry: params.expiry ?? new Date(Date.now() + EXPIRY.SHARE_MS),
2410
- isRevoked: false,
2411
- allowSubDelegation: !(params.disableSubDelegation ?? false),
2412
- createdAt: /* @__PURE__ */ new Date()
2413
- };
2414
- return { ok: true, data: delegation };
2415
- } catch (error) {
2416
- if (error instanceof Error && error.name === "AbortError") {
2417
- return {
2418
- ok: false,
2419
- error: createError(
2420
- DelegationErrorCodes.ABORTED,
2421
- "Request aborted",
2422
- error
2423
- )
2424
- };
2425
- }
2426
- return {
2427
- ok: false,
2428
- error: createError(
2429
- DelegationErrorCodes.NETWORK_ERROR,
2430
- `Network error during delegation creation: ${String(error)}`,
2431
- error instanceof Error ? error : void 0
2432
- )
2433
- };
2434
2320
  }
2321
+ throw new ManifestValidationError(
2322
+ `unknown encryption action ${JSON.stringify(action)}; expected decrypt, network.create, or network.revoke`
2323
+ );
2435
2324
  }
2436
- /**
2437
- * Revokes an existing delegation.
2438
- *
2439
- * Once revoked, the delegation can no longer be used to access resources.
2440
- * This also invalidates any sub-delegations derived from this delegation.
2441
- *
2442
- * @param cid - The CID of the delegation to revoke
2443
- * @returns Result indicating success or an error
2444
- *
2445
- * @example
2446
- * ```typescript
2447
- * const result = await manager.revoke("bafy...");
2448
- * if (result.ok) {
2449
- * console.log("Delegation revoked successfully");
2450
- * }
2451
- * ```
2452
- */
2453
- async revoke(cid) {
2454
- if (!cid) {
2455
- return {
2456
- ok: false,
2457
- error: createError(
2458
- DelegationErrorCodes.INVALID_INPUT,
2459
- "cid is required"
2460
- )
2461
- };
2325
+ const dedupedActions = [];
2326
+ const seen = /* @__PURE__ */ new Set();
2327
+ for (const a of normalizedActions) {
2328
+ if (!seen.has(a)) {
2329
+ dedupedActions.push(a);
2330
+ seen.add(a);
2462
2331
  }
2463
- try {
2464
- const body = JSON.stringify({ cid });
2465
- const response = await this.invokeOperation(
2466
- cid,
2467
- DelegationAction.REVOKE,
2468
- body
2469
- );
2470
- if (!response.ok) {
2471
- const errorText = await response.text();
2472
- if (response.status === 404) {
2473
- return {
2474
- ok: false,
2475
- error: createError(
2476
- DelegationErrorCodes.NOT_FOUND,
2477
- `Delegation not found: ${cid}`
2478
- )
2479
- };
2480
- }
2481
- return {
2482
- ok: false,
2483
- error: createError(
2484
- DelegationErrorCodes.REVOCATION_FAILED,
2485
- `Failed to revoke delegation: ${response.status} - ${errorText}`,
2486
- void 0,
2487
- { status: response.status, cid }
2488
- )
2489
- };
2490
- }
2491
- return { ok: true, data: void 0 };
2492
- } catch (error) {
2493
- if (error instanceof Error && error.name === "AbortError") {
2494
- return {
2495
- ok: false,
2496
- error: createError(
2497
- DelegationErrorCodes.ABORTED,
2498
- "Request aborted",
2499
- error
2500
- )
2501
- };
2502
- }
2503
- return {
2504
- ok: false,
2505
- error: createError(
2506
- DelegationErrorCodes.NETWORK_ERROR,
2507
- `Network error during delegation revocation: ${String(error)}`,
2508
- error instanceof Error ? error : void 0
2509
- )
2510
- };
2332
+ }
2333
+ return [
2334
+ {
2335
+ service: ENCRYPTION_PERMISSION_SERVICE,
2336
+ space: ENCRYPTION_MANIFEST_SPACE,
2337
+ path: entry.path,
2338
+ actions: dedupedActions,
2339
+ skipPrefix: true,
2340
+ ...entry.expiry !== void 0 ? { expiry: entry.expiry } : {},
2341
+ ...entry.description !== void 0 ? { description: entry.description } : {}
2511
2342
  }
2343
+ ];
2344
+ }
2345
+ function expandPermissionEntries(entries) {
2346
+ return entries.flatMap(expandPermissionEntry);
2347
+ }
2348
+ function applyPrefix(prefix, path, skipPrefix) {
2349
+ if (skipPrefix) {
2350
+ return path;
2512
2351
  }
2513
- /**
2514
- * Lists all delegations for the current session's space.
2515
- *
2516
- * Returns both delegations created by the current user (as delegator)
2517
- * and delegations granted to the current user (as delegatee).
2518
- *
2519
- * @returns Result containing an array of Delegations or an error
2520
- *
2521
- * @example
2522
- * ```typescript
2523
- * const result = await manager.list();
2524
- * if (result.ok) {
2525
- * for (const delegation of result.data) {
2526
- * console.log(`${delegation.cid}: ${delegation.path} -> ${delegation.delegateDID}`);
2527
- * }
2528
- * }
2529
- * ```
2530
- */
2531
- async list() {
2532
- try {
2533
- const response = await this.invokeOperation("", DelegationAction.LIST);
2534
- if (!response.ok) {
2535
- const errorText = await response.text();
2536
- return {
2537
- ok: false,
2538
- error: createError(
2539
- DelegationErrorCodes.NETWORK_ERROR,
2540
- `Failed to list delegations: ${response.status} - ${errorText}`,
2541
- void 0,
2542
- { status: response.status }
2543
- )
2544
- };
2545
- }
2546
- const data = await response.json();
2547
- const delegations = data.map((item) => ({
2548
- cid: item.cid,
2549
- delegateDID: item.delegateDID,
2550
- delegatorDID: item.delegatorDID,
2551
- spaceId: item.spaceId,
2552
- path: item.path,
2553
- actions: item.actions,
2554
- expiry: new Date(item.expiry),
2555
- isRevoked: item.isRevoked,
2556
- createdAt: item.createdAt ? new Date(item.createdAt) : void 0,
2557
- parentCid: item.parentCid,
2558
- allowSubDelegation: item.allowSubDelegation
2559
- }));
2560
- return { ok: true, data: delegations };
2561
- } catch (error) {
2562
- if (error instanceof Error && error.name === "AbortError") {
2563
- return {
2564
- ok: false,
2565
- error: createError(
2566
- DelegationErrorCodes.ABORTED,
2567
- "Request aborted",
2568
- error
2569
- )
2570
- };
2571
- }
2572
- return {
2573
- ok: false,
2574
- error: createError(
2575
- DelegationErrorCodes.NETWORK_ERROR,
2576
- `Network error during delegation list: ${String(error)}`,
2577
- error instanceof Error ? error : void 0
2578
- )
2579
- };
2352
+ if (prefix === "") {
2353
+ return path;
2354
+ }
2355
+ if (path.startsWith("/")) {
2356
+ return `${prefix}${path}`;
2357
+ }
2358
+ return `${prefix}/${path}`;
2359
+ }
2360
+ async function loadManifest(url) {
2361
+ const fetchFn = globalThis.fetch;
2362
+ if (typeof fetchFn !== "function") {
2363
+ throw new ManifestValidationError(
2364
+ "loadManifest requires a global fetch; pass the manifest object directly on runtimes without fetch"
2365
+ );
2366
+ }
2367
+ const res = await fetchFn(url);
2368
+ if (!res.ok) {
2369
+ throw new ManifestValidationError(
2370
+ `failed to fetch manifest from ${url}: HTTP ${res.status}`
2371
+ );
2372
+ }
2373
+ const json = await res.json();
2374
+ return validateManifest(json);
2375
+ }
2376
+ function validateManifest(input) {
2377
+ if (input === null || typeof input !== "object") {
2378
+ throw new ManifestValidationError("manifest must be an object");
2379
+ }
2380
+ const m = input;
2381
+ if (m.manifest_version !== void 0 && m.manifest_version !== DEFAULT_MANIFEST_VERSION) {
2382
+ throw new ManifestValidationError(
2383
+ `manifest.manifest_version must be ${DEFAULT_MANIFEST_VERSION}`
2384
+ );
2385
+ }
2386
+ if (typeof m.app_id !== "string" || m.app_id.length === 0) {
2387
+ throw new ManifestValidationError(
2388
+ "manifest.app_id is required and must be a non-empty string"
2389
+ );
2390
+ }
2391
+ if (typeof m.name !== "string" || m.name.length === 0) {
2392
+ throw new ManifestValidationError(
2393
+ "manifest.name is required and must be a non-empty string"
2394
+ );
2395
+ }
2396
+ if (m.did !== void 0 && (typeof m.did !== "string" || m.did.length === 0)) {
2397
+ throw new ManifestValidationError(
2398
+ "manifest.did must be a non-empty DID string"
2399
+ );
2400
+ }
2401
+ if (m.space !== void 0 && (typeof m.space !== "string" || m.space.length === 0)) {
2402
+ throw new ManifestValidationError(
2403
+ "manifest.space must be a non-empty string"
2404
+ );
2405
+ }
2406
+ if (m.expiry !== void 0) {
2407
+ parseExpiry(m.expiry);
2408
+ }
2409
+ if (m.permissions !== void 0) {
2410
+ if (!Array.isArray(m.permissions)) {
2411
+ throw new ManifestValidationError(
2412
+ "manifest.permissions must be an array"
2413
+ );
2580
2414
  }
2415
+ m.permissions.forEach(
2416
+ (p, i) => validatePermissionEntry(p, `permissions[${i}]`)
2417
+ );
2581
2418
  }
2582
- /**
2583
- * Gets the full delegation chain for a given delegation.
2584
- *
2585
- * Returns the chain of delegations from the root (original delegator)
2586
- * to the specified delegation, including all intermediate sub-delegations.
2587
- *
2588
- * @param cid - The CID of the delegation to get the chain for
2589
- * @returns Result containing the DelegationChain or an error
2590
- *
2591
- * @example
2592
- * ```typescript
2593
- * const result = await manager.getChain("bafy...");
2594
- * if (result.ok) {
2595
- * console.log("Chain length:", result.data.length);
2596
- * for (const delegation of result.data) {
2597
- * console.log(`- ${delegation.delegatorDID} -> ${delegation.delegateDID}`);
2598
- * }
2599
- * }
2600
- * ```
2601
- */
2602
- async getChain(cid) {
2603
- if (!cid) {
2604
- return {
2605
- ok: false,
2606
- error: createError(
2607
- DelegationErrorCodes.INVALID_INPUT,
2608
- "cid is required"
2609
- )
2610
- };
2419
+ if (m.secrets !== void 0) {
2420
+ validateManifestSecrets(m.secrets);
2421
+ }
2422
+ return m;
2423
+ }
2424
+ function validateManifestSecrets(secrets) {
2425
+ if (secrets === null || typeof secrets !== "object" || Array.isArray(secrets)) {
2426
+ throw new ManifestValidationError("manifest.secrets must be an object");
2427
+ }
2428
+ for (const [name, spec] of Object.entries(secrets)) {
2429
+ if (!SECRET_NAME_RE.test(name)) {
2430
+ throw new ManifestValidationError(
2431
+ `manifest.secrets.${name} must match ${SECRET_NAME_RE.source}`
2432
+ );
2611
2433
  }
2612
2434
  try {
2613
- const body = JSON.stringify({ cid, includeChain: true });
2614
- const response = await this.invokeOperation(
2615
- cid,
2616
- DelegationAction.GET,
2617
- body
2435
+ resolveSecretPath(
2436
+ secretNameFromSpec(name, spec),
2437
+ { scope: secretScopeFromSpec(spec) }
2618
2438
  );
2619
- if (!response.ok) {
2620
- const errorText = await response.text();
2621
- if (response.status === 404) {
2622
- return {
2623
- ok: false,
2624
- error: createError(
2625
- DelegationErrorCodes.NOT_FOUND,
2626
- `Delegation not found: ${cid}`
2627
- )
2628
- };
2629
- }
2630
- return {
2631
- ok: false,
2632
- error: createError(
2633
- DelegationErrorCodes.NETWORK_ERROR,
2634
- `Failed to get delegation chain: ${response.status} - ${errorText}`,
2635
- void 0,
2636
- { status: response.status, cid }
2637
- )
2638
- };
2639
- }
2640
- const data = await response.json();
2641
- const chain = data.chain.map((item) => ({
2642
- cid: item.cid,
2643
- delegateDID: item.delegateDID,
2644
- delegatorDID: item.delegatorDID,
2645
- spaceId: item.spaceId,
2646
- path: item.path,
2647
- actions: item.actions,
2648
- expiry: new Date(item.expiry),
2649
- isRevoked: item.isRevoked,
2650
- createdAt: item.createdAt ? new Date(item.createdAt) : void 0,
2651
- parentCid: item.parentCid,
2652
- allowSubDelegation: item.allowSubDelegation
2653
- }));
2654
- return { ok: true, data: chain };
2655
2439
  } catch (error) {
2656
- if (error instanceof Error && error.name === "AbortError") {
2657
- return {
2658
- ok: false,
2659
- error: createError(
2660
- DelegationErrorCodes.ABORTED,
2661
- "Request aborted",
2662
- error
2663
- )
2664
- };
2440
+ throw new ManifestValidationError(
2441
+ `manifest.secrets.${name}: ${error instanceof Error ? error.message : String(error)}`
2442
+ );
2443
+ }
2444
+ const actions = secretActionsFromSpec(name, spec);
2445
+ if (actions.length === 0) {
2446
+ throw new ManifestValidationError(
2447
+ `manifest.secrets.${name} actions must be non-empty`
2448
+ );
2449
+ }
2450
+ for (const action of actions) {
2451
+ if (typeof action !== "string" || action.length === 0) {
2452
+ throw new ManifestValidationError(
2453
+ `manifest.secrets.${name} actions must be non-empty strings`
2454
+ );
2665
2455
  }
2666
- return {
2667
- ok: false,
2668
- error: createError(
2669
- DelegationErrorCodes.NETWORK_ERROR,
2670
- `Network error during chain retrieval: ${String(error)}`,
2671
- error instanceof Error ? error : void 0
2672
- )
2673
- };
2456
+ }
2457
+ if (spec !== null && typeof spec === "object" && !Array.isArray(spec) && spec.expiry !== void 0) {
2458
+ parseExpiry(spec.expiry);
2674
2459
  }
2675
2460
  }
2676
- /**
2677
- * Checks if the current session has permission for a given path and action.
2678
- *
2679
- * This can be used to verify permissions before attempting an operation,
2680
- * or to implement custom access control logic.
2681
- *
2682
- * @param path - The resource path to check
2683
- * @param action - The action to check (e.g., "tinycloud.kv/get")
2684
- * @returns Result containing a boolean indicating permission or an error
2685
- *
2686
- * @example
2687
- * ```typescript
2688
- * const result = await manager.checkPermission("documents/private/", "tinycloud.kv/put");
2689
- * if (result.ok && result.data) {
2690
- * console.log("Permission granted");
2691
- * } else {
2692
- * console.log("Permission denied");
2693
- * }
2694
- * ```
2695
- */
2696
- async checkPermission(path, action) {
2697
- if (!path) {
2698
- return {
2699
- ok: false,
2700
- error: createError(
2701
- DelegationErrorCodes.INVALID_INPUT,
2702
- "path is required"
2703
- )
2704
- };
2705
- }
2706
- if (!action) {
2707
- return {
2708
- ok: false,
2709
- error: createError(
2710
- DelegationErrorCodes.INVALID_INPUT,
2711
- "action is required"
2712
- )
2713
- };
2714
- }
2715
- try {
2716
- const body = JSON.stringify({ path, action });
2717
- const response = await this.invokeOperation(
2718
- path,
2719
- DelegationAction.CHECK,
2720
- body
2721
- );
2722
- if (!response.ok) {
2723
- if (response.status === 403) {
2724
- return { ok: true, data: false };
2725
- }
2726
- const errorText = await response.text();
2727
- return {
2728
- ok: false,
2729
- error: createError(
2730
- DelegationErrorCodes.NETWORK_ERROR,
2731
- `Failed to check permission: ${response.status} - ${errorText}`,
2732
- void 0,
2733
- { status: response.status, path, action }
2734
- )
2735
- };
2736
- }
2737
- const data = await response.json();
2738
- return { ok: true, data: data.allowed };
2739
- } catch (error) {
2740
- if (error instanceof Error && error.name === "AbortError") {
2741
- return {
2742
- ok: false,
2743
- error: createError(
2744
- DelegationErrorCodes.ABORTED,
2745
- "Request aborted",
2746
- error
2747
- )
2748
- };
2749
- }
2750
- return {
2751
- ok: false,
2752
- error: createError(
2753
- DelegationErrorCodes.NETWORK_ERROR,
2754
- `Network error during permission check: ${String(error)}`,
2755
- error instanceof Error ? error : void 0
2756
- )
2757
- };
2758
- }
2759
- }
2760
- };
2761
-
2762
- // src/delegations/SharingService.schema.ts
2763
- import { z as z5 } from "zod";
2764
- var EncodedShareDataSchema = z5.object({
2765
- /** Private key in JWK format (must include d parameter) */
2766
- key: JWKSchema.refine(
2767
- (jwk) => typeof jwk.d === "string" && jwk.d.length > 0,
2768
- { message: "JWK must include private key (d parameter)" }
2769
- ),
2770
- /** DID of the key */
2771
- keyDid: z5.string().min(1, "keyDid is required"),
2772
- /** The delegation granting access */
2773
- delegation: DelegationSchema,
2774
- /** Resource path this link grants access to */
2775
- path: z5.string().min(1, "path is required"),
2776
- /** TinyCloud host URL */
2777
- host: z5.string().url("host must be a valid URL"),
2778
- /** Space ID */
2779
- spaceId: z5.string().min(1, "spaceId is required"),
2780
- /** Schema version (must be 1) */
2781
- version: z5.literal(1)
2782
- });
2783
- var ReceiveOptionsSchema = z5.object({
2784
- /**
2785
- * Whether to automatically create a sub-delegation to the current session key.
2786
- * Default: true
2787
- */
2788
- autoSubdelegate: z5.boolean().optional(),
2789
- /**
2790
- * Whether to use the current session key for operations (requires autoSubdelegate).
2791
- * Default: true
2792
- */
2793
- useSessionKey: z5.boolean().optional(),
2794
- /**
2795
- * Ingestion options passed to CapabilityKeyRegistry.
2796
- */
2797
- ingestOptions: IngestOptionsSchema.optional()
2798
- });
2799
- var SharingServiceConfigSchema = z5.object({
2800
- /** TinyCloud host URLs */
2801
- hosts: z5.array(z5.string().url()).min(1, "At least one host URL is required"),
2802
- /**
2803
- * Active session for authentication.
2804
- * Required for generate(), optional for receive().
2805
- */
2806
- session: z5.unknown().refine(
2807
- (val) => val === void 0 || val !== null && typeof val === "object",
2808
- { message: "Expected a ServiceSession object or undefined" }
2809
- ).optional(),
2810
- /** Platform-specific invoke function */
2811
- invoke: z5.unknown().refine((val) => typeof val === "function", {
2812
- message: "Expected an invoke function"
2813
- }),
2814
- /** Optional custom fetch implementation */
2815
- fetch: z5.unknown().refine(
2816
- (val) => val === void 0 || typeof val === "function",
2817
- { message: "Expected a fetch function or undefined" }
2818
- ).optional(),
2819
- /** Key provider for cryptographic operations */
2820
- keyProvider: KeyProviderSchema,
2821
- /** Capability key registry for key/delegation management */
2822
- registry: z5.unknown().refine(
2823
- (val) => val !== null && typeof val === "object",
2824
- { message: "Expected an ICapabilityKeyRegistry object" }
2825
- ),
2826
- /**
2827
- * Delegation manager for creating delegations.
2828
- * Required for generate(), optional for receive().
2829
- */
2830
- delegationManager: z5.unknown().refine(
2831
- (val) => val === void 0 || val !== null && typeof val === "object",
2832
- { message: "Expected a DelegationManager object or undefined" }
2833
- ).optional(),
2834
- /** Factory for creating KV service instances */
2835
- createKVService: z5.unknown().refine(
2836
- (val) => typeof val === "function",
2837
- { message: "Expected a createKVService factory function" }
2838
- ),
2839
- /** Base URL for sharing links (e.g., "https://share.myapp.com") */
2840
- baseUrl: z5.string().optional(),
2841
- /**
2842
- * Custom delegation creation function.
2843
- */
2844
- createDelegation: z5.unknown().refine((val) => val === void 0 || typeof val === "function", {
2845
- message: "Expected a createDelegation function or undefined"
2846
- }).optional(),
2847
- /**
2848
- * WASM function for client-side delegation creation.
2849
- */
2850
- createDelegationWasm: z5.unknown().refine((val) => val === void 0 || typeof val === "function", {
2851
- message: "Expected a createDelegationWasm function or undefined"
2852
- }).optional(),
2853
- /**
2854
- * Path prefix for KV operations.
2855
- */
2856
- pathPrefix: z5.string().optional(),
2857
- /**
2858
- * Session expiry time.
2859
- */
2860
- sessionExpiry: z5.date().optional(),
2861
- /**
2862
- * Callback to create a DIRECT delegation from wallet to share key.
2863
- * This is the preferred method for long-lived share links because it
2864
- * bypasses the session delegation chain entirely.
2865
- */
2866
- onRootDelegationNeeded: z5.unknown().refine((val) => val === void 0 || typeof val === "function", {
2867
- message: "Expected an onRootDelegationNeeded function or undefined"
2868
- }).optional()
2869
- });
2870
- function validateEncodedShareData(data) {
2871
- const result = EncodedShareDataSchema.safeParse(data);
2872
- if (!result.success) {
2873
- return {
2874
- ok: false,
2875
- error: {
2876
- code: DelegationErrorCodes.VALIDATION_ERROR,
2877
- message: `Invalid share data: ${result.error.message}`,
2878
- service: "delegation",
2879
- meta: { issues: result.error.issues }
2880
- }
2881
- };
2882
- }
2883
- return { ok: true, data: result.data };
2884
2461
  }
2885
-
2886
- // src/manifest.ts
2887
- import ms from "ms";
2888
- import { resolveSecretPath, SECRET_NAME_RE } from "@tinycloud/sdk-services";
2889
- var ManifestValidationError = class extends Error {
2890
- constructor(message) {
2891
- super(`Manifest validation failed: ${message}`);
2892
- this.name = "ManifestValidationError";
2893
- }
2894
- };
2895
- var DEFAULT_EXPIRY = "30d";
2896
- var DEFAULT_DEFAULTS = true;
2897
- var DEFAULT_MANIFEST_VERSION = 1;
2898
- var DEFAULT_MANIFEST_SPACE = "applications";
2899
- var ACCOUNT_REGISTRY_SPACE = "account";
2900
- var ACCOUNT_REGISTRY_PATH = "applications/";
2901
- var SECRETS_SPACE = "secrets";
2902
- var VAULT_PERMISSION_SERVICE = "tinycloud.vault";
2903
- var SERVICE_SHORT_TO_LONG = Object.freeze({
2904
- kv: "tinycloud.kv",
2905
- sql: "tinycloud.sql",
2906
- duckdb: "tinycloud.duckdb",
2907
- capabilities: "tinycloud.capabilities",
2908
- hooks: "tinycloud.hooks",
2909
- encryption: "tinycloud.encryption"
2910
- });
2911
- var ENCRYPTION_PERMISSION_SERVICE = "tinycloud.encryption";
2912
- var ENCRYPTION_MANIFEST_SPACE = "encryption";
2913
- var SERVICE_LONG_TO_SHORT = Object.freeze(
2914
- Object.fromEntries(
2915
- Object.entries(SERVICE_SHORT_TO_LONG).map(([s, l]) => [l, s])
2916
- )
2917
- );
2918
- var DEFAULT_STANDARD_ENTRIES = [
2919
- {
2920
- service: "tinycloud.kv",
2921
- space: DEFAULT_MANIFEST_SPACE,
2922
- path: "/",
2923
- actions: ["get", "put", "del", "list", "metadata"]
2924
- },
2925
- {
2926
- service: "tinycloud.sql",
2927
- space: DEFAULT_MANIFEST_SPACE,
2928
- path: "/",
2929
- actions: ["read", "write"]
2462
+ function validatePermissionEntry(p, path) {
2463
+ if (p === null || typeof p !== "object") {
2464
+ throw new ManifestValidationError(`${path} must be an object`);
2930
2465
  }
2931
- ];
2932
- var DEFAULT_ADMIN_ENTRIES = [
2933
- {
2934
- service: "tinycloud.kv",
2935
- space: DEFAULT_MANIFEST_SPACE,
2936
- path: "/",
2937
- actions: ["get", "put", "del", "list", "metadata"]
2938
- },
2939
- {
2940
- service: "tinycloud.sql",
2941
- space: DEFAULT_MANIFEST_SPACE,
2942
- path: "/",
2943
- actions: ["read", "write", "ddl"]
2466
+ const entry = p;
2467
+ if (typeof entry.service !== "string" || entry.service.length === 0) {
2468
+ throw new ManifestValidationError(`${path}.service is required`);
2944
2469
  }
2945
- ];
2946
- var DEFAULT_ALL_ENTRIES = [
2947
- {
2948
- service: "tinycloud.kv",
2949
- space: DEFAULT_MANIFEST_SPACE,
2950
- path: "/",
2951
- actions: ["get", "put", "del", "list", "metadata"]
2952
- },
2953
- {
2954
- service: "tinycloud.sql",
2955
- space: DEFAULT_MANIFEST_SPACE,
2956
- path: "/",
2957
- actions: ["read", "write", "ddl"]
2958
- },
2959
- {
2960
- service: "tinycloud.duckdb",
2961
- space: DEFAULT_MANIFEST_SPACE,
2962
- path: "/",
2963
- actions: ["read", "write"]
2470
+ if (entry.space !== void 0 && (typeof entry.space !== "string" || entry.space.length === 0)) {
2471
+ throw new ManifestValidationError(
2472
+ `${path}.space must be a non-empty string`
2473
+ );
2964
2474
  }
2965
- ];
2966
- function parseExpiry(duration) {
2967
- if (typeof duration !== "string" || duration.length === 0) {
2475
+ if (typeof entry.path !== "string") {
2968
2476
  throw new ManifestValidationError(
2969
- `expiry must be a non-empty duration string (got ${JSON.stringify(duration)})`
2477
+ `${path}.path is required (use "" or "/" for root)`
2970
2478
  );
2971
2479
  }
2972
- const parsed = ms(duration);
2973
- if (typeof parsed !== "number" || !Number.isFinite(parsed) || parsed <= 0) {
2480
+ if (!Array.isArray(entry.actions) || entry.actions.length === 0) {
2974
2481
  throw new ManifestValidationError(
2975
- `invalid expiry duration: ${JSON.stringify(duration)}`
2482
+ `${path}.actions must be a non-empty array`
2976
2483
  );
2977
2484
  }
2978
- return parsed;
2979
- }
2980
- function expandActionShortNames(service, actions) {
2981
- return actions.map((a) => {
2982
- if (a.includes("/")) {
2983
- return a;
2485
+ for (const action of entry.actions) {
2486
+ if (typeof action !== "string" || action.length === 0) {
2487
+ throw new ManifestValidationError(
2488
+ `${path}.actions must contain non-empty strings`
2489
+ );
2984
2490
  }
2985
- return `${service}/${a}`;
2986
- });
2491
+ if (entry.service === VAULT_PERMISSION_SERVICE) {
2492
+ vaultActionExpansion(action);
2493
+ }
2494
+ }
2495
+ if (entry.expiry !== void 0) {
2496
+ parseExpiry(entry.expiry);
2497
+ }
2987
2498
  }
2988
- function expandPermissionEntry(entry) {
2989
- if (entry.service === ENCRYPTION_PERMISSION_SERVICE) {
2990
- return expandEncryptionPermissionEntry(entry);
2499
+ function normalizeDefaults(value) {
2500
+ if (value === void 0) {
2501
+ return DEFAULT_DEFAULTS;
2991
2502
  }
2992
- if (entry.service !== VAULT_PERMISSION_SERVICE) {
2993
- return [
2994
- {
2995
- ...entry,
2996
- actions: expandActionShortNames(entry.service, entry.actions)
2997
- }
2998
- ];
2503
+ if (typeof value === "boolean") {
2504
+ return value;
2999
2505
  }
3000
- return expandVaultPermissionEntry(entry);
2506
+ if (typeof value !== "string") {
2507
+ return true;
2508
+ }
2509
+ const normalized = value.trim().toLowerCase();
2510
+ if (normalized === "admin" || normalized === "all") {
2511
+ return normalized;
2512
+ }
2513
+ return true;
3001
2514
  }
3002
- function expandEncryptionPermissionEntry(entry) {
3003
- if (typeof entry.path !== "string" || !entry.path.startsWith("urn:tinycloud:encryption:")) {
3004
- throw new ManifestValidationError(
3005
- `tinycloud.encryption entries require path to be a networkId URN (got ${JSON.stringify(entry.path)})`
3006
- );
2515
+ function defaultEntriesForTier(tier) {
2516
+ if (tier === false) {
2517
+ return [];
3007
2518
  }
3008
- const normalizedActions = [];
3009
- for (const action of entry.actions) {
3010
- if (action === "decrypt" || action === "tinycloud.encryption/decrypt") {
3011
- normalizedActions.push("tinycloud.encryption/decrypt");
2519
+ const source = tier === "admin" ? DEFAULT_ADMIN_ENTRIES : tier === "all" ? DEFAULT_ALL_ENTRIES : DEFAULT_STANDARD_ENTRIES;
2520
+ return source.map((e) => ({
2521
+ service: e.service,
2522
+ space: e.space,
2523
+ path: e.path,
2524
+ actions: [...e.actions],
2525
+ ...e.skipPrefix !== void 0 ? { skipPrefix: e.skipPrefix } : {}
2526
+ }));
2527
+ }
2528
+ function resolveManifest(input) {
2529
+ const manifest = validateManifest(input);
2530
+ const prefix = manifest.prefix !== void 0 ? manifest.prefix : manifest.app_id;
2531
+ const space = manifest.space ?? DEFAULT_MANIFEST_SPACE;
2532
+ const expiryMs = parseExpiry(manifest.expiry ?? DEFAULT_EXPIRY);
2533
+ const includePublicSpace = manifest.includePublicSpace ?? true;
2534
+ const tier = normalizeDefaults(manifest.defaults);
2535
+ const defaultEntries = defaultEntriesForTier(tier);
2536
+ const explicitEntries = manifest.permissions ?? [];
2537
+ const secretEntries = secretEntriesForManifest(manifest.secrets);
2538
+ const allEntries = [
2539
+ ...defaultEntries,
2540
+ ...explicitEntries,
2541
+ ...secretEntries
2542
+ ];
2543
+ const resources = withCapabilitiesReadForSpaces(
2544
+ allEntries.flatMap((entry) => resolveEntry(entry, prefix, expiryMs, space))
2545
+ );
2546
+ const additionalDelegates = manifest.did === void 0 ? [] : [
2547
+ {
2548
+ did: manifest.did,
2549
+ name: manifest.name,
2550
+ expiryMs,
2551
+ permissions: resources.map(cloneResourceCapability)
2552
+ }
2553
+ ];
2554
+ return {
2555
+ app_id: manifest.app_id,
2556
+ ...manifest.did !== void 0 ? { did: manifest.did } : {},
2557
+ space,
2558
+ resources,
2559
+ expiryMs,
2560
+ includePublicSpace,
2561
+ additionalDelegates
2562
+ };
2563
+ }
2564
+ function normalizeSecretActions(actions) {
2565
+ const out = [];
2566
+ const seen = /* @__PURE__ */ new Set();
2567
+ const add = (action) => {
2568
+ if (!seen.has(action)) {
2569
+ out.push(action);
2570
+ seen.add(action);
2571
+ }
2572
+ };
2573
+ for (const action of actions) {
2574
+ if (action === "read") {
2575
+ add("get");
3012
2576
  continue;
3013
2577
  }
3014
- if (action === "network.create" || action === "tinycloud.encryption/network.create") {
3015
- normalizedActions.push("tinycloud.encryption/network.create");
2578
+ if (action === "write") {
2579
+ add("put");
3016
2580
  continue;
3017
2581
  }
3018
- if (action === "network.revoke" || action === "tinycloud.encryption/network.revoke") {
3019
- normalizedActions.push("tinycloud.encryption/network.revoke");
2582
+ if (action === "delete") {
2583
+ add("del");
3020
2584
  continue;
3021
2585
  }
3022
- if (action.includes("/")) {
3023
- throw new ManifestValidationError(
3024
- `unknown encryption action ${JSON.stringify(action)}; expected decrypt, network.create, or network.revoke`
3025
- );
2586
+ if (action === "get" || action === "put" || action === "del" || action === "list" || action === "metadata") {
2587
+ add(action);
2588
+ continue;
2589
+ }
2590
+ if (action === "tinycloud.kv/get" || action === "tinycloud.kv/put" || action === "tinycloud.kv/del" || action === "tinycloud.kv/list" || action === "tinycloud.kv/metadata") {
2591
+ add(action);
2592
+ continue;
3026
2593
  }
3027
2594
  throw new ManifestValidationError(
3028
- `unknown encryption action ${JSON.stringify(action)}; expected decrypt, network.create, or network.revoke`
2595
+ `unknown secret action ${JSON.stringify(action)}; expected read, write, delete, list, or metadata`
3029
2596
  );
3030
2597
  }
3031
- const dedupedActions = [];
3032
- const seen = /* @__PURE__ */ new Set();
3033
- for (const a of normalizedActions) {
3034
- if (!seen.has(a)) {
3035
- dedupedActions.push(a);
3036
- seen.add(a);
3037
- }
3038
- }
3039
- return [
3040
- {
3041
- service: ENCRYPTION_PERMISSION_SERVICE,
3042
- space: ENCRYPTION_MANIFEST_SPACE,
3043
- path: entry.path,
3044
- actions: dedupedActions,
3045
- skipPrefix: true,
3046
- ...entry.expiry !== void 0 ? { expiry: entry.expiry } : {},
3047
- ...entry.description !== void 0 ? { description: entry.description } : {}
3048
- }
3049
- ];
3050
- }
3051
- function expandPermissionEntries(entries) {
3052
- return entries.flatMap(expandPermissionEntry);
2598
+ return out;
3053
2599
  }
3054
- function applyPrefix(prefix, path, skipPrefix) {
3055
- if (skipPrefix) {
3056
- return path;
3057
- }
3058
- if (prefix === "") {
3059
- return path;
3060
- }
3061
- if (path.startsWith("/")) {
3062
- return `${prefix}${path}`;
2600
+ function secretNameFromSpec(fallbackName, spec) {
2601
+ if (spec !== null && typeof spec === "object" && !Array.isArray(spec)) {
2602
+ return spec.name ?? fallbackName;
3063
2603
  }
3064
- return `${prefix}/${path}`;
2604
+ return fallbackName;
3065
2605
  }
3066
- async function loadManifest(url) {
3067
- const fetchFn = globalThis.fetch;
3068
- if (typeof fetchFn !== "function") {
3069
- throw new ManifestValidationError(
3070
- "loadManifest requires a global fetch; pass the manifest object directly on runtimes without fetch"
3071
- );
3072
- }
3073
- const res = await fetchFn(url);
3074
- if (!res.ok) {
3075
- throw new ManifestValidationError(
3076
- `failed to fetch manifest from ${url}: HTTP ${res.status}`
3077
- );
2606
+ function secretScopeFromSpec(spec) {
2607
+ if (spec !== null && typeof spec === "object" && !Array.isArray(spec)) {
2608
+ return spec.scope;
3078
2609
  }
3079
- const json = await res.json();
3080
- return validateManifest(json);
2610
+ return void 0;
3081
2611
  }
3082
- function validateManifest(input) {
3083
- if (input === null || typeof input !== "object") {
3084
- throw new ManifestValidationError("manifest must be an object");
3085
- }
3086
- const m = input;
3087
- if (m.manifest_version !== void 0 && m.manifest_version !== DEFAULT_MANIFEST_VERSION) {
3088
- throw new ManifestValidationError(
3089
- `manifest.manifest_version must be ${DEFAULT_MANIFEST_VERSION}`
3090
- );
3091
- }
3092
- if (typeof m.app_id !== "string" || m.app_id.length === 0) {
3093
- throw new ManifestValidationError(
3094
- "manifest.app_id is required and must be a non-empty string"
3095
- );
2612
+ function secretActionsFromSpec(name, spec) {
2613
+ if (spec === true) {
2614
+ return ["read"];
3096
2615
  }
3097
- if (typeof m.name !== "string" || m.name.length === 0) {
3098
- throw new ManifestValidationError(
3099
- "manifest.name is required and must be a non-empty string"
3100
- );
2616
+ if (typeof spec === "string") {
2617
+ return [spec];
3101
2618
  }
3102
- if (m.did !== void 0 && (typeof m.did !== "string" || m.did.length === 0)) {
3103
- throw new ManifestValidationError(
3104
- "manifest.did must be a non-empty DID string"
3105
- );
2619
+ if (Array.isArray(spec)) {
2620
+ return spec;
3106
2621
  }
3107
- if (m.space !== void 0 && (typeof m.space !== "string" || m.space.length === 0)) {
2622
+ if (spec === null || typeof spec !== "object") {
3108
2623
  throw new ManifestValidationError(
3109
- "manifest.space must be a non-empty string"
2624
+ `manifest.secrets.${name} must be true, a string action, an actions array, or an object`
3110
2625
  );
3111
2626
  }
3112
- if (m.expiry !== void 0) {
3113
- parseExpiry(m.expiry);
2627
+ if (spec.actions === void 0) {
2628
+ return ["read"];
3114
2629
  }
3115
- if (m.permissions !== void 0) {
3116
- if (!Array.isArray(m.permissions)) {
3117
- throw new ManifestValidationError(
3118
- "manifest.permissions must be an array"
3119
- );
3120
- }
3121
- m.permissions.forEach(
3122
- (p, i) => validatePermissionEntry(p, `permissions[${i}]`)
3123
- );
2630
+ if (typeof spec.actions === "string") {
2631
+ return [spec.actions];
3124
2632
  }
3125
- if (m.secrets !== void 0) {
3126
- validateManifestSecrets(m.secrets);
2633
+ if (Array.isArray(spec.actions)) {
2634
+ return spec.actions;
3127
2635
  }
3128
- return m;
2636
+ throw new ManifestValidationError(
2637
+ `manifest.secrets.${name}.actions must be a string or array`
2638
+ );
3129
2639
  }
3130
- function validateManifestSecrets(secrets) {
3131
- if (secrets === null || typeof secrets !== "object" || Array.isArray(secrets)) {
3132
- throw new ManifestValidationError("manifest.secrets must be an object");
2640
+ function secretEntriesForManifest(secrets) {
2641
+ if (secrets === void 0) {
2642
+ return [];
3133
2643
  }
2644
+ const entries = [];
3134
2645
  for (const [name, spec] of Object.entries(secrets)) {
3135
- if (!SECRET_NAME_RE.test(name)) {
3136
- throw new ManifestValidationError(
3137
- `manifest.secrets.${name} must match ${SECRET_NAME_RE.source}`
3138
- );
3139
- }
3140
- try {
3141
- resolveSecretPath(
3142
- secretNameFromSpec(name, spec),
3143
- { scope: secretScopeFromSpec(spec) }
3144
- );
3145
- } catch (error) {
3146
- throw new ManifestValidationError(
3147
- `manifest.secrets.${name}: ${error instanceof Error ? error.message : String(error)}`
3148
- );
3149
- }
3150
2646
  const actions = secretActionsFromSpec(name, spec);
3151
- if (actions.length === 0) {
3152
- throw new ManifestValidationError(
3153
- `manifest.secrets.${name} actions must be non-empty`
3154
- );
3155
- }
3156
- for (const action of actions) {
3157
- if (typeof action !== "string" || action.length === 0) {
3158
- throw new ManifestValidationError(
3159
- `manifest.secrets.${name} actions must be non-empty strings`
3160
- );
2647
+ const secretPath = resolveSecretPath(
2648
+ secretNameFromSpec(name, spec),
2649
+ { scope: secretScopeFromSpec(spec) }
2650
+ );
2651
+ const extra = spec !== true && typeof spec === "object" && !Array.isArray(spec) ? spec : {};
2652
+ entries.push({
2653
+ service: VAULT_PERMISSION_SERVICE,
2654
+ space: SECRETS_SPACE,
2655
+ path: secretPath.vaultKey,
2656
+ actions: normalizeSecretActions(actions),
2657
+ skipPrefix: true,
2658
+ ...extra.expiry !== void 0 ? { expiry: extra.expiry } : {},
2659
+ ...extra.description !== void 0 ? { description: extra.description } : {}
2660
+ });
2661
+ }
2662
+ return entries;
2663
+ }
2664
+ function resolveEntry(entry, prefix, _inheritedExpiryMs, inheritedSpace) {
2665
+ const skipPrefixForEntry = entry.skipPrefix === true || entry.service === ENCRYPTION_PERMISSION_SERVICE;
2666
+ const resolvedPath = applyPrefix(prefix, entry.path, skipPrefixForEntry);
2667
+ const entryExpiryMs = entry.expiry !== void 0 ? parseExpiry(entry.expiry) : void 0;
2668
+ return expandPermissionEntry({
2669
+ ...entry,
2670
+ space: entry.space ?? inheritedSpace,
2671
+ path: resolvedPath,
2672
+ skipPrefix: true
2673
+ }).map((expanded) => ({
2674
+ service: expanded.service,
2675
+ space: expanded.space ?? inheritedSpace,
2676
+ path: expanded.path,
2677
+ actions: expanded.actions,
2678
+ // Only populate `expiryMs` when the entry had its own expiry override.
2679
+ // When absent, callers use the parent (delegation or manifest) expiry
2680
+ // which is carried on ResolvedDelegate.expiryMs / ResolvedCapabilities.expiryMs.
2681
+ ...entryExpiryMs !== void 0 ? { expiryMs: entryExpiryMs } : {},
2682
+ ...entry.description !== void 0 ? { description: entry.description } : {}
2683
+ }));
2684
+ }
2685
+ function expandVaultPermissionEntry(entry) {
2686
+ const byBase = /* @__PURE__ */ new Map();
2687
+ for (const action of entry.actions) {
2688
+ const expansion = vaultActionExpansion(action);
2689
+ for (const base of expansion.bases) {
2690
+ const actions = byBase.get(base) ?? [];
2691
+ if (!actions.includes(expansion.action)) {
2692
+ actions.push(expansion.action);
3161
2693
  }
3162
- }
3163
- if (spec !== null && typeof spec === "object" && !Array.isArray(spec) && spec.expiry !== void 0) {
3164
- parseExpiry(spec.expiry);
2694
+ byBase.set(base, actions);
3165
2695
  }
3166
2696
  }
2697
+ return [...byBase.entries()].map(([base, actions]) => ({
2698
+ ...entry,
2699
+ service: "tinycloud.kv",
2700
+ path: vaultKVPath(base, entry.path),
2701
+ actions,
2702
+ skipPrefix: true
2703
+ }));
3167
2704
  }
3168
- function validatePermissionEntry(p, path) {
3169
- if (p === null || typeof p !== "object") {
3170
- throw new ManifestValidationError(`${path} must be an object`);
3171
- }
3172
- const entry = p;
3173
- if (typeof entry.service !== "string" || entry.service.length === 0) {
3174
- throw new ManifestValidationError(`${path}.service is required`);
2705
+ function vaultActionExpansion(action) {
2706
+ const normalized = normalizeVaultAction(action);
2707
+ if (normalized === "read" || normalized === "get") {
2708
+ return { bases: ["vault"], action: "tinycloud.kv/get" };
3175
2709
  }
3176
- if (entry.space !== void 0 && (typeof entry.space !== "string" || entry.space.length === 0)) {
3177
- throw new ManifestValidationError(
3178
- `${path}.space must be a non-empty string`
3179
- );
2710
+ if (normalized === "write" || normalized === "put") {
2711
+ return { bases: ["vault"], action: "tinycloud.kv/put" };
3180
2712
  }
3181
- if (typeof entry.path !== "string") {
3182
- throw new ManifestValidationError(
3183
- `${path}.path is required (use "" or "/" for root)`
3184
- );
2713
+ if (normalized === "delete" || normalized === "del") {
2714
+ return { bases: ["vault"], action: "tinycloud.kv/del" };
3185
2715
  }
3186
- if (!Array.isArray(entry.actions) || entry.actions.length === 0) {
3187
- throw new ManifestValidationError(
3188
- `${path}.actions must be a non-empty array`
3189
- );
2716
+ if (normalized === "list") {
2717
+ return { bases: ["vault"], action: "tinycloud.kv/list" };
3190
2718
  }
3191
- for (const action of entry.actions) {
3192
- if (typeof action !== "string" || action.length === 0) {
3193
- throw new ManifestValidationError(
3194
- `${path}.actions must contain non-empty strings`
3195
- );
3196
- }
3197
- if (entry.service === VAULT_PERMISSION_SERVICE) {
3198
- vaultActionExpansion(action);
3199
- }
2719
+ if (normalized === "head") {
2720
+ return { bases: ["vault"], action: "tinycloud.kv/get" };
3200
2721
  }
3201
- if (entry.expiry !== void 0) {
3202
- parseExpiry(entry.expiry);
2722
+ if (normalized === "metadata") {
2723
+ return { bases: ["vault"], action: "tinycloud.kv/metadata" };
3203
2724
  }
2725
+ throw new ManifestValidationError(
2726
+ `unknown vault action ${JSON.stringify(action)}; expected read, write, delete, get, put, del, list, head, or metadata`
2727
+ );
3204
2728
  }
3205
- function normalizeDefaults(value) {
3206
- if (value === void 0) {
3207
- return DEFAULT_DEFAULTS;
2729
+ function normalizeVaultAction(action) {
2730
+ if (action.startsWith(`${VAULT_PERMISSION_SERVICE}/`)) {
2731
+ return action.slice(`${VAULT_PERMISSION_SERVICE}/`.length);
3208
2732
  }
3209
- if (typeof value === "boolean") {
3210
- return value;
2733
+ if (action.startsWith("tinycloud.kv/")) {
2734
+ return action.slice("tinycloud.kv/".length);
3211
2735
  }
3212
- if (typeof value !== "string") {
3213
- return true;
2736
+ if (action.includes("/")) {
2737
+ throw new ManifestValidationError(
2738
+ `unknown vault action ${JSON.stringify(action)}; expected a tinycloud.vault or tinycloud.kv action`
2739
+ );
3214
2740
  }
3215
- const normalized = value.trim().toLowerCase();
3216
- if (normalized === "admin" || normalized === "all") {
3217
- return normalized;
2741
+ return action;
2742
+ }
2743
+ function vaultKVPath(base, path) {
2744
+ const normalized = path.startsWith("/") ? path.slice(1) : path;
2745
+ return `${base}/${normalized}`;
2746
+ }
2747
+ function cloneResourceCapability(entry) {
2748
+ return {
2749
+ service: entry.service,
2750
+ space: entry.space,
2751
+ path: entry.path,
2752
+ actions: [...entry.actions],
2753
+ ...entry.expiryMs !== void 0 ? { expiryMs: entry.expiryMs } : {},
2754
+ ...entry.description !== void 0 ? { description: entry.description } : {}
2755
+ };
2756
+ }
2757
+ function clonePermissionEntry(entry) {
2758
+ return {
2759
+ service: entry.service,
2760
+ ...entry.space !== void 0 ? { space: entry.space } : {},
2761
+ path: entry.path,
2762
+ actions: [...entry.actions],
2763
+ ...entry.skipPrefix !== void 0 ? { skipPrefix: entry.skipPrefix } : {},
2764
+ ...entry.expiry !== void 0 ? { expiry: entry.expiry } : {},
2765
+ ...entry.description !== void 0 ? { description: entry.description } : {}
2766
+ };
2767
+ }
2768
+ function dedupeResources(resources) {
2769
+ const byKey = /* @__PURE__ */ new Map();
2770
+ for (const resource of resources) {
2771
+ const key = `${resource.service}\0${resource.space}\0${resource.path}\0${resource.expiryMs ?? ""}`;
2772
+ const existing = byKey.get(key);
2773
+ if (existing === void 0) {
2774
+ byKey.set(key, cloneResourceCapability(resource));
2775
+ continue;
2776
+ }
2777
+ const seen = new Set(existing.actions);
2778
+ for (const action of resource.actions) {
2779
+ if (!seen.has(action)) {
2780
+ existing.actions.push(action);
2781
+ seen.add(action);
2782
+ }
2783
+ }
2784
+ if (existing.description === void 0 && resource.description !== void 0) {
2785
+ existing.description = resource.description;
2786
+ }
3218
2787
  }
3219
- return true;
2788
+ return [...byKey.values()];
3220
2789
  }
3221
- function defaultEntriesForTier(tier) {
3222
- if (tier === false) {
2790
+ function capabilitiesReadPermission(space) {
2791
+ return {
2792
+ service: "tinycloud.capabilities",
2793
+ space,
2794
+ path: "",
2795
+ actions: ["tinycloud.capabilities/read"]
2796
+ };
2797
+ }
2798
+ function withCapabilitiesReadForSpaces(resources) {
2799
+ if (resources.length === 0) {
3223
2800
  return [];
3224
2801
  }
3225
- const source = tier === "admin" ? DEFAULT_ADMIN_ENTRIES : tier === "all" ? DEFAULT_ALL_ENTRIES : DEFAULT_STANDARD_ENTRIES;
3226
- return source.map((e) => ({
3227
- service: e.service,
3228
- space: e.space,
3229
- path: e.path,
3230
- actions: [...e.actions],
3231
- ...e.skipPrefix !== void 0 ? { skipPrefix: e.skipPrefix } : {}
2802
+ const spaces = new Set(
2803
+ resources.filter((resource) => resource.service !== ENCRYPTION_PERMISSION_SERVICE).map((resource) => resource.space)
2804
+ );
2805
+ return dedupeResources([
2806
+ ...resources,
2807
+ ...[...spaces].map(capabilitiesReadPermission)
2808
+ ]);
2809
+ }
2810
+ function accountRegistryPermissions() {
2811
+ return [ACCOUNT_REGISTRY_PATH, "spaces/"].map((path) => ({
2812
+ service: "tinycloud.kv",
2813
+ space: ACCOUNT_REGISTRY_SPACE,
2814
+ path,
2815
+ actions: ["tinycloud.kv/get", "tinycloud.kv/put", "tinycloud.kv/list"]
3232
2816
  }));
3233
2817
  }
3234
- function resolveManifest(input) {
3235
- const manifest = validateManifest(input);
3236
- const prefix = manifest.prefix !== void 0 ? manifest.prefix : manifest.app_id;
3237
- const space = manifest.space ?? DEFAULT_MANIFEST_SPACE;
3238
- const expiryMs = parseExpiry(manifest.expiry ?? DEFAULT_EXPIRY);
3239
- const includePublicSpace = manifest.includePublicSpace ?? true;
3240
- const tier = normalizeDefaults(manifest.defaults);
3241
- const defaultEntries = defaultEntriesForTier(tier);
3242
- const explicitEntries = manifest.permissions ?? [];
3243
- const secretEntries = secretEntriesForManifest(manifest.secrets);
3244
- const allEntries = [
3245
- ...defaultEntries,
3246
- ...explicitEntries,
3247
- ...secretEntries
3248
- ];
3249
- const resources = withCapabilitiesReadForSpaces(
3250
- allEntries.flatMap((entry) => resolveEntry(entry, prefix, expiryMs, space))
3251
- );
3252
- const additionalDelegates = manifest.did === void 0 ? [] : [
3253
- {
3254
- did: manifest.did,
3255
- name: manifest.name,
3256
- expiryMs,
3257
- permissions: resources.map(cloneResourceCapability)
3258
- }
3259
- ];
2818
+ function accountRegistryIndexPermission() {
3260
2819
  return {
3261
- app_id: manifest.app_id,
3262
- ...manifest.did !== void 0 ? { did: manifest.did } : {},
3263
- space,
3264
- resources,
3265
- expiryMs,
3266
- includePublicSpace,
3267
- additionalDelegates
2820
+ service: "tinycloud.sql",
2821
+ space: ACCOUNT_REGISTRY_SPACE,
2822
+ path: "account",
2823
+ actions: ["tinycloud.sql/read", "tinycloud.sql/write", "tinycloud.sql/ddl"]
3268
2824
  };
3269
2825
  }
3270
- function normalizeSecretActions(actions) {
3271
- const out = [];
3272
- const seen = /* @__PURE__ */ new Set();
3273
- const add = (action) => {
3274
- if (!seen.has(action)) {
3275
- out.push(action);
3276
- seen.add(action);
2826
+ function composeManifestRequest(inputs, options = {}) {
2827
+ if (!Array.isArray(inputs) || inputs.length === 0) {
2828
+ throw new ManifestValidationError(
2829
+ "composeManifestRequest requires at least one manifest"
2830
+ );
2831
+ }
2832
+ const includeAccountRegistryPermissions = options.includeAccountRegistryPermissions ?? true;
2833
+ const manifests = inputs.map(validateManifest);
2834
+ const resolved = manifests.map(resolveManifest);
2835
+ const resources = resolved.flatMap((entry) => entry.resources);
2836
+ const delegationTargets = resolved.flatMap(
2837
+ (entry) => entry.additionalDelegates.map((delegate) => ({
2838
+ ...delegate,
2839
+ permissions: dedupeResources(delegate.permissions)
2840
+ }))
2841
+ );
2842
+ if (includeAccountRegistryPermissions) {
2843
+ resources.push(...accountRegistryPermissions());
2844
+ resources.push(accountRegistryIndexPermission());
2845
+ }
2846
+ const resourcesWithImplicitCapabilities = withCapabilitiesReadForSpaces(resources);
2847
+ const manifestsByAppId = /* @__PURE__ */ new Map();
2848
+ for (const manifest of manifests) {
2849
+ const current = manifestsByAppId.get(manifest.app_id);
2850
+ if (current === void 0) {
2851
+ manifestsByAppId.set(manifest.app_id, [manifest]);
2852
+ } else {
2853
+ current.push(manifest);
3277
2854
  }
2855
+ }
2856
+ const registryRecords = includeAccountRegistryPermissions ? [...manifestsByAppId.entries()].map(([app_id, appManifests]) => ({
2857
+ key: `${ACCOUNT_REGISTRY_PATH}${app_id}`,
2858
+ app_id,
2859
+ manifests: appManifests.map((manifest) => ({
2860
+ ...manifest,
2861
+ permissions: manifest.permissions?.map(clonePermissionEntry)
2862
+ }))
2863
+ })) : [];
2864
+ return {
2865
+ manifests,
2866
+ resources: resourcesWithImplicitCapabilities,
2867
+ delegationTargets,
2868
+ registryRecords,
2869
+ expiryMs: Math.max(...resolved.map((entry) => entry.expiryMs)),
2870
+ includePublicSpace: resolved.some((entry) => entry.includePublicSpace)
3278
2871
  };
3279
- for (const action of actions) {
3280
- if (action === "read") {
3281
- add("get");
3282
- continue;
3283
- }
3284
- if (action === "write") {
3285
- add("put");
3286
- continue;
3287
- }
3288
- if (action === "delete") {
3289
- add("del");
3290
- continue;
2872
+ }
2873
+ function resourceCapabilitiesToAbilitiesMap(resources) {
2874
+ const out = {};
2875
+ for (const r of resources) {
2876
+ const shortService = SERVICE_LONG_TO_SHORT[r.service];
2877
+ if (shortService === void 0) {
2878
+ throw new ManifestValidationError(
2879
+ `unknown service '${r.service}' \u2014 no short-form mapping. Known services: ${Object.keys(SERVICE_LONG_TO_SHORT).join(", ")}`
2880
+ );
3291
2881
  }
3292
- if (action === "get" || action === "put" || action === "del" || action === "list" || action === "metadata") {
3293
- add(action);
3294
- continue;
2882
+ if (out[shortService] === void 0) {
2883
+ out[shortService] = {};
3295
2884
  }
3296
- if (action === "tinycloud.kv/get" || action === "tinycloud.kv/put" || action === "tinycloud.kv/del" || action === "tinycloud.kv/list" || action === "tinycloud.kv/metadata") {
3297
- add(action);
3298
- continue;
2885
+ const pathsMap = out[shortService];
2886
+ const existing = pathsMap[r.path];
2887
+ if (existing === void 0) {
2888
+ pathsMap[r.path] = [...r.actions];
2889
+ } else {
2890
+ const seen = new Set(existing);
2891
+ for (const action of r.actions) {
2892
+ if (!seen.has(action)) {
2893
+ existing.push(action);
2894
+ seen.add(action);
2895
+ }
2896
+ }
3299
2897
  }
3300
- throw new ManifestValidationError(
3301
- `unknown secret action ${JSON.stringify(action)}; expected read, write, delete, list, or metadata`
3302
- );
3303
2898
  }
3304
2899
  return out;
3305
2900
  }
3306
- function secretNameFromSpec(fallbackName, spec) {
3307
- if (spec !== null && typeof spec === "object" && !Array.isArray(spec)) {
3308
- return spec.name ?? fallbackName;
2901
+ function resourceCapabilitiesToSpaceAbilitiesMap(resources) {
2902
+ const grouped = /* @__PURE__ */ new Map();
2903
+ for (const resource of resources) {
2904
+ const entries = grouped.get(resource.space);
2905
+ if (entries === void 0) {
2906
+ grouped.set(resource.space, [resource]);
2907
+ } else {
2908
+ entries.push(resource);
2909
+ }
3309
2910
  }
3310
- return fallbackName;
2911
+ const out = {};
2912
+ for (const [space, entries] of grouped.entries()) {
2913
+ out[space] = resourceCapabilitiesToAbilitiesMap(entries);
2914
+ }
2915
+ return out;
3311
2916
  }
3312
- function secretScopeFromSpec(spec) {
3313
- if (spec !== null && typeof spec === "object" && !Array.isArray(spec)) {
3314
- return spec.scope;
2917
+ function manifestAbilitiesUnion(resolved) {
2918
+ const all = [...resolved.resources];
2919
+ for (const delegate of resolved.additionalDelegates) {
2920
+ for (const perm of delegate.permissions) {
2921
+ all.push(perm);
2922
+ }
3315
2923
  }
3316
- return void 0;
2924
+ return resourceCapabilitiesToAbilitiesMap(all);
3317
2925
  }
3318
- function secretActionsFromSpec(name, spec) {
3319
- if (spec === true) {
3320
- return ["read"];
2926
+
2927
+ // src/account/AccountService.ts
2928
+ var SERVICE_NAME2 = "account";
2929
+ var ACCOUNT_INDEX_DB = "account";
2930
+ var ACCOUNT_INDEX_NAMESPACE = "tinycloud.account.index";
2931
+ var ACCOUNT_SPACES_PATH = "spaces/";
2932
+ var AccountService = class {
2933
+ constructor(config) {
2934
+ this.config = config;
2935
+ this.applications = {
2936
+ list: async (options = {}) => {
2937
+ if (options.preferIndex) {
2938
+ const indexed = await this.index.applications.list();
2939
+ if (indexed.ok && indexed.data.length > 0) return indexed;
2940
+ if (!indexed.ok && !isMissingIndexError(indexed.error)) return indexed;
2941
+ const canonical = await this.applications.list();
2942
+ if (canonical.ok && options.refreshIndex !== false) {
2943
+ await this.replaceApplicationsIndexQuietly(canonical.data);
2944
+ }
2945
+ return canonical;
2946
+ }
2947
+ const kvResult = this.accountKV();
2948
+ if (!kvResult.ok) return kvResult;
2949
+ const listed = await kvResult.data.list({ prefix: ACCOUNT_REGISTRY_PATH });
2950
+ if (!listed.ok) return accountErr(listed.error);
2951
+ const applications = [];
2952
+ for (const key of listed.data.keys) {
2953
+ const loaded = await kvResult.data.get(key);
2954
+ if (!loaded.ok) return accountErr(loaded.error);
2955
+ applications.push(applicationFromRecord(key, loaded.data.data));
2956
+ }
2957
+ applications.sort((a, b) => a.appId.localeCompare(b.appId));
2958
+ return ok3(applications);
2959
+ },
2960
+ get: async (appId) => {
2961
+ const kvResult = this.accountKV();
2962
+ if (!kvResult.ok) return kvResult;
2963
+ const key = applicationKey(appId);
2964
+ const loaded = await kvResult.data.get(key);
2965
+ if (!loaded.ok) return accountErr(loaded.error);
2966
+ return ok3(applicationFromRecord(key, loaded.data.data));
2967
+ },
2968
+ register: async (manifest) => {
2969
+ const manifests = Array.isArray(manifest) ? manifest : [manifest];
2970
+ const request = composeManifestRequest(manifests);
2971
+ if (request.registryRecords.length === 0) {
2972
+ return err3(
2973
+ serviceError3(
2974
+ "INVALID_MANIFEST",
2975
+ "Manifest did not produce an account application registry record",
2976
+ SERVICE_NAME2
2977
+ )
2978
+ );
2979
+ }
2980
+ await this.config.ensureAccountSpaceHosted?.();
2981
+ const kvResult = this.accountKV();
2982
+ if (!kvResult.ok) return kvResult;
2983
+ let registered;
2984
+ for (const record of request.registryRecords) {
2985
+ const manifestHash = hashJson(record.manifests);
2986
+ if (await this.indexHasApplicationHash(record.app_id, manifestHash)) {
2987
+ registered = {
2988
+ appId: record.app_id,
2989
+ manifests: record.manifests,
2990
+ manifestHash,
2991
+ name: record.manifests[0]?.name,
2992
+ description: record.manifests[0]?.description
2993
+ };
2994
+ continue;
2995
+ }
2996
+ const stored = {
2997
+ app_id: record.app_id,
2998
+ manifests: record.manifests,
2999
+ manifest_hash: manifestHash,
3000
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
3001
+ };
3002
+ const written = await kvResult.data.put(record.key, stored);
3003
+ if (!written.ok) return accountErr(written.error);
3004
+ registered = applicationFromRecord(record.key, stored);
3005
+ await this.upsertApplicationIndexQuietly(registered);
3006
+ }
3007
+ return ok3(registered);
3008
+ },
3009
+ remove: async (appId) => {
3010
+ const kvResult = this.accountKV();
3011
+ if (!kvResult.ok) return kvResult;
3012
+ const removed = await kvResult.data.delete(applicationKey(appId));
3013
+ if (!removed.ok) return accountErr(removed.error);
3014
+ await this.deleteApplicationIndexQuietly(appId);
3015
+ return ok3(void 0);
3016
+ }
3017
+ };
3018
+ this.spaces = {
3019
+ list: async (options = {}) => {
3020
+ if (options.preferIndex) {
3021
+ const indexed = await this.index.spaces.list();
3022
+ if (indexed.ok && indexed.data.length > 0) return indexed;
3023
+ if (!indexed.ok && !isMissingIndexError(indexed.error)) return indexed;
3024
+ const canonical = await this.spaces.syncAccessible();
3025
+ if (canonical.ok && options.refreshIndex !== false) {
3026
+ await this.replaceSpacesIndexQuietly(canonical.data);
3027
+ }
3028
+ return canonical;
3029
+ }
3030
+ const kvResult = this.accountKV();
3031
+ if (!kvResult.ok) return kvResult;
3032
+ const listed = await kvResult.data.list({ prefix: ACCOUNT_SPACES_PATH });
3033
+ if (!listed.ok) return accountErr(listed.error);
3034
+ const spaces = [];
3035
+ for (const key of listed.data.keys) {
3036
+ const loaded = await kvResult.data.get(key);
3037
+ if (!loaded.ok) return accountErr(loaded.error);
3038
+ spaces.push(spaceFromRecord(key, loaded.data.data));
3039
+ }
3040
+ spaces.sort((a, b) => a.name.localeCompare(b.name) || a.spaceId.localeCompare(b.spaceId));
3041
+ return ok3(spaces);
3042
+ },
3043
+ get: async (spaceId) => {
3044
+ const kvResult = this.accountKV();
3045
+ if (!kvResult.ok) return kvResult;
3046
+ const loaded = await kvResult.data.get(spaceKey(spaceId));
3047
+ if (!loaded.ok) return accountErr(loaded.error);
3048
+ return ok3(spaceFromRecord(spaceKey(spaceId), loaded.data.data));
3049
+ },
3050
+ register: async (space) => {
3051
+ await this.config.ensureAccountSpaceHosted?.();
3052
+ const kvResult = this.accountKV();
3053
+ if (!kvResult.ok) return kvResult;
3054
+ const stored = spaceRecordFromInput(space);
3055
+ const written = await kvResult.data.put(spaceKey(stored.space_id), stored);
3056
+ if (!written.ok) return accountErr(written.error);
3057
+ const registered = spaceFromRecord(spaceKey(stored.space_id), stored);
3058
+ await this.upsertSpaceIndexQuietly(registered);
3059
+ return ok3(registered);
3060
+ },
3061
+ syncAccessible: async () => {
3062
+ const listed = await this.config.getSpaces().list();
3063
+ if (!listed.ok) return accountErr(listed.error);
3064
+ const registered = [];
3065
+ for (const space of listed.data) {
3066
+ const result = await this.spaces.register(space);
3067
+ if (!result.ok) return result;
3068
+ registered.push(result.data);
3069
+ }
3070
+ return ok3(registered);
3071
+ },
3072
+ remove: async (spaceId) => {
3073
+ const kvResult = this.accountKV();
3074
+ if (!kvResult.ok) return kvResult;
3075
+ const removed = await kvResult.data.delete(spaceKey(spaceId));
3076
+ if (!removed.ok) return accountErr(removed.error);
3077
+ await this.deleteSpaceIndexQuietly(spaceId);
3078
+ return ok3(void 0);
3079
+ }
3080
+ };
3081
+ this.delegations = {
3082
+ list: async (options = {}) => {
3083
+ if (options.preferIndex) {
3084
+ const indexed = await this.index.delegations.list(options);
3085
+ if (indexed.ok && indexed.data.length > 0) return indexed;
3086
+ if (!indexed.ok && !isMissingIndexError(indexed.error)) return indexed;
3087
+ const live = await this.delegations.list({
3088
+ direction: options.direction,
3089
+ space: options.space
3090
+ });
3091
+ if (live.ok && options.refreshIndex !== false) {
3092
+ await this.replaceDelegationsIndexQuietly(live.data);
3093
+ }
3094
+ return live;
3095
+ }
3096
+ const spaces = await this.config.getSpaces().list();
3097
+ if (!spaces.ok) return accountErr(spaces.error);
3098
+ const targetSpaces = options.space ? spaces.data.filter((space) => space.id === options.space || space.name === options.space) : spaces.data;
3099
+ const delegations = [];
3100
+ for (const space of targetSpaces) {
3101
+ const scoped = this.config.getSpaces().get(space.id).delegations;
3102
+ if (options.direction !== "received") {
3103
+ const granted = await scoped.list();
3104
+ if (!granted.ok) return accountErr(granted.error);
3105
+ delegations.push(...granted.data.map((d) => mapDelegation(d, space, "granted")));
3106
+ }
3107
+ if (options.direction !== "granted") {
3108
+ const received = await scoped.listReceived();
3109
+ if (!received.ok) return accountErr(received.error);
3110
+ delegations.push(...received.data.map((d) => mapDelegation(d, space, "received")));
3111
+ }
3112
+ }
3113
+ delegations.sort((a, b) => a.spaceId.localeCompare(b.spaceId) || a.cid.localeCompare(b.cid));
3114
+ return ok3(delegations);
3115
+ },
3116
+ revoke: async (options) => {
3117
+ const space = await this.resolveSpace(options.space);
3118
+ if (!space.ok) return space;
3119
+ const revoked = await this.config.getSpaces().get(space.data.id).delegations.revoke(options.cid);
3120
+ if (!revoked.ok) return accountErr(revoked.error);
3121
+ return ok3(void 0);
3122
+ }
3123
+ };
3124
+ this.index = {
3125
+ rebuild: async () => {
3126
+ const dbResult = this.accountDb();
3127
+ if (!dbResult.ok) return dbResult;
3128
+ const applications = await this.applications.list();
3129
+ if (!applications.ok) return applications;
3130
+ const spaces = await this.spaces.list();
3131
+ if (!spaces.ok) return spaces;
3132
+ const delegations = await this.delegations.list();
3133
+ if (!delegations.ok) return delegations;
3134
+ const syncedAt = (/* @__PURE__ */ new Date()).toISOString();
3135
+ const schema = await this.ensureAccountIndex(dbResult.data);
3136
+ if (!schema.ok) return schema;
3137
+ const statements = [
3138
+ { sql: "DELETE FROM applications" },
3139
+ { sql: "DELETE FROM application_state" },
3140
+ { sql: "DELETE FROM spaces" },
3141
+ { sql: "DELETE FROM delegations" },
3142
+ { sql: "DELETE FROM sync_state" },
3143
+ ...applications.data.map((app) => ({
3144
+ sql: "INSERT INTO applications (app_id, name, description, updated_at, manifest_json) VALUES (?, ?, ?, ?, ?)",
3145
+ params: [
3146
+ app.appId,
3147
+ app.name ?? null,
3148
+ app.description ?? null,
3149
+ app.updatedAt ?? syncedAt,
3150
+ JSON.stringify(app.manifests)
3151
+ ]
3152
+ })),
3153
+ ...applications.data.map((app) => ({
3154
+ sql: "INSERT OR REPLACE INTO application_state (app_id, manifest_hash, indexed_at) VALUES (?, ?, ?)",
3155
+ params: [app.appId, app.manifestHash ?? hashJson(app.manifests), syncedAt]
3156
+ })),
3157
+ ...spaces.data.map((space) => ({
3158
+ sql: "INSERT OR REPLACE INTO spaces (space_id, name, owner_did, type, permissions_json, status, registered_at, updated_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
3159
+ params: [
3160
+ space.spaceId,
3161
+ space.name,
3162
+ space.ownerDid,
3163
+ space.type,
3164
+ JSON.stringify(space.permissions),
3165
+ space.status,
3166
+ space.registeredAt ?? syncedAt,
3167
+ space.updatedAt ?? syncedAt,
3168
+ space.expiresAt?.toISOString() ?? null
3169
+ ]
3170
+ })),
3171
+ ...delegations.data.map((delegation) => ({
3172
+ sql: "INSERT INTO delegations (cid, direction, space_id, space_name, counterparty_did, delegate_did, delegator_did, path, actions_json, expiry, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
3173
+ params: [
3174
+ delegation.cid,
3175
+ delegation.direction,
3176
+ delegation.spaceId,
3177
+ delegation.spaceName ?? null,
3178
+ delegation.counterpartyDid,
3179
+ delegation.delegateDid,
3180
+ delegation.delegatorDid ?? null,
3181
+ delegation.path,
3182
+ JSON.stringify(delegation.actions),
3183
+ delegation.expiry.toISOString(),
3184
+ delegation.status,
3185
+ delegation.createdAt?.toISOString() ?? null,
3186
+ syncedAt
3187
+ ]
3188
+ })),
3189
+ {
3190
+ sql: "INSERT INTO sync_state (source, synced_at, count) VALUES (?, ?, ?)",
3191
+ params: ["applications", syncedAt, applications.data.length]
3192
+ },
3193
+ {
3194
+ sql: "INSERT INTO sync_state (source, synced_at, count) VALUES (?, ?, ?)",
3195
+ params: ["spaces", syncedAt, spaces.data.length]
3196
+ },
3197
+ {
3198
+ sql: "INSERT INTO sync_state (source, synced_at, count) VALUES (?, ?, ?)",
3199
+ params: ["delegations", syncedAt, delegations.data.length]
3200
+ }
3201
+ ];
3202
+ const rebuilt = await dbResult.data.batch(statements);
3203
+ if (!rebuilt.ok) return accountErr(rebuilt.error);
3204
+ return ok3({
3205
+ database: ACCOUNT_INDEX_DB,
3206
+ applications: applications.data.length,
3207
+ spaces: spaces.data.length,
3208
+ delegations: delegations.data.length,
3209
+ syncedAt
3210
+ });
3211
+ },
3212
+ applications: {
3213
+ list: async () => {
3214
+ const dbResult = this.accountDb();
3215
+ if (!dbResult.ok) return dbResult;
3216
+ const queried = await dbResult.data.query(
3217
+ "SELECT applications.app_id, name, description, updated_at, manifest_json, application_state.manifest_hash FROM applications LEFT JOIN application_state ON applications.app_id = application_state.app_id ORDER BY applications.app_id"
3218
+ );
3219
+ if (!queried.ok) return accountErr(queried.error);
3220
+ return ok3(queried.data.rows.map(indexedApplicationFromRow));
3221
+ }
3222
+ },
3223
+ spaces: {
3224
+ list: async () => {
3225
+ const dbResult = this.accountDb();
3226
+ if (!dbResult.ok) return dbResult;
3227
+ const queried = await dbResult.data.query(
3228
+ "SELECT space_id, name, owner_did, type, permissions_json, status, registered_at, updated_at, expires_at FROM spaces ORDER BY name, space_id"
3229
+ );
3230
+ if (!queried.ok) return accountErr(queried.error);
3231
+ return ok3(queried.data.rows.map(indexedSpaceFromRow));
3232
+ }
3233
+ },
3234
+ delegations: {
3235
+ list: async (options = {}) => {
3236
+ const dbResult = this.accountDb();
3237
+ if (!dbResult.ok) return dbResult;
3238
+ const where = [];
3239
+ const params = [];
3240
+ if (options.direction && options.direction !== "all") {
3241
+ where.push("direction = ?");
3242
+ params.push(options.direction);
3243
+ }
3244
+ if (options.space) {
3245
+ where.push("(space_id = ? OR space_name = ?)");
3246
+ params.push(options.space, options.space);
3247
+ }
3248
+ const queried = await dbResult.data.query(
3249
+ `SELECT cid, direction, space_id, space_name, counterparty_did, delegate_did, delegator_did, path, actions_json, expiry, status, created_at FROM delegations${where.length > 0 ? ` WHERE ${where.join(" AND ")}` : ""} ORDER BY space_id, cid`,
3250
+ params
3251
+ );
3252
+ if (!queried.ok) return accountErr(queried.error);
3253
+ return ok3(queried.data.rows.map(indexedDelegationFromRow));
3254
+ }
3255
+ },
3256
+ query: async (sql, params) => {
3257
+ const dbResult = this.accountDb();
3258
+ if (!dbResult.ok) return dbResult;
3259
+ const queried = await dbResult.data.query(sql, params);
3260
+ if (!queried.ok) return accountErr(queried.error);
3261
+ return ok3(queried.data);
3262
+ },
3263
+ status: async () => {
3264
+ const dbResult = this.accountDb();
3265
+ if (!dbResult.ok) return dbResult;
3266
+ const queried = await dbResult.data.query(
3267
+ "SELECT source, synced_at, count FROM sync_state ORDER BY source"
3268
+ );
3269
+ if (!queried.ok) {
3270
+ if (isMissingIndexError(queried.error)) {
3271
+ return ok3({ database: ACCOUNT_INDEX_DB, state: "missing", sources: [] });
3272
+ }
3273
+ return accountErr(queried.error);
3274
+ }
3275
+ return ok3({
3276
+ database: ACCOUNT_INDEX_DB,
3277
+ state: "ready",
3278
+ sources: queried.data.rows.map(([source, syncedAt, count]) => ({
3279
+ source,
3280
+ syncedAt,
3281
+ count
3282
+ }))
3283
+ });
3284
+ }
3285
+ };
3286
+ }
3287
+ async status() {
3288
+ const apps = await this.applications.list();
3289
+ if (!apps.ok) return apps;
3290
+ const delegations = await this.delegations.list();
3291
+ if (!delegations.ok) return delegations;
3292
+ const spaces = await this.spaces.list();
3293
+ if (!spaces.ok) return spaces;
3294
+ return ok3({
3295
+ did: this.config.getDid(),
3296
+ host: this.config.getHost(),
3297
+ primarySpaceId: this.config.getPrimarySpaceId(),
3298
+ accountSpaceId: this.config.getAccountSpaceId(),
3299
+ applications: apps.data.length,
3300
+ spaces: spaces.data.length,
3301
+ grantedDelegations: delegations.data.filter((d) => d.direction === "granted").length,
3302
+ receivedDelegations: delegations.data.filter((d) => d.direction === "received").length
3303
+ });
3304
+ }
3305
+ accountKV() {
3306
+ const accountSpaceId = this.config.getAccountSpaceId();
3307
+ if (!accountSpaceId) {
3308
+ return err3(
3309
+ serviceError3(
3310
+ "ACCOUNT_SPACE_UNAVAILABLE",
3311
+ "Account space is unavailable. Sign in with a wallet-backed profile first.",
3312
+ SERVICE_NAME2
3313
+ )
3314
+ );
3315
+ }
3316
+ return ok3(this.config.getSpaces().get(accountSpaceId).kv);
3317
+ }
3318
+ accountDb() {
3319
+ const db = this.config.getAccountDb?.();
3320
+ if (!db) {
3321
+ return err3(
3322
+ serviceError3(
3323
+ "ACCOUNT_INDEX_UNAVAILABLE",
3324
+ "Account index database is unavailable. Sign in with a wallet-backed profile first.",
3325
+ SERVICE_NAME2
3326
+ )
3327
+ );
3328
+ }
3329
+ return ok3(db);
3321
3330
  }
3322
- if (typeof spec === "string") {
3323
- return [spec];
3331
+ async indexHasApplicationHash(appId, manifestHash) {
3332
+ const dbResult = this.accountDb();
3333
+ if (!dbResult.ok) return false;
3334
+ const schema = await this.ensureAccountIndex(dbResult.data);
3335
+ if (!schema.ok) return false;
3336
+ const queried = await dbResult.data.query(
3337
+ "SELECT 1 FROM application_state WHERE app_id = ? AND manifest_hash = ? LIMIT 1",
3338
+ [appId, manifestHash]
3339
+ );
3340
+ return queried.ok && queried.data.rows.length > 0;
3341
+ }
3342
+ async upsertApplicationIndexQuietly(app) {
3343
+ await ignoreIndexFailure(() => this.upsertApplicationIndex(app));
3344
+ }
3345
+ async upsertApplicationIndex(app) {
3346
+ const dbResult = this.accountDb();
3347
+ if (!dbResult.ok) return ok3(void 0);
3348
+ const schema = await this.ensureAccountIndex(dbResult.data);
3349
+ if (!schema.ok) return schema;
3350
+ const updatedAt = app.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
3351
+ const manifestHash = app.manifestHash ?? hashJson(app.manifests);
3352
+ const written = await dbResult.data.batch([
3353
+ {
3354
+ sql: "INSERT OR REPLACE INTO applications (app_id, name, description, updated_at, manifest_json) VALUES (?, ?, ?, ?, ?)",
3355
+ params: [
3356
+ app.appId,
3357
+ app.name ?? null,
3358
+ app.description ?? null,
3359
+ updatedAt,
3360
+ JSON.stringify(app.manifests)
3361
+ ]
3362
+ },
3363
+ {
3364
+ sql: "INSERT OR REPLACE INTO application_state (app_id, manifest_hash, indexed_at) VALUES (?, ?, ?)",
3365
+ params: [app.appId, manifestHash, updatedAt]
3366
+ }
3367
+ ]);
3368
+ if (!written.ok) return accountErr(written.error);
3369
+ return ok3(void 0);
3324
3370
  }
3325
- if (Array.isArray(spec)) {
3326
- return spec;
3371
+ async deleteApplicationIndexQuietly(appId) {
3372
+ await ignoreIndexFailure(() => this.deleteApplicationIndex(appId));
3373
+ }
3374
+ async deleteApplicationIndex(appId) {
3375
+ const dbResult = this.accountDb();
3376
+ if (!dbResult.ok) return ok3(void 0);
3377
+ const schema = await this.ensureAccountIndex(dbResult.data);
3378
+ if (!schema.ok) return schema;
3379
+ const deleted = await dbResult.data.batch([
3380
+ { sql: "DELETE FROM applications WHERE app_id = ?", params: [appId] },
3381
+ { sql: "DELETE FROM application_state WHERE app_id = ?", params: [appId] }
3382
+ ]);
3383
+ if (!deleted.ok) return accountErr(deleted.error);
3384
+ return ok3(void 0);
3327
3385
  }
3328
- if (spec === null || typeof spec !== "object") {
3329
- throw new ManifestValidationError(
3330
- `manifest.secrets.${name} must be true, a string action, an actions array, or an object`
3331
- );
3386
+ async upsertSpaceIndexQuietly(space) {
3387
+ await ignoreIndexFailure(() => this.upsertSpaceIndex(space));
3332
3388
  }
3333
- if (spec.actions === void 0) {
3334
- return ["read"];
3389
+ async upsertSpaceIndex(space) {
3390
+ const dbResult = this.accountDb();
3391
+ if (!dbResult.ok) return ok3(void 0);
3392
+ const schema = await this.ensureAccountIndex(dbResult.data);
3393
+ if (!schema.ok) return schema;
3394
+ const updatedAt = space.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
3395
+ const written = await dbResult.data.batch([
3396
+ {
3397
+ sql: "INSERT OR REPLACE INTO spaces (space_id, name, owner_did, type, permissions_json, status, registered_at, updated_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
3398
+ params: [
3399
+ space.spaceId,
3400
+ space.name,
3401
+ space.ownerDid,
3402
+ space.type,
3403
+ JSON.stringify(space.permissions),
3404
+ space.status,
3405
+ space.registeredAt ?? updatedAt,
3406
+ updatedAt,
3407
+ space.expiresAt?.toISOString() ?? null
3408
+ ]
3409
+ }
3410
+ ]);
3411
+ if (!written.ok) return accountErr(written.error);
3412
+ return ok3(void 0);
3335
3413
  }
3336
- if (typeof spec.actions === "string") {
3337
- return [spec.actions];
3414
+ async deleteSpaceIndexQuietly(spaceId) {
3415
+ await ignoreIndexFailure(() => this.deleteSpaceIndex(spaceId));
3416
+ }
3417
+ async deleteSpaceIndex(spaceId) {
3418
+ const dbResult = this.accountDb();
3419
+ if (!dbResult.ok) return ok3(void 0);
3420
+ const schema = await this.ensureAccountIndex(dbResult.data);
3421
+ if (!schema.ok) return schema;
3422
+ const deleted = await dbResult.data.batch([
3423
+ { sql: "DELETE FROM spaces WHERE space_id = ?", params: [spaceId] }
3424
+ ]);
3425
+ if (!deleted.ok) return accountErr(deleted.error);
3426
+ return ok3(void 0);
3338
3427
  }
3339
- if (Array.isArray(spec.actions)) {
3340
- return spec.actions;
3428
+ async resolveSpace(space) {
3429
+ const listed = await this.config.getSpaces().list();
3430
+ if (!listed.ok) return accountErr(listed.error);
3431
+ const found = listed.data.find((candidate) => candidate.id === space || candidate.name === space);
3432
+ if (!found) {
3433
+ return err3(
3434
+ serviceError3("SPACE_NOT_FOUND", `No account space found for ${JSON.stringify(space)}`, SERVICE_NAME2)
3435
+ );
3436
+ }
3437
+ return ok3(found);
3438
+ }
3439
+ async replaceApplicationsIndexQuietly(applications) {
3440
+ await ignoreIndexFailure(async () => {
3441
+ const dbResult = this.accountDb();
3442
+ if (!dbResult.ok) return;
3443
+ const syncedAt = (/* @__PURE__ */ new Date()).toISOString();
3444
+ const schema = await this.ensureAccountIndex(dbResult.data);
3445
+ if (!schema.ok) return;
3446
+ await dbResult.data.batch([
3447
+ { sql: "DELETE FROM applications" },
3448
+ { sql: "DELETE FROM application_state" },
3449
+ ...applications.map((app) => ({
3450
+ sql: "INSERT OR REPLACE INTO applications (app_id, name, description, updated_at, manifest_json) VALUES (?, ?, ?, ?, ?)",
3451
+ params: [
3452
+ app.appId,
3453
+ app.name ?? null,
3454
+ app.description ?? null,
3455
+ app.updatedAt ?? syncedAt,
3456
+ JSON.stringify(app.manifests)
3457
+ ]
3458
+ })),
3459
+ ...applications.map((app) => ({
3460
+ sql: "INSERT OR REPLACE INTO application_state (app_id, manifest_hash, indexed_at) VALUES (?, ?, ?)",
3461
+ params: [app.appId, app.manifestHash ?? hashJson(app.manifests), syncedAt]
3462
+ })),
3463
+ {
3464
+ sql: "INSERT OR REPLACE INTO sync_state (source, synced_at, count) VALUES (?, ?, ?)",
3465
+ params: ["applications", syncedAt, applications.length]
3466
+ }
3467
+ ]);
3468
+ });
3341
3469
  }
3342
- throw new ManifestValidationError(
3343
- `manifest.secrets.${name}.actions must be a string or array`
3344
- );
3345
- }
3346
- function secretEntriesForManifest(secrets) {
3347
- if (secrets === void 0) {
3348
- return [];
3470
+ async replaceSpacesIndexQuietly(spaces) {
3471
+ await ignoreIndexFailure(async () => {
3472
+ const dbResult = this.accountDb();
3473
+ if (!dbResult.ok) return;
3474
+ const syncedAt = (/* @__PURE__ */ new Date()).toISOString();
3475
+ const schema = await this.ensureAccountIndex(dbResult.data);
3476
+ if (!schema.ok) return;
3477
+ await dbResult.data.batch([
3478
+ { sql: "DELETE FROM spaces" },
3479
+ ...spaces.map((space) => ({
3480
+ sql: "INSERT OR REPLACE INTO spaces (space_id, name, owner_did, type, permissions_json, status, registered_at, updated_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
3481
+ params: [
3482
+ space.spaceId,
3483
+ space.name,
3484
+ space.ownerDid,
3485
+ space.type,
3486
+ JSON.stringify(space.permissions),
3487
+ space.status,
3488
+ space.registeredAt ?? syncedAt,
3489
+ space.updatedAt ?? syncedAt,
3490
+ space.expiresAt?.toISOString() ?? null
3491
+ ]
3492
+ })),
3493
+ {
3494
+ sql: "INSERT OR REPLACE INTO sync_state (source, synced_at, count) VALUES (?, ?, ?)",
3495
+ params: ["spaces", syncedAt, spaces.length]
3496
+ }
3497
+ ]);
3498
+ });
3349
3499
  }
3350
- const entries = [];
3351
- for (const [name, spec] of Object.entries(secrets)) {
3352
- const actions = secretActionsFromSpec(name, spec);
3353
- const secretPath = resolveSecretPath(
3354
- secretNameFromSpec(name, spec),
3355
- { scope: secretScopeFromSpec(spec) }
3356
- );
3357
- const extra = spec !== true && typeof spec === "object" && !Array.isArray(spec) ? spec : {};
3358
- entries.push({
3359
- service: VAULT_PERMISSION_SERVICE,
3360
- space: SECRETS_SPACE,
3361
- path: secretPath.vaultKey,
3362
- actions: normalizeSecretActions(actions),
3363
- skipPrefix: true,
3364
- ...extra.expiry !== void 0 ? { expiry: extra.expiry } : {},
3365
- ...extra.description !== void 0 ? { description: extra.description } : {}
3500
+ async replaceDelegationsIndexQuietly(delegations) {
3501
+ await ignoreIndexFailure(async () => {
3502
+ const dbResult = this.accountDb();
3503
+ if (!dbResult.ok) return;
3504
+ const syncedAt = (/* @__PURE__ */ new Date()).toISOString();
3505
+ const schema = await this.ensureAccountIndex(dbResult.data);
3506
+ if (!schema.ok) return;
3507
+ await dbResult.data.batch([
3508
+ { sql: "DELETE FROM delegations" },
3509
+ ...delegations.map((delegation) => ({
3510
+ sql: "INSERT OR REPLACE INTO delegations (cid, direction, space_id, space_name, counterparty_did, delegate_did, delegator_did, path, actions_json, expiry, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
3511
+ params: [
3512
+ delegation.cid,
3513
+ delegation.direction,
3514
+ delegation.spaceId,
3515
+ delegation.spaceName ?? null,
3516
+ delegation.counterpartyDid,
3517
+ delegation.delegateDid,
3518
+ delegation.delegatorDid ?? null,
3519
+ delegation.path,
3520
+ JSON.stringify(delegation.actions),
3521
+ delegation.expiry.toISOString(),
3522
+ delegation.status,
3523
+ delegation.createdAt?.toISOString() ?? null,
3524
+ syncedAt
3525
+ ]
3526
+ })),
3527
+ {
3528
+ sql: "INSERT OR REPLACE INTO sync_state (source, synced_at, count) VALUES (?, ?, ?)",
3529
+ params: ["delegations", syncedAt, delegations.length]
3530
+ }
3531
+ ]);
3366
3532
  });
3367
3533
  }
3368
- return entries;
3534
+ async ensureAccountIndex(db) {
3535
+ const migrated = await db.migrations.apply({
3536
+ namespace: ACCOUNT_INDEX_NAMESPACE,
3537
+ migrations: [
3538
+ {
3539
+ id: "001_initial",
3540
+ sql: ACCOUNT_INDEX_SCHEMA
3541
+ }
3542
+ ]
3543
+ });
3544
+ if (!migrated.ok) return accountErr(migrated.error);
3545
+ return ok3(void 0);
3546
+ }
3547
+ };
3548
+ var ACCOUNT_INDEX_SCHEMA = [
3549
+ `CREATE TABLE IF NOT EXISTS applications (
3550
+ app_id TEXT PRIMARY KEY,
3551
+ name TEXT,
3552
+ description TEXT,
3553
+ updated_at TEXT,
3554
+ manifest_json TEXT NOT NULL
3555
+ )`,
3556
+ `CREATE TABLE IF NOT EXISTS application_state (
3557
+ app_id TEXT PRIMARY KEY,
3558
+ manifest_hash TEXT NOT NULL,
3559
+ indexed_at TEXT NOT NULL
3560
+ )`,
3561
+ `CREATE TABLE IF NOT EXISTS spaces (
3562
+ space_id TEXT PRIMARY KEY,
3563
+ name TEXT NOT NULL,
3564
+ owner_did TEXT NOT NULL,
3565
+ type TEXT NOT NULL,
3566
+ permissions_json TEXT NOT NULL,
3567
+ status TEXT NOT NULL,
3568
+ registered_at TEXT,
3569
+ updated_at TEXT NOT NULL,
3570
+ expires_at TEXT
3571
+ )`,
3572
+ `CREATE TABLE IF NOT EXISTS delegations (
3573
+ cid TEXT PRIMARY KEY,
3574
+ direction TEXT NOT NULL,
3575
+ space_id TEXT NOT NULL,
3576
+ space_name TEXT,
3577
+ counterparty_did TEXT NOT NULL,
3578
+ delegate_did TEXT NOT NULL,
3579
+ delegator_did TEXT,
3580
+ path TEXT NOT NULL,
3581
+ actions_json TEXT NOT NULL,
3582
+ expiry TEXT NOT NULL,
3583
+ status TEXT NOT NULL,
3584
+ created_at TEXT,
3585
+ updated_at TEXT NOT NULL
3586
+ )`,
3587
+ `CREATE TABLE IF NOT EXISTS sync_state (
3588
+ source TEXT PRIMARY KEY,
3589
+ synced_at TEXT NOT NULL,
3590
+ count INTEGER NOT NULL
3591
+ )`,
3592
+ "CREATE INDEX IF NOT EXISTS idx_delegations_direction ON delegations(direction)",
3593
+ "CREATE INDEX IF NOT EXISTS idx_delegations_space ON delegations(space_id)",
3594
+ "CREATE INDEX IF NOT EXISTS idx_delegations_counterparty ON delegations(counterparty_did)",
3595
+ "CREATE INDEX IF NOT EXISTS idx_spaces_owner ON spaces(owner_did)",
3596
+ "CREATE INDEX IF NOT EXISTS idx_spaces_type ON spaces(type)"
3597
+ ];
3598
+ function applicationKey(appId) {
3599
+ return `${ACCOUNT_REGISTRY_PATH}${appId}`;
3369
3600
  }
3370
- function resolveEntry(entry, prefix, _inheritedExpiryMs, inheritedSpace) {
3371
- const skipPrefixForEntry = entry.skipPrefix === true || entry.service === ENCRYPTION_PERMISSION_SERVICE;
3372
- const resolvedPath = applyPrefix(prefix, entry.path, skipPrefixForEntry);
3373
- const entryExpiryMs = entry.expiry !== void 0 ? parseExpiry(entry.expiry) : void 0;
3374
- return expandPermissionEntry({
3375
- ...entry,
3376
- space: entry.space ?? inheritedSpace,
3377
- path: resolvedPath,
3378
- skipPrefix: true
3379
- }).map((expanded) => ({
3380
- service: expanded.service,
3381
- space: expanded.space ?? inheritedSpace,
3382
- path: expanded.path,
3383
- actions: expanded.actions,
3384
- // Only populate `expiryMs` when the entry had its own expiry override.
3385
- // When absent, callers use the parent (delegation or manifest) expiry
3386
- // which is carried on ResolvedDelegate.expiryMs / ResolvedCapabilities.expiryMs.
3387
- ...entryExpiryMs !== void 0 ? { expiryMs: entryExpiryMs } : {},
3388
- ...entry.description !== void 0 ? { description: entry.description } : {}
3389
- }));
3601
+ function appIdFromKey(key) {
3602
+ return key.startsWith(ACCOUNT_REGISTRY_PATH) ? key.slice(ACCOUNT_REGISTRY_PATH.length) : key;
3390
3603
  }
3391
- function expandVaultPermissionEntry(entry) {
3392
- const byBase = /* @__PURE__ */ new Map();
3393
- for (const action of entry.actions) {
3394
- const expansion = vaultActionExpansion(action);
3395
- for (const base of expansion.bases) {
3396
- const actions = byBase.get(base) ?? [];
3397
- if (!actions.includes(expansion.action)) {
3398
- actions.push(expansion.action);
3399
- }
3400
- byBase.set(base, actions);
3401
- }
3402
- }
3403
- return [...byBase.entries()].map(([base, actions]) => ({
3404
- ...entry,
3405
- service: "tinycloud.kv",
3406
- path: vaultKVPath(base, entry.path),
3407
- actions,
3408
- skipPrefix: true
3409
- }));
3604
+ function applicationFromRecord(key, record) {
3605
+ const manifests = Array.isArray(record.manifests) ? record.manifests : [];
3606
+ const first = manifests[0];
3607
+ return {
3608
+ appId: record.app_id ?? record.appId ?? first?.app_id ?? appIdFromKey(key),
3609
+ manifests,
3610
+ updatedAt: record.updated_at ?? record.updatedAt,
3611
+ name: first?.name,
3612
+ description: first?.description,
3613
+ manifestHash: record.manifest_hash ?? record.manifestHash ?? hashJson(manifests)
3614
+ };
3410
3615
  }
3411
- function vaultActionExpansion(action) {
3412
- const normalized = normalizeVaultAction(action);
3413
- if (normalized === "read" || normalized === "get") {
3414
- return { bases: ["vault"], action: "tinycloud.kv/get" };
3415
- }
3416
- if (normalized === "write" || normalized === "put") {
3417
- return { bases: ["vault"], action: "tinycloud.kv/put" };
3418
- }
3419
- if (normalized === "delete" || normalized === "del") {
3420
- return { bases: ["vault"], action: "tinycloud.kv/del" };
3421
- }
3422
- if (normalized === "list") {
3423
- return { bases: ["vault"], action: "tinycloud.kv/list" };
3424
- }
3425
- if (normalized === "head") {
3426
- return { bases: ["vault"], action: "tinycloud.kv/get" };
3616
+ function indexedApplicationFromRow(row) {
3617
+ const [appId, name, description, updatedAt, manifestJson, manifestHash] = row;
3618
+ return {
3619
+ appId,
3620
+ name: name ?? void 0,
3621
+ description: description ?? void 0,
3622
+ updatedAt: updatedAt ?? void 0,
3623
+ manifests: JSON.parse(manifestJson),
3624
+ manifestHash: manifestHash ?? void 0
3625
+ };
3626
+ }
3627
+ function spaceKey(spaceId) {
3628
+ return `${ACCOUNT_SPACES_PATH}${spaceId}`;
3629
+ }
3630
+ function spaceIdFromKey(key) {
3631
+ return key.startsWith(ACCOUNT_SPACES_PATH) ? key.slice(ACCOUNT_SPACES_PATH.length) : key;
3632
+ }
3633
+ function spaceRecordFromInput(space) {
3634
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3635
+ const accountSpace = "spaceId" in space ? space : {
3636
+ spaceId: space.id,
3637
+ name: space.name ?? space.id.split(":").pop() ?? space.id,
3638
+ ownerDid: space.owner ?? "",
3639
+ type: space.type ?? "discovered",
3640
+ permissions: space.permissions ?? [],
3641
+ status: "active",
3642
+ expiresAt: space.expiresAt
3643
+ };
3644
+ return {
3645
+ space_id: accountSpace.spaceId,
3646
+ name: accountSpace.name,
3647
+ owner_did: accountSpace.ownerDid,
3648
+ type: accountSpace.type,
3649
+ permissions: accountSpace.permissions,
3650
+ status: accountSpace.status,
3651
+ registered_at: accountSpace.registeredAt ?? now,
3652
+ updated_at: now,
3653
+ expires_at: accountSpace.expiresAt instanceof Date ? accountSpace.expiresAt.toISOString() : accountSpace.expiresAt
3654
+ };
3655
+ }
3656
+ function spaceFromRecord(key, record) {
3657
+ const expiresAt = record.expires_at ?? record.expiresAt;
3658
+ return {
3659
+ spaceId: record.space_id ?? record.spaceId ?? spaceIdFromKey(key),
3660
+ name: record.name ?? spaceIdFromKey(key).split(":").pop() ?? spaceIdFromKey(key),
3661
+ ownerDid: record.owner_did ?? record.ownerDid ?? record.owner ?? "",
3662
+ type: record.type ?? "discovered",
3663
+ permissions: Array.isArray(record.permissions) ? record.permissions : [],
3664
+ status: record.status ?? "active",
3665
+ registeredAt: record.registered_at ?? record.registeredAt,
3666
+ updatedAt: record.updated_at ?? record.updatedAt,
3667
+ expiresAt: expiresAt ? new Date(expiresAt) : void 0
3668
+ };
3669
+ }
3670
+ function indexedSpaceFromRow(row) {
3671
+ const [spaceId, name, ownerDid, type, permissionsJson, status, registeredAt, updatedAt, expiresAt] = row;
3672
+ return {
3673
+ spaceId,
3674
+ name,
3675
+ ownerDid,
3676
+ type,
3677
+ permissions: JSON.parse(permissionsJson),
3678
+ status,
3679
+ registeredAt: registeredAt ?? void 0,
3680
+ updatedAt,
3681
+ expiresAt: expiresAt ? new Date(expiresAt) : void 0
3682
+ };
3683
+ }
3684
+ function hashJson(value) {
3685
+ const input = stableJson(value);
3686
+ let hash = 0xcbf29ce484222325n;
3687
+ const prime = 0x100000001b3n;
3688
+ for (let index = 0; index < input.length; index += 1) {
3689
+ hash ^= BigInt(input.charCodeAt(index));
3690
+ hash = BigInt.asUintN(64, hash * prime);
3691
+ }
3692
+ return hash.toString(16).padStart(16, "0");
3693
+ }
3694
+ function stableJson(value) {
3695
+ if (value === null || typeof value !== "object") {
3696
+ return JSON.stringify(value);
3427
3697
  }
3428
- if (normalized === "metadata") {
3429
- return { bases: ["vault"], action: "tinycloud.kv/metadata" };
3698
+ if (Array.isArray(value)) {
3699
+ return `[${value.map(stableJson).join(",")}]`;
3430
3700
  }
3431
- throw new ManifestValidationError(
3432
- `unknown vault action ${JSON.stringify(action)}; expected read, write, delete, get, put, del, list, head, or metadata`
3433
- );
3701
+ const object = value;
3702
+ return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableJson(object[key])}`).join(",")}}`;
3434
3703
  }
3435
- function normalizeVaultAction(action) {
3436
- if (action.startsWith(`${VAULT_PERMISSION_SERVICE}/`)) {
3437
- return action.slice(`${VAULT_PERMISSION_SERVICE}/`.length);
3704
+ function indexedDelegationFromRow(row) {
3705
+ const [
3706
+ cid,
3707
+ direction,
3708
+ spaceId,
3709
+ spaceName,
3710
+ counterpartyDid,
3711
+ delegateDid,
3712
+ delegatorDid,
3713
+ path,
3714
+ actionsJson,
3715
+ expiry,
3716
+ status,
3717
+ createdAt
3718
+ ] = row;
3719
+ return {
3720
+ cid,
3721
+ direction,
3722
+ spaceId,
3723
+ spaceName: spaceName ?? void 0,
3724
+ counterpartyDid,
3725
+ delegateDid,
3726
+ delegatorDid: delegatorDid ?? void 0,
3727
+ path,
3728
+ actions: JSON.parse(actionsJson),
3729
+ expiry: new Date(expiry),
3730
+ status,
3731
+ createdAt: createdAt ? new Date(createdAt) : void 0
3732
+ };
3733
+ }
3734
+ function mapDelegation(delegation, space, direction) {
3735
+ return {
3736
+ cid: delegation.cid,
3737
+ direction,
3738
+ spaceId: delegation.spaceId || space.id,
3739
+ spaceName: space.name,
3740
+ counterpartyDid: direction === "granted" ? delegation.delegateDID : delegation.delegatorDID ?? delegation.delegateDID,
3741
+ delegateDid: delegation.delegateDID,
3742
+ delegatorDid: delegation.delegatorDID,
3743
+ path: delegation.path,
3744
+ actions: delegation.actions,
3745
+ expiry: delegation.expiry,
3746
+ status: delegation.isRevoked ? "revoked" : delegation.expiry.getTime() <= Date.now() ? "expired" : "active",
3747
+ createdAt: delegation.createdAt
3748
+ };
3749
+ }
3750
+ function accountErr(error) {
3751
+ return err3(serviceError3(error.code, error.message, SERVICE_NAME2, { cause: error.cause, meta: error.meta }));
3752
+ }
3753
+ function isMissingIndexError(error) {
3754
+ return /no such table:/i.test(error.message);
3755
+ }
3756
+ async function ignoreIndexFailure(task) {
3757
+ try {
3758
+ await task();
3759
+ } catch {
3438
3760
  }
3439
- if (action.startsWith("tinycloud.kv/")) {
3440
- return action.slice("tinycloud.kv/".length);
3761
+ }
3762
+
3763
+ // src/index.ts
3764
+ import {
3765
+ ServiceContext as ServiceContext2,
3766
+ KVService as KVService2,
3767
+ PrefixedKVService,
3768
+ ok as ok5,
3769
+ err as err5,
3770
+ serviceError as serviceError5,
3771
+ ErrorCodes as ErrorCodes2,
3772
+ defaultRetryPolicy as defaultRetryPolicy2,
3773
+ SQLService as SQLService2,
3774
+ DatabaseHandle,
3775
+ SQLAction,
3776
+ DuckDbService as DuckDbService2,
3777
+ DuckDbDatabaseHandle,
3778
+ DuckDbAction,
3779
+ HooksService as HooksService2,
3780
+ DataVaultService,
3781
+ VaultHeaders,
3782
+ VaultPublicSpaceKVActions,
3783
+ createVaultCrypto,
3784
+ SecretsService,
3785
+ SECRET_NAME_RE as SECRET_NAME_RE2,
3786
+ canonicalizeSecretScope,
3787
+ resolveSecretListPrefix,
3788
+ resolveSecretPath as resolveSecretPath2,
3789
+ EncryptionService,
3790
+ parseNetworkId as parseNetworkId2,
3791
+ buildNetworkId as buildNetworkId2,
3792
+ isNetworkId,
3793
+ networkDiscoveryKey,
3794
+ NetworkIdError,
3795
+ ENCRYPTION_NETWORK_URN_PREFIX,
3796
+ NETWORK_NAME_PATTERN,
3797
+ canonicalizeEncryptionJson,
3798
+ canonicalHashHex,
3799
+ hexEncode,
3800
+ hexDecode,
3801
+ base64Encode,
3802
+ base64Decode,
3803
+ utf8Encode,
3804
+ utf8Decode,
3805
+ encryptToNetwork,
3806
+ decryptEnvelopeWithKey,
3807
+ validateEnvelope,
3808
+ generateRandomReceiverKey,
3809
+ deriveSignedReceiverKey,
3810
+ buildCanonicalDecryptRequest,
3811
+ buildDecryptFacts,
3812
+ buildDecryptAttenuation,
3813
+ buildDecryptInvocation,
3814
+ checkDecryptInvocationInput,
3815
+ verifyDecryptResponse,
3816
+ canonicalSignedResponse,
3817
+ openWrappedKey,
3818
+ discoverNetwork,
3819
+ ensureNetworkUsableForDecrypt,
3820
+ DEFAULT_ENCRYPTION_ALG,
3821
+ ENVELOPE_VERSION,
3822
+ DEFAULT_KEY_VERSION,
3823
+ DECRYPT_FACT_TYPE,
3824
+ DECRYPT_RESULT_TYPE,
3825
+ DECRYPT_ACTION,
3826
+ ENCRYPTION_SERVICE,
3827
+ ENCRYPTION_SERVICE_SHORT,
3828
+ encryptionError
3829
+ } from "@tinycloud/sdk-services";
3830
+
3831
+ // src/space.ts
3832
+ async function fetchPeerId(host, spaceId) {
3833
+ const res = await fetch(
3834
+ `${host}/peer/generate/${encodeURIComponent(spaceId)}`
3835
+ );
3836
+ if (!res.ok) {
3837
+ const error = await res.text().catch(() => res.statusText);
3838
+ throw new Error(`Failed to get peer ID: ${res.status} - ${error}`);
3441
3839
  }
3442
- if (action.includes("/")) {
3443
- throw new ManifestValidationError(
3444
- `unknown vault action ${JSON.stringify(action)}; expected a tinycloud.vault or tinycloud.kv action`
3445
- );
3840
+ return res.text();
3841
+ }
3842
+ async function submitHostDelegation(host, headers) {
3843
+ const res = await fetch(`${host}/delegate`, {
3844
+ method: "POST",
3845
+ headers
3846
+ });
3847
+ return {
3848
+ success: res.ok,
3849
+ status: res.status,
3850
+ error: res.ok ? void 0 : await res.text().catch(() => res.statusText)
3851
+ };
3852
+ }
3853
+ async function activateSessionWithHost(host, delegationHeader) {
3854
+ const res = await fetch(`${host}/delegate`, {
3855
+ method: "POST",
3856
+ headers: delegationHeader
3857
+ });
3858
+ if (res.ok) {
3859
+ try {
3860
+ const body = await res.json();
3861
+ return {
3862
+ success: true,
3863
+ status: res.status,
3864
+ activated: body.activated ?? [],
3865
+ skipped: body.skipped ?? []
3866
+ };
3867
+ } catch {
3868
+ return {
3869
+ success: true,
3870
+ status: res.status,
3871
+ activated: [],
3872
+ skipped: []
3873
+ };
3874
+ }
3446
3875
  }
3447
- return action;
3448
- }
3449
- function vaultKVPath(base, path) {
3450
- const normalized = path.startsWith("/") ? path.slice(1) : path;
3451
- return `${base}/${normalized}`;
3452
- }
3453
- function cloneResourceCapability(entry) {
3454
3876
  return {
3455
- service: entry.service,
3456
- space: entry.space,
3457
- path: entry.path,
3458
- actions: [...entry.actions],
3459
- ...entry.expiryMs !== void 0 ? { expiryMs: entry.expiryMs } : {},
3460
- ...entry.description !== void 0 ? { description: entry.description } : {}
3877
+ success: false,
3878
+ status: res.status,
3879
+ error: await res.text().catch(() => res.statusText)
3461
3880
  };
3462
3881
  }
3463
- function clonePermissionEntry(entry) {
3882
+
3883
+ // src/delegations/DelegationManager.ts
3884
+ var DelegationAction = {
3885
+ CREATE: "tinycloud.delegation/create",
3886
+ REVOKE: "tinycloud.delegation/revoke",
3887
+ LIST: "tinycloud.delegation/list",
3888
+ GET: "tinycloud.delegation/get",
3889
+ CHECK: "tinycloud.delegation/check"
3890
+ };
3891
+ function createError(code, message, cause, meta) {
3464
3892
  return {
3465
- service: entry.service,
3466
- ...entry.space !== void 0 ? { space: entry.space } : {},
3467
- path: entry.path,
3468
- actions: [...entry.actions],
3469
- ...entry.skipPrefix !== void 0 ? { skipPrefix: entry.skipPrefix } : {},
3470
- ...entry.expiry !== void 0 ? { expiry: entry.expiry } : {},
3471
- ...entry.description !== void 0 ? { description: entry.description } : {}
3893
+ code,
3894
+ message,
3895
+ service: "delegation",
3896
+ cause,
3897
+ meta
3472
3898
  };
3473
3899
  }
3474
- function dedupeResources(resources) {
3475
- const byKey = /* @__PURE__ */ new Map();
3476
- for (const resource of resources) {
3477
- const key = `${resource.service}\0${resource.space}\0${resource.path}\0${resource.expiryMs ?? ""}`;
3478
- const existing = byKey.get(key);
3479
- if (existing === void 0) {
3480
- byKey.set(key, cloneResourceCapability(resource));
3481
- continue;
3900
+ var DelegationManager = class {
3901
+ /**
3902
+ * Creates a new DelegationManager instance.
3903
+ *
3904
+ * @param config - Configuration including hosts, session, and invoke function
3905
+ */
3906
+ constructor(config) {
3907
+ this.hosts = config.hosts;
3908
+ this.session = config.session;
3909
+ this.invoke = config.invoke;
3910
+ this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
3911
+ }
3912
+ /**
3913
+ * Updates the session (e.g., after re-authentication).
3914
+ *
3915
+ * @param session - New session to use for operations
3916
+ */
3917
+ updateSession(session) {
3918
+ this.session = session;
3919
+ }
3920
+ /**
3921
+ * Gets the primary host URL.
3922
+ */
3923
+ get host() {
3924
+ return this.hosts[0];
3925
+ }
3926
+ /**
3927
+ * Executes an invoke operation against the delegation API.
3928
+ */
3929
+ async invokeOperation(path, action, body) {
3930
+ const headers = this.invoke(this.session, "delegation", path, action);
3931
+ return this.fetchFn(`${this.host}/invoke`, {
3932
+ method: "POST",
3933
+ headers,
3934
+ body
3935
+ });
3936
+ }
3937
+ /**
3938
+ * Creates a new delegation.
3939
+ *
3940
+ * Delegates specific permissions to another DID for a given path.
3941
+ * The delegatee can then use these permissions to access resources
3942
+ * within the specified scope.
3943
+ *
3944
+ * @param params - Parameters for the delegation
3945
+ * @returns Result containing the created Delegation or an error
3946
+ *
3947
+ * @example
3948
+ * ```typescript
3949
+ * const result = await manager.create({
3950
+ * delegateDID: bob.did,
3951
+ * path: "documents/shared/",
3952
+ * actions: ["tinycloud.kv/get", "tinycloud.kv/put"],
3953
+ * expiry: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 days
3954
+ * });
3955
+ * ```
3956
+ */
3957
+ async create(params) {
3958
+ if (!params.delegateDID) {
3959
+ return {
3960
+ ok: false,
3961
+ error: createError(
3962
+ DelegationErrorCodes.INVALID_INPUT,
3963
+ "delegateDID is required"
3964
+ )
3965
+ };
3482
3966
  }
3483
- const seen = new Set(existing.actions);
3484
- for (const action of resource.actions) {
3485
- if (!seen.has(action)) {
3486
- existing.actions.push(action);
3487
- seen.add(action);
3967
+ if (!params.path) {
3968
+ return {
3969
+ ok: false,
3970
+ error: createError(
3971
+ DelegationErrorCodes.INVALID_INPUT,
3972
+ "path is required"
3973
+ )
3974
+ };
3975
+ }
3976
+ if (!params.actions || params.actions.length === 0) {
3977
+ return {
3978
+ ok: false,
3979
+ error: createError(
3980
+ DelegationErrorCodes.INVALID_INPUT,
3981
+ "at least one action is required"
3982
+ )
3983
+ };
3984
+ }
3985
+ try {
3986
+ const body = JSON.stringify({
3987
+ delegateDID: params.delegateDID,
3988
+ path: params.path,
3989
+ actions: params.actions,
3990
+ expiry: params.expiry?.toISOString(),
3991
+ disableSubDelegation: params.disableSubDelegation ?? false,
3992
+ statement: params.statement
3993
+ });
3994
+ const response = await this.invokeOperation(
3995
+ params.path,
3996
+ DelegationAction.CREATE,
3997
+ body
3998
+ );
3999
+ if (!response.ok) {
4000
+ const errorText = await response.text();
4001
+ return {
4002
+ ok: false,
4003
+ error: createError(
4004
+ DelegationErrorCodes.CREATION_FAILED,
4005
+ `Failed to create delegation: ${response.status} - ${errorText}`,
4006
+ void 0,
4007
+ { status: response.status, path: params.path }
4008
+ )
4009
+ };
4010
+ }
4011
+ const apiResponse = await response.json();
4012
+ const delegation = {
4013
+ cid: apiResponse.cid ?? "",
4014
+ delegateDID: params.delegateDID,
4015
+ spaceId: this.session.spaceId,
4016
+ path: params.path,
4017
+ actions: params.actions,
4018
+ expiry: params.expiry ?? new Date(Date.now() + EXPIRY.SHARE_MS),
4019
+ isRevoked: false,
4020
+ allowSubDelegation: !(params.disableSubDelegation ?? false),
4021
+ createdAt: /* @__PURE__ */ new Date()
4022
+ };
4023
+ return { ok: true, data: delegation };
4024
+ } catch (error) {
4025
+ if (error instanceof Error && error.name === "AbortError") {
4026
+ return {
4027
+ ok: false,
4028
+ error: createError(
4029
+ DelegationErrorCodes.ABORTED,
4030
+ "Request aborted",
4031
+ error
4032
+ )
4033
+ };
4034
+ }
4035
+ return {
4036
+ ok: false,
4037
+ error: createError(
4038
+ DelegationErrorCodes.NETWORK_ERROR,
4039
+ `Network error during delegation creation: ${String(error)}`,
4040
+ error instanceof Error ? error : void 0
4041
+ )
4042
+ };
4043
+ }
4044
+ }
4045
+ /**
4046
+ * Revokes an existing delegation.
4047
+ *
4048
+ * Once revoked, the delegation can no longer be used to access resources.
4049
+ * This also invalidates any sub-delegations derived from this delegation.
4050
+ *
4051
+ * @param cid - The CID of the delegation to revoke
4052
+ * @returns Result indicating success or an error
4053
+ *
4054
+ * @example
4055
+ * ```typescript
4056
+ * const result = await manager.revoke("bafy...");
4057
+ * if (result.ok) {
4058
+ * console.log("Delegation revoked successfully");
4059
+ * }
4060
+ * ```
4061
+ */
4062
+ async revoke(cid) {
4063
+ if (!cid) {
4064
+ return {
4065
+ ok: false,
4066
+ error: createError(
4067
+ DelegationErrorCodes.INVALID_INPUT,
4068
+ "cid is required"
4069
+ )
4070
+ };
4071
+ }
4072
+ try {
4073
+ const body = JSON.stringify({ cid });
4074
+ const response = await this.invokeOperation(
4075
+ cid,
4076
+ DelegationAction.REVOKE,
4077
+ body
4078
+ );
4079
+ if (!response.ok) {
4080
+ const errorText = await response.text();
4081
+ if (response.status === 404) {
4082
+ return {
4083
+ ok: false,
4084
+ error: createError(
4085
+ DelegationErrorCodes.NOT_FOUND,
4086
+ `Delegation not found: ${cid}`
4087
+ )
4088
+ };
4089
+ }
4090
+ return {
4091
+ ok: false,
4092
+ error: createError(
4093
+ DelegationErrorCodes.REVOCATION_FAILED,
4094
+ `Failed to revoke delegation: ${response.status} - ${errorText}`,
4095
+ void 0,
4096
+ { status: response.status, cid }
4097
+ )
4098
+ };
3488
4099
  }
4100
+ return { ok: true, data: void 0 };
4101
+ } catch (error) {
4102
+ if (error instanceof Error && error.name === "AbortError") {
4103
+ return {
4104
+ ok: false,
4105
+ error: createError(
4106
+ DelegationErrorCodes.ABORTED,
4107
+ "Request aborted",
4108
+ error
4109
+ )
4110
+ };
4111
+ }
4112
+ return {
4113
+ ok: false,
4114
+ error: createError(
4115
+ DelegationErrorCodes.NETWORK_ERROR,
4116
+ `Network error during delegation revocation: ${String(error)}`,
4117
+ error instanceof Error ? error : void 0
4118
+ )
4119
+ };
3489
4120
  }
3490
- if (existing.description === void 0 && resource.description !== void 0) {
3491
- existing.description = resource.description;
3492
- }
3493
- }
3494
- return [...byKey.values()];
3495
- }
3496
- function capabilitiesReadPermission(space) {
3497
- return {
3498
- service: "tinycloud.capabilities",
3499
- space,
3500
- path: "",
3501
- actions: ["tinycloud.capabilities/read"]
3502
- };
3503
- }
3504
- function withCapabilitiesReadForSpaces(resources) {
3505
- if (resources.length === 0) {
3506
- return [];
3507
- }
3508
- const spaces = new Set(
3509
- resources.filter((resource) => resource.service !== ENCRYPTION_PERMISSION_SERVICE).map((resource) => resource.space)
3510
- );
3511
- return dedupeResources([
3512
- ...resources,
3513
- ...[...spaces].map(capabilitiesReadPermission)
3514
- ]);
3515
- }
3516
- function accountRegistryPermission() {
3517
- return {
3518
- service: "tinycloud.kv",
3519
- space: ACCOUNT_REGISTRY_SPACE,
3520
- path: ACCOUNT_REGISTRY_PATH,
3521
- actions: ["tinycloud.kv/get", "tinycloud.kv/put", "tinycloud.kv/list"]
3522
- };
3523
- }
3524
- function composeManifestRequest(inputs, options = {}) {
3525
- if (!Array.isArray(inputs) || inputs.length === 0) {
3526
- throw new ManifestValidationError(
3527
- "composeManifestRequest requires at least one manifest"
3528
- );
3529
- }
3530
- const includeAccountRegistryPermissions = options.includeAccountRegistryPermissions ?? true;
3531
- const manifests = inputs.map(validateManifest);
3532
- const resolved = manifests.map(resolveManifest);
3533
- const resources = resolved.flatMap((entry) => entry.resources);
3534
- const delegationTargets = resolved.flatMap(
3535
- (entry) => entry.additionalDelegates.map((delegate) => ({
3536
- ...delegate,
3537
- permissions: dedupeResources(delegate.permissions)
3538
- }))
3539
- );
3540
- if (includeAccountRegistryPermissions) {
3541
- resources.push(accountRegistryPermission());
3542
4121
  }
3543
- const resourcesWithImplicitCapabilities = withCapabilitiesReadForSpaces(resources);
3544
- const manifestsByAppId = /* @__PURE__ */ new Map();
3545
- for (const manifest of manifests) {
3546
- const current = manifestsByAppId.get(manifest.app_id);
3547
- if (current === void 0) {
3548
- manifestsByAppId.set(manifest.app_id, [manifest]);
3549
- } else {
3550
- current.push(manifest);
4122
+ /**
4123
+ * Lists all delegations for the current session's space.
4124
+ *
4125
+ * Returns both delegations created by the current user (as delegator)
4126
+ * and delegations granted to the current user (as delegatee).
4127
+ *
4128
+ * @returns Result containing an array of Delegations or an error
4129
+ *
4130
+ * @example
4131
+ * ```typescript
4132
+ * const result = await manager.list();
4133
+ * if (result.ok) {
4134
+ * for (const delegation of result.data) {
4135
+ * console.log(`${delegation.cid}: ${delegation.path} -> ${delegation.delegateDID}`);
4136
+ * }
4137
+ * }
4138
+ * ```
4139
+ */
4140
+ async list() {
4141
+ try {
4142
+ const response = await this.invokeOperation("", DelegationAction.LIST);
4143
+ if (!response.ok) {
4144
+ const errorText = await response.text();
4145
+ return {
4146
+ ok: false,
4147
+ error: createError(
4148
+ DelegationErrorCodes.NETWORK_ERROR,
4149
+ `Failed to list delegations: ${response.status} - ${errorText}`,
4150
+ void 0,
4151
+ { status: response.status }
4152
+ )
4153
+ };
4154
+ }
4155
+ const data = await response.json();
4156
+ const delegations = data.map((item) => ({
4157
+ cid: item.cid,
4158
+ delegateDID: item.delegateDID,
4159
+ delegatorDID: item.delegatorDID,
4160
+ spaceId: item.spaceId,
4161
+ path: item.path,
4162
+ actions: item.actions,
4163
+ expiry: new Date(item.expiry),
4164
+ isRevoked: item.isRevoked,
4165
+ createdAt: item.createdAt ? new Date(item.createdAt) : void 0,
4166
+ parentCid: item.parentCid,
4167
+ allowSubDelegation: item.allowSubDelegation
4168
+ }));
4169
+ return { ok: true, data: delegations };
4170
+ } catch (error) {
4171
+ if (error instanceof Error && error.name === "AbortError") {
4172
+ return {
4173
+ ok: false,
4174
+ error: createError(
4175
+ DelegationErrorCodes.ABORTED,
4176
+ "Request aborted",
4177
+ error
4178
+ )
4179
+ };
4180
+ }
4181
+ return {
4182
+ ok: false,
4183
+ error: createError(
4184
+ DelegationErrorCodes.NETWORK_ERROR,
4185
+ `Network error during delegation list: ${String(error)}`,
4186
+ error instanceof Error ? error : void 0
4187
+ )
4188
+ };
3551
4189
  }
3552
4190
  }
3553
- const registryRecords = includeAccountRegistryPermissions ? [...manifestsByAppId.entries()].map(([app_id, appManifests]) => ({
3554
- key: `${ACCOUNT_REGISTRY_PATH}${app_id}`,
3555
- app_id,
3556
- manifests: appManifests.map((manifest) => ({
3557
- ...manifest,
3558
- permissions: manifest.permissions?.map(clonePermissionEntry)
3559
- }))
3560
- })) : [];
3561
- return {
3562
- manifests,
3563
- resources: resourcesWithImplicitCapabilities,
3564
- delegationTargets,
3565
- registryRecords,
3566
- expiryMs: Math.max(...resolved.map((entry) => entry.expiryMs)),
3567
- includePublicSpace: resolved.some((entry) => entry.includePublicSpace)
3568
- };
3569
- }
3570
- function resourceCapabilitiesToAbilitiesMap(resources) {
3571
- const out = {};
3572
- for (const r of resources) {
3573
- const shortService = SERVICE_LONG_TO_SHORT[r.service];
3574
- if (shortService === void 0) {
3575
- throw new ManifestValidationError(
3576
- `unknown service '${r.service}' \u2014 no short-form mapping. Known services: ${Object.keys(SERVICE_LONG_TO_SHORT).join(", ")}`
4191
+ /**
4192
+ * Gets the full delegation chain for a given delegation.
4193
+ *
4194
+ * Returns the chain of delegations from the root (original delegator)
4195
+ * to the specified delegation, including all intermediate sub-delegations.
4196
+ *
4197
+ * @param cid - The CID of the delegation to get the chain for
4198
+ * @returns Result containing the DelegationChain or an error
4199
+ *
4200
+ * @example
4201
+ * ```typescript
4202
+ * const result = await manager.getChain("bafy...");
4203
+ * if (result.ok) {
4204
+ * console.log("Chain length:", result.data.length);
4205
+ * for (const delegation of result.data) {
4206
+ * console.log(`- ${delegation.delegatorDID} -> ${delegation.delegateDID}`);
4207
+ * }
4208
+ * }
4209
+ * ```
4210
+ */
4211
+ async getChain(cid) {
4212
+ if (!cid) {
4213
+ return {
4214
+ ok: false,
4215
+ error: createError(
4216
+ DelegationErrorCodes.INVALID_INPUT,
4217
+ "cid is required"
4218
+ )
4219
+ };
4220
+ }
4221
+ try {
4222
+ const body = JSON.stringify({ cid, includeChain: true });
4223
+ const response = await this.invokeOperation(
4224
+ cid,
4225
+ DelegationAction.GET,
4226
+ body
3577
4227
  );
4228
+ if (!response.ok) {
4229
+ const errorText = await response.text();
4230
+ if (response.status === 404) {
4231
+ return {
4232
+ ok: false,
4233
+ error: createError(
4234
+ DelegationErrorCodes.NOT_FOUND,
4235
+ `Delegation not found: ${cid}`
4236
+ )
4237
+ };
4238
+ }
4239
+ return {
4240
+ ok: false,
4241
+ error: createError(
4242
+ DelegationErrorCodes.NETWORK_ERROR,
4243
+ `Failed to get delegation chain: ${response.status} - ${errorText}`,
4244
+ void 0,
4245
+ { status: response.status, cid }
4246
+ )
4247
+ };
4248
+ }
4249
+ const data = await response.json();
4250
+ const chain = data.chain.map((item) => ({
4251
+ cid: item.cid,
4252
+ delegateDID: item.delegateDID,
4253
+ delegatorDID: item.delegatorDID,
4254
+ spaceId: item.spaceId,
4255
+ path: item.path,
4256
+ actions: item.actions,
4257
+ expiry: new Date(item.expiry),
4258
+ isRevoked: item.isRevoked,
4259
+ createdAt: item.createdAt ? new Date(item.createdAt) : void 0,
4260
+ parentCid: item.parentCid,
4261
+ allowSubDelegation: item.allowSubDelegation
4262
+ }));
4263
+ return { ok: true, data: chain };
4264
+ } catch (error) {
4265
+ if (error instanceof Error && error.name === "AbortError") {
4266
+ return {
4267
+ ok: false,
4268
+ error: createError(
4269
+ DelegationErrorCodes.ABORTED,
4270
+ "Request aborted",
4271
+ error
4272
+ )
4273
+ };
4274
+ }
4275
+ return {
4276
+ ok: false,
4277
+ error: createError(
4278
+ DelegationErrorCodes.NETWORK_ERROR,
4279
+ `Network error during chain retrieval: ${String(error)}`,
4280
+ error instanceof Error ? error : void 0
4281
+ )
4282
+ };
3578
4283
  }
3579
- if (out[shortService] === void 0) {
3580
- out[shortService] = {};
4284
+ }
4285
+ /**
4286
+ * Checks if the current session has permission for a given path and action.
4287
+ *
4288
+ * This can be used to verify permissions before attempting an operation,
4289
+ * or to implement custom access control logic.
4290
+ *
4291
+ * @param path - The resource path to check
4292
+ * @param action - The action to check (e.g., "tinycloud.kv/get")
4293
+ * @returns Result containing a boolean indicating permission or an error
4294
+ *
4295
+ * @example
4296
+ * ```typescript
4297
+ * const result = await manager.checkPermission("documents/private/", "tinycloud.kv/put");
4298
+ * if (result.ok && result.data) {
4299
+ * console.log("Permission granted");
4300
+ * } else {
4301
+ * console.log("Permission denied");
4302
+ * }
4303
+ * ```
4304
+ */
4305
+ async checkPermission(path, action) {
4306
+ if (!path) {
4307
+ return {
4308
+ ok: false,
4309
+ error: createError(
4310
+ DelegationErrorCodes.INVALID_INPUT,
4311
+ "path is required"
4312
+ )
4313
+ };
3581
4314
  }
3582
- const pathsMap = out[shortService];
3583
- const existing = pathsMap[r.path];
3584
- if (existing === void 0) {
3585
- pathsMap[r.path] = [...r.actions];
3586
- } else {
3587
- const seen = new Set(existing);
3588
- for (const action of r.actions) {
3589
- if (!seen.has(action)) {
3590
- existing.push(action);
3591
- seen.add(action);
4315
+ if (!action) {
4316
+ return {
4317
+ ok: false,
4318
+ error: createError(
4319
+ DelegationErrorCodes.INVALID_INPUT,
4320
+ "action is required"
4321
+ )
4322
+ };
4323
+ }
4324
+ try {
4325
+ const body = JSON.stringify({ path, action });
4326
+ const response = await this.invokeOperation(
4327
+ path,
4328
+ DelegationAction.CHECK,
4329
+ body
4330
+ );
4331
+ if (!response.ok) {
4332
+ if (response.status === 403) {
4333
+ return { ok: true, data: false };
3592
4334
  }
4335
+ const errorText = await response.text();
4336
+ return {
4337
+ ok: false,
4338
+ error: createError(
4339
+ DelegationErrorCodes.NETWORK_ERROR,
4340
+ `Failed to check permission: ${response.status} - ${errorText}`,
4341
+ void 0,
4342
+ { status: response.status, path, action }
4343
+ )
4344
+ };
3593
4345
  }
4346
+ const data = await response.json();
4347
+ return { ok: true, data: data.allowed };
4348
+ } catch (error) {
4349
+ if (error instanceof Error && error.name === "AbortError") {
4350
+ return {
4351
+ ok: false,
4352
+ error: createError(
4353
+ DelegationErrorCodes.ABORTED,
4354
+ "Request aborted",
4355
+ error
4356
+ )
4357
+ };
4358
+ }
4359
+ return {
4360
+ ok: false,
4361
+ error: createError(
4362
+ DelegationErrorCodes.NETWORK_ERROR,
4363
+ `Network error during permission check: ${String(error)}`,
4364
+ error instanceof Error ? error : void 0
4365
+ )
4366
+ };
3594
4367
  }
3595
4368
  }
3596
- return out;
3597
- }
3598
- function resourceCapabilitiesToSpaceAbilitiesMap(resources) {
3599
- const grouped = /* @__PURE__ */ new Map();
3600
- for (const resource of resources) {
3601
- const entries = grouped.get(resource.space);
3602
- if (entries === void 0) {
3603
- grouped.set(resource.space, [resource]);
3604
- } else {
3605
- entries.push(resource);
3606
- }
3607
- }
3608
- const out = {};
3609
- for (const [space, entries] of grouped.entries()) {
3610
- out[space] = resourceCapabilitiesToAbilitiesMap(entries);
3611
- }
3612
- return out;
3613
- }
3614
- function manifestAbilitiesUnion(resolved) {
3615
- const all = [...resolved.resources];
3616
- for (const delegate of resolved.additionalDelegates) {
3617
- for (const perm of delegate.permissions) {
3618
- all.push(perm);
3619
- }
4369
+ };
4370
+
4371
+ // src/delegations/SharingService.schema.ts
4372
+ import { z as z5 } from "zod";
4373
+ var EncodedShareDataSchema = z5.object({
4374
+ /** Private key in JWK format (must include d parameter) */
4375
+ key: JWKSchema.refine(
4376
+ (jwk) => typeof jwk.d === "string" && jwk.d.length > 0,
4377
+ { message: "JWK must include private key (d parameter)" }
4378
+ ),
4379
+ /** DID of the key */
4380
+ keyDid: z5.string().min(1, "keyDid is required"),
4381
+ /** The delegation granting access */
4382
+ delegation: DelegationSchema,
4383
+ /** Resource path this link grants access to */
4384
+ path: z5.string().min(1, "path is required"),
4385
+ /** TinyCloud host URL */
4386
+ host: z5.string().url("host must be a valid URL"),
4387
+ /** Space ID */
4388
+ spaceId: z5.string().min(1, "spaceId is required"),
4389
+ /** Schema version (must be 1) */
4390
+ version: z5.literal(1)
4391
+ });
4392
+ var ReceiveOptionsSchema = z5.object({
4393
+ /**
4394
+ * Whether to automatically create a sub-delegation to the current session key.
4395
+ * Default: true
4396
+ */
4397
+ autoSubdelegate: z5.boolean().optional(),
4398
+ /**
4399
+ * Whether to use the current session key for operations (requires autoSubdelegate).
4400
+ * Default: true
4401
+ */
4402
+ useSessionKey: z5.boolean().optional(),
4403
+ /**
4404
+ * Ingestion options passed to CapabilityKeyRegistry.
4405
+ */
4406
+ ingestOptions: IngestOptionsSchema.optional()
4407
+ });
4408
+ var SharingServiceConfigSchema = z5.object({
4409
+ /** TinyCloud host URLs */
4410
+ hosts: z5.array(z5.string().url()).min(1, "At least one host URL is required"),
4411
+ /**
4412
+ * Active session for authentication.
4413
+ * Required for generate(), optional for receive().
4414
+ */
4415
+ session: z5.unknown().refine(
4416
+ (val) => val === void 0 || val !== null && typeof val === "object",
4417
+ { message: "Expected a ServiceSession object or undefined" }
4418
+ ).optional(),
4419
+ /** Platform-specific invoke function */
4420
+ invoke: z5.unknown().refine((val) => typeof val === "function", {
4421
+ message: "Expected an invoke function"
4422
+ }),
4423
+ /** Optional custom fetch implementation */
4424
+ fetch: z5.unknown().refine(
4425
+ (val) => val === void 0 || typeof val === "function",
4426
+ { message: "Expected a fetch function or undefined" }
4427
+ ).optional(),
4428
+ /** Key provider for cryptographic operations */
4429
+ keyProvider: KeyProviderSchema,
4430
+ /** Capability key registry for key/delegation management */
4431
+ registry: z5.unknown().refine(
4432
+ (val) => val !== null && typeof val === "object",
4433
+ { message: "Expected an ICapabilityKeyRegistry object" }
4434
+ ),
4435
+ /**
4436
+ * Delegation manager for creating delegations.
4437
+ * Required for generate(), optional for receive().
4438
+ */
4439
+ delegationManager: z5.unknown().refine(
4440
+ (val) => val === void 0 || val !== null && typeof val === "object",
4441
+ { message: "Expected a DelegationManager object or undefined" }
4442
+ ).optional(),
4443
+ /** Factory for creating KV service instances */
4444
+ createKVService: z5.unknown().refine(
4445
+ (val) => typeof val === "function",
4446
+ { message: "Expected a createKVService factory function" }
4447
+ ),
4448
+ /** Base URL for sharing links (e.g., "https://share.myapp.com") */
4449
+ baseUrl: z5.string().optional(),
4450
+ /**
4451
+ * Custom delegation creation function.
4452
+ */
4453
+ createDelegation: z5.unknown().refine((val) => val === void 0 || typeof val === "function", {
4454
+ message: "Expected a createDelegation function or undefined"
4455
+ }).optional(),
4456
+ /**
4457
+ * WASM function for client-side delegation creation.
4458
+ */
4459
+ createDelegationWasm: z5.unknown().refine((val) => val === void 0 || typeof val === "function", {
4460
+ message: "Expected a createDelegationWasm function or undefined"
4461
+ }).optional(),
4462
+ /**
4463
+ * Path prefix for KV operations.
4464
+ */
4465
+ pathPrefix: z5.string().optional(),
4466
+ /**
4467
+ * Session expiry time.
4468
+ */
4469
+ sessionExpiry: z5.date().optional(),
4470
+ /**
4471
+ * Callback to create a DIRECT delegation from wallet to share key.
4472
+ * This is the preferred method for long-lived share links because it
4473
+ * bypasses the session delegation chain entirely.
4474
+ */
4475
+ onRootDelegationNeeded: z5.unknown().refine((val) => val === void 0 || typeof val === "function", {
4476
+ message: "Expected an onRootDelegationNeeded function or undefined"
4477
+ }).optional()
4478
+ });
4479
+ function validateEncodedShareData(data) {
4480
+ const result = EncodedShareDataSchema.safeParse(data);
4481
+ if (!result.success) {
4482
+ return {
4483
+ ok: false,
4484
+ error: {
4485
+ code: DelegationErrorCodes.VALIDATION_ERROR,
4486
+ message: `Invalid share data: ${result.error.message}`,
4487
+ service: "delegation",
4488
+ meta: { issues: result.error.issues }
4489
+ }
4490
+ };
3620
4491
  }
3621
- return resourceCapabilitiesToAbilitiesMap(all);
4492
+ return { ok: true, data: result.data };
3622
4493
  }
3623
4494
 
3624
4495
  // src/delegations/SharingService.ts
@@ -3799,13 +4670,13 @@ var SharingService = class {
3799
4670
  )
3800
4671
  };
3801
4672
  }
3802
- } catch (err5) {
4673
+ } catch (err6) {
3803
4674
  return {
3804
4675
  ok: false,
3805
4676
  error: createError2(
3806
4677
  DelegationErrorCodes.CREATION_FAILED,
3807
- `Failed to generate session key for share: ${err5 instanceof Error ? err5.message : String(err5)}`,
3808
- err5 instanceof Error ? err5 : void 0
4678
+ `Failed to generate session key for share: ${err6 instanceof Error ? err6.message : String(err6)}`,
4679
+ err6 instanceof Error ? err6 : void 0
3809
4680
  )
3810
4681
  };
3811
4682
  }
@@ -3851,7 +4722,7 @@ var SharingService = class {
3851
4722
  }
3852
4723
  delegation = parsed;
3853
4724
  }
3854
- } catch (err5) {
4725
+ } catch (err6) {
3855
4726
  const fallbackResult = await this.handleSessionExtensionFallback(requestedExpiry);
3856
4727
  expiry = fallbackResult.expiry;
3857
4728
  const delegationResult = await this.createSessionDelegation(plainDID, fullPath, actions, expiry);
@@ -4049,13 +4920,13 @@ var SharingService = class {
4049
4920
  allowSubDelegation: true,
4050
4921
  createdAt: /* @__PURE__ */ new Date()
4051
4922
  };
4052
- } catch (err5) {
4923
+ } catch (err6) {
4053
4924
  return {
4054
4925
  ok: false,
4055
4926
  error: createError2(
4056
4927
  DelegationErrorCodes.CREATION_FAILED,
4057
- `Failed to create delegation via WASM: ${err5 instanceof Error ? err5.message : String(err5)}`,
4058
- err5 instanceof Error ? err5 : void 0
4928
+ `Failed to create delegation via WASM: ${err6 instanceof Error ? err6.message : String(err6)}`,
4929
+ err6 instanceof Error ? err6 : void 0
4059
4930
  )
4060
4931
  };
4061
4932
  }
@@ -4136,8 +5007,8 @@ var SharingService = class {
4136
5007
  let activeKey = keyInfo;
4137
5008
  if (autoSubdelegate && useSessionKey && this.session) {
4138
5009
  try {
4139
- } catch (err5) {
4140
- console.warn("Auto-subdelegation failed, using ingested key directly:", err5);
5010
+ } catch (err6) {
5011
+ console.warn("Auto-subdelegation failed, using ingested key directly:", err6);
4141
5012
  }
4142
5013
  }
4143
5014
  const authHeader = shareData.delegation.authHeader ?? `Bearer ${shareData.delegation.cid}`;
@@ -4235,26 +5106,26 @@ var SharingService = class {
4235
5106
  let jsonString;
4236
5107
  try {
4237
5108
  jsonString = base64UrlDecode(base64Data);
4238
- } catch (err5) {
5109
+ } catch (err6) {
4239
5110
  return {
4240
5111
  ok: false,
4241
5112
  error: createError2(
4242
5113
  DelegationErrorCodes.INVALID_TOKEN,
4243
- `Failed to decode base64 data: ${err5 instanceof Error ? err5.message : String(err5)}`,
4244
- err5 instanceof Error ? err5 : void 0
5114
+ `Failed to decode base64 data: ${err6 instanceof Error ? err6.message : String(err6)}`,
5115
+ err6 instanceof Error ? err6 : void 0
4245
5116
  )
4246
5117
  };
4247
5118
  }
4248
5119
  let parsed;
4249
5120
  try {
4250
5121
  parsed = JSON.parse(jsonString);
4251
- } catch (err5) {
5122
+ } catch (err6) {
4252
5123
  return {
4253
5124
  ok: false,
4254
5125
  error: createError2(
4255
5126
  DelegationErrorCodes.INVALID_TOKEN,
4256
- `Failed to parse share data JSON: ${err5 instanceof Error ? err5.message : String(err5)}`,
4257
- err5 instanceof Error ? err5 : void 0
5127
+ `Failed to parse share data JSON: ${err6 instanceof Error ? err6.message : String(err6)}`,
5128
+ err6 instanceof Error ? err6 : void 0
4258
5129
  )
4259
5130
  };
4260
5131
  }
@@ -4281,8 +5152,8 @@ function createSharingService(config) {
4281
5152
  }
4282
5153
 
4283
5154
  // src/authorization/CapabilityKeyRegistry.ts
4284
- import { ok as ok3, err as err3, serviceError as serviceError3 } from "@tinycloud/sdk-services";
4285
- var SERVICE_NAME2 = "capability-key-registry";
5155
+ import { ok as ok4, err as err4, serviceError as serviceError4 } from "@tinycloud/sdk-services";
5156
+ var SERVICE_NAME3 = "capability-key-registry";
4286
5157
  var CapabilityKeyRegistryErrorCodes = {
4287
5158
  /** Key not found in registry */
4288
5159
  KEY_NOT_FOUND: "KEY_NOT_FOUND",
@@ -4499,11 +5370,11 @@ var CapabilityKeyRegistry = class {
4499
5370
  revokeDelegation(cid) {
4500
5371
  const stored = this.store.byCid.get(cid);
4501
5372
  if (!stored) {
4502
- return err3(
4503
- serviceError3(
5373
+ return err4(
5374
+ serviceError4(
4504
5375
  CapabilityKeyRegistryErrorCodes.KEY_NOT_FOUND,
4505
5376
  `Delegation not found: ${cid}`,
4506
- SERVICE_NAME2
5377
+ SERVICE_NAME3
4507
5378
  )
4508
5379
  );
4509
5380
  }
@@ -4522,7 +5393,7 @@ var CapabilityKeyRegistry = class {
4522
5393
  }
4523
5394
  }
4524
5395
  }
4525
- return ok3(void 0);
5396
+ return ok4(void 0);
4526
5397
  }
4527
5398
  // ===========================================================================
4528
5399
  // Search
@@ -4751,8 +5622,8 @@ async function checkNodeInfo(host, sdkProtocol, fetchFn = globalThis.fetch.bind(
4751
5622
  response = await fetchFn(`${host}/info`, {
4752
5623
  signal: AbortSignal.timeout(5e3)
4753
5624
  });
4754
- } catch (err5) {
4755
- throw new VersionCheckError(host, err5);
5625
+ } catch (err6) {
5626
+ throw new VersionCheckError(host, err6);
4756
5627
  }
4757
5628
  if (!response.ok) {
4758
5629
  throw new VersionCheckError(host);
@@ -5284,6 +6155,7 @@ function parseRecapCapabilities(parseWasm, siwe) {
5284
6155
  export {
5285
6156
  ACCOUNT_REGISTRY_PATH,
5286
6157
  ACCOUNT_REGISTRY_SPACE,
6158
+ AccountService,
5287
6159
  AutoApproveSpaceCreationHandler,
5288
6160
  CapabilityKeyRegistry,
5289
6161
  CapabilityKeyRegistryErrorCodes,
@@ -5389,7 +6261,7 @@ export {
5389
6261
  utf8Decode as encryptionUtf8Decode,
5390
6262
  utf8Encode as encryptionUtf8Encode,
5391
6263
  ensureNetworkUsableForDecrypt,
5392
- err4 as err,
6264
+ err5 as err,
5393
6265
  expandActionShortNames,
5394
6266
  expandPermissionEntries,
5395
6267
  expandPermissionEntry,
@@ -5410,7 +6282,7 @@ export {
5410
6282
  multiaddrToHttpUrl,
5411
6283
  networkDiscoveryKey,
5412
6284
  normalizeDefaults,
5413
- ok4 as ok,
6285
+ ok5 as ok,
5414
6286
  openWrappedKey,
5415
6287
  parseCanonicalNetworkId,
5416
6288
  parseExpiry,
@@ -5428,7 +6300,7 @@ export {
5428
6300
  resolveTinyCloudHosts,
5429
6301
  resourceCapabilitiesToAbilitiesMap,
5430
6302
  resourceCapabilitiesToSpaceAbilitiesMap,
5431
- serviceError4 as serviceError,
6303
+ serviceError5 as serviceError,
5432
6304
  signLocationRecord,
5433
6305
  submitHostDelegation,
5434
6306
  validateClientSession,