@tinycloud/node-sdk 2.2.0-beta.0 → 2.2.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core.js CHANGED
@@ -207,7 +207,8 @@ import {
207
207
  DEFAULT_MANIFEST_SPACE,
208
208
  composeManifestRequest,
209
209
  resourceCapabilitiesToAbilitiesMap,
210
- resourceCapabilitiesToSpaceAbilitiesMap
210
+ resourceCapabilitiesToSpaceAbilitiesMap,
211
+ resolveTinyCloudHosts
211
212
  } from "@tinycloud/sdk-core";
212
213
 
213
214
  // src/authorization/strategies.ts
@@ -270,9 +271,9 @@ var NodeUserAuthorization = class {
270
271
  this.sessionExpirationMs = config.sessionExpirationMs ?? 60 * 60 * 1e3;
271
272
  this.autoCreateSpace = config.autoCreateSpace ?? false;
272
273
  this.spaceCreationHandler = config.spaceCreationHandler;
273
- this.tinycloudHosts = config.tinycloudHosts ?? [
274
- "https://node.tinycloud.xyz"
275
- ];
274
+ this.tinycloudHosts = config.tinycloudHosts;
275
+ this.tinycloudRegistryUrl = config.tinycloudRegistryUrl;
276
+ this.tinycloudFallbackHosts = config.tinycloudFallbackHosts;
276
277
  this.enablePublicSpace = config.enablePublicSpace ?? true;
277
278
  this.nonce = config.nonce;
278
279
  this.siweConfig = config.siweConfig;
@@ -293,6 +294,9 @@ var NodeUserAuthorization = class {
293
294
  get capabilityRequest() {
294
295
  return this.getCapabilityRequest();
295
296
  }
297
+ get hosts() {
298
+ return this.tinycloudHosts ? [...this.tinycloudHosts] : [];
299
+ }
296
300
  /**
297
301
  * Install or replace the stored manifest. Takes effect on the next
298
302
  * `signIn()` call — the current session (if any) is not touched.
@@ -317,6 +321,26 @@ var NodeUserAuthorization = class {
317
321
  get tinyCloudSession() {
318
322
  return this._tinyCloudSession;
319
323
  }
324
+ async resolveTinyCloudHostsForSignIn(address, chainId) {
325
+ if (this.tinycloudHosts && this.tinycloudHosts.length > 0) {
326
+ return;
327
+ }
328
+ const subject = `did:pkh:eip155:${chainId}:${address}`;
329
+ const resolved = await resolveTinyCloudHosts(subject, {
330
+ registryUrl: this.tinycloudRegistryUrl,
331
+ fallbackHosts: this.tinycloudFallbackHosts
332
+ });
333
+ this.tinycloudHosts = resolved.hosts;
334
+ }
335
+ requireTinyCloudHosts() {
336
+ if (!this.tinycloudHosts || this.tinycloudHosts.length === 0) {
337
+ throw new Error("TinyCloud hosts have not been resolved. Call signIn() first.");
338
+ }
339
+ return this.tinycloudHosts;
340
+ }
341
+ get primaryTinyCloudHost() {
342
+ return this.requireTinyCloudHosts()[0];
343
+ }
320
344
  get nodeFeatures() {
321
345
  return this._nodeFeatures;
322
346
  }
@@ -448,7 +472,7 @@ var NodeUserAuthorization = class {
448
472
  if (!this._tinyCloudSession || !this._address || !this._chainId) {
449
473
  throw new Error("Must be signed in to host space");
450
474
  }
451
- const host = this.tinycloudHosts[0];
475
+ const host = this.primaryTinyCloudHost;
452
476
  const spaceId = targetSpaceId ?? this._tinyCloudSession.spaceId;
453
477
  const peerId = await fetchPeerId(host, spaceId);
454
478
  const siwe = this.wasm.generateHostSIWEMessage({
@@ -490,7 +514,7 @@ var NodeUserAuthorization = class {
490
514
  if (!this._tinyCloudSession) {
491
515
  throw new Error("Must be signed in to ensure space exists");
492
516
  }
493
- const host = this.tinycloudHosts[0];
517
+ const host = this.primaryTinyCloudHost;
494
518
  const primarySpaceId = this._tinyCloudSession.spaceId;
495
519
  const result = await activateSessionWithHost(
496
520
  host,
@@ -599,6 +623,7 @@ var NodeUserAuthorization = class {
599
623
  this._chainId = await this.signer.getChainId();
600
624
  const address = this.wasm.ensureEip55(this._address);
601
625
  const chainId = this._chainId;
626
+ await this.resolveTinyCloudHostsForSignIn(address, chainId);
602
627
  const keyId = `session-${Date.now()}`;
603
628
  this.sessionManager.renameSessionKeyId("default", keyId);
604
629
  const jwkString = this.sessionManager.jwk(keyId);
@@ -677,7 +702,7 @@ var NodeUserAuthorization = class {
677
702
  this._address = address;
678
703
  this._chainId = chainId;
679
704
  const nodeInfo = await checkNodeInfo(
680
- this.tinycloudHosts[0],
705
+ this.primaryTinyCloudHost,
681
706
  this.wasm.protocolVersion()
682
707
  );
683
708
  this._nodeFeatures = nodeInfo.features;
@@ -793,6 +818,7 @@ var NodeUserAuthorization = class {
793
818
  });
794
819
  const address = this.wasm.ensureEip55(await this.signer.getAddress());
795
820
  const chainId = await this.signer.getChainId();
821
+ await this.resolveTinyCloudHostsForSignIn(address, chainId);
796
822
  const clientSession = {
797
823
  address,
798
824
  walletAddress: address,
@@ -842,7 +868,7 @@ var NodeUserAuthorization = class {
842
868
  this._address = address;
843
869
  this._chainId = chainId;
844
870
  const nodeInfo = await checkNodeInfo(
845
- this.tinycloudHosts[0],
871
+ this.primaryTinyCloudHost,
846
872
  this.wasm.protocolVersion()
847
873
  );
848
874
  this._nodeFeatures = nodeInfo.features;
@@ -933,6 +959,7 @@ import {
933
959
  DuckDbService as DuckDbService2,
934
960
  HooksService as HooksService2,
935
961
  DataVaultService,
962
+ SecretsService,
936
963
  createVaultCrypto,
937
964
  ServiceContext as ServiceContext2,
938
965
  SilentNotificationHandler,
@@ -945,7 +972,7 @@ import {
945
972
  ACCOUNT_REGISTRY_SPACE,
946
973
  PermissionNotInManifestError,
947
974
  SessionExpiredError,
948
- expandActionShortNames,
975
+ expandPermissionEntries as expandPermissionEntriesCore,
949
976
  isCapabilitySubset,
950
977
  parseRecapCapabilities,
951
978
  SERVICE_LONG_TO_SHORT
@@ -1033,6 +1060,24 @@ var DelegatedAccess = class {
1033
1060
  get hooks() {
1034
1061
  return this._hooks;
1035
1062
  }
1063
+ /**
1064
+ * Export the handles needed to rehydrate this activated delegation via
1065
+ * `TinyCloudNode.restoreSession(...)` in another process or after a
1066
+ * restart.
1067
+ *
1068
+ * See `RestorableSession` for lifetime caveats.
1069
+ */
1070
+ get restorable() {
1071
+ return {
1072
+ delegationHeader: this.session.delegationHeader,
1073
+ delegationCid: this.session.delegationCid,
1074
+ spaceId: this.session.spaceId,
1075
+ jwk: this.session.jwk,
1076
+ verificationMethod: this.session.verificationMethod,
1077
+ address: this.session.address,
1078
+ chainId: this.session.chainId
1079
+ };
1080
+ }
1036
1081
  };
1037
1082
 
1038
1083
  // src/keys/WasmKeyProvider.ts
@@ -1173,6 +1218,152 @@ function extractSiweExpiration(siwe) {
1173
1218
  return d;
1174
1219
  }
1175
1220
 
1221
+ // src/NodeSecretsService.ts
1222
+ import {
1223
+ ErrorCodes,
1224
+ resolveSecretPath,
1225
+ resolveManifest
1226
+ } from "@tinycloud/sdk-core";
1227
+ var SECRETS_SPACE = "secrets";
1228
+ function ok() {
1229
+ return { ok: true, data: void 0 };
1230
+ }
1231
+ function secretsError(code, message, cause) {
1232
+ return {
1233
+ ok: false,
1234
+ error: {
1235
+ code,
1236
+ service: "secrets",
1237
+ message,
1238
+ ...cause ? { cause } : {}
1239
+ }
1240
+ };
1241
+ }
1242
+ function displayActionUrn(action) {
1243
+ return action === "put" ? "tinycloud.vault/write" : "tinycloud.vault/delete";
1244
+ }
1245
+ function kvActionUrn(action) {
1246
+ return `tinycloud.kv/${action}`;
1247
+ }
1248
+ function vaultMutationAction(action) {
1249
+ return action === "put" ? "write" : "delete";
1250
+ }
1251
+ function secretPermissionEntries(name, options, action) {
1252
+ const secretPath = resolveSecretPath(name, options);
1253
+ return [
1254
+ {
1255
+ service: "tinycloud.vault",
1256
+ space: SECRETS_SPACE,
1257
+ path: secretPath.vaultKey,
1258
+ actions: [vaultMutationAction(action)],
1259
+ skipPrefix: true
1260
+ }
1261
+ ];
1262
+ }
1263
+ function isSecretsSpace(space) {
1264
+ return space === SECRETS_SPACE || space.endsWith(`:${SECRETS_SPACE}`);
1265
+ }
1266
+ var NodeSecretsService = class {
1267
+ constructor(config) {
1268
+ this.config = config;
1269
+ this.shouldRestoreUnlock = false;
1270
+ }
1271
+ get vault() {
1272
+ return this.service.vault;
1273
+ }
1274
+ get isUnlocked() {
1275
+ return this.service.isUnlocked;
1276
+ }
1277
+ async unlock(signer) {
1278
+ const effectiveSigner = signer ?? this.config.getUnlockSigner?.();
1279
+ if (effectiveSigner !== void 0) {
1280
+ this.unlockSigner = effectiveSigner;
1281
+ }
1282
+ const result = await this.service.unlock(effectiveSigner);
1283
+ if (result.ok) {
1284
+ this.shouldRestoreUnlock = true;
1285
+ }
1286
+ return result;
1287
+ }
1288
+ lock() {
1289
+ this.shouldRestoreUnlock = false;
1290
+ this.service.lock();
1291
+ }
1292
+ get(name, options) {
1293
+ return options === void 0 ? this.service.get(name) : this.service.get(name, options);
1294
+ }
1295
+ async put(name, value, options) {
1296
+ const permission = await this.ensureMutationPermission(name, options, "put");
1297
+ if (!permission.ok) return permission;
1298
+ return options === void 0 ? this.service.put(name, value) : this.service.put(name, value, options);
1299
+ }
1300
+ async delete(name, options) {
1301
+ const permission = await this.ensureMutationPermission(name, options, "del");
1302
+ if (!permission.ok) return permission;
1303
+ return options === void 0 ? this.service.delete(name) : this.service.delete(name, options);
1304
+ }
1305
+ list(options) {
1306
+ return options === void 0 ? this.service.list() : this.service.list(options);
1307
+ }
1308
+ get service() {
1309
+ return this.config.getService();
1310
+ }
1311
+ async ensureMutationPermission(name, options, action) {
1312
+ let permissionEntries;
1313
+ try {
1314
+ permissionEntries = secretPermissionEntries(name, options, action);
1315
+ } catch (error) {
1316
+ return secretsError(
1317
+ ErrorCodes.INVALID_INPUT,
1318
+ error instanceof Error ? error.message : String(error),
1319
+ error instanceof Error ? error : void 0
1320
+ );
1321
+ }
1322
+ if (this.hasMutationPermission(name, options, action)) {
1323
+ return ok();
1324
+ }
1325
+ if (!this.config.canEscalate()) {
1326
+ return secretsError(
1327
+ ErrorCodes.PERMISSION_DENIED,
1328
+ `Cannot autosign ${displayActionUrn(action)} for ${name}; TinyCloudNode needs wallet mode with a signer or privateKey.`
1329
+ );
1330
+ }
1331
+ try {
1332
+ await this.config.grantPermissions(permissionEntries);
1333
+ return this.restoreUnlockAfterEscalation();
1334
+ } catch (error) {
1335
+ return secretsError(
1336
+ ErrorCodes.PERMISSION_DENIED,
1337
+ error instanceof Error ? error.message : `Autosign escalation for ${displayActionUrn(action)} on ${name} failed.`,
1338
+ error instanceof Error ? error : void 0
1339
+ );
1340
+ }
1341
+ }
1342
+ async restoreUnlockAfterEscalation() {
1343
+ if (!this.shouldRestoreUnlock) {
1344
+ return ok();
1345
+ }
1346
+ return this.service.unlock(this.unlockSigner);
1347
+ }
1348
+ hasMutationPermission(name, options, action) {
1349
+ const manifest = this.config.getManifest();
1350
+ if (manifest === void 0) {
1351
+ return false;
1352
+ }
1353
+ const manifests = Array.isArray(manifest) ? manifest : [manifest];
1354
+ const requiredAction = kvActionUrn(action);
1355
+ const secretPath = resolveSecretPath(name, options);
1356
+ return manifests.some((entry) => {
1357
+ const resolved = resolveManifest(entry);
1358
+ return ["keys", "vault"].every(
1359
+ (base) => resolved.resources.some(
1360
+ (resource) => resource.service === "tinycloud.kv" && isSecretsSpace(resource.space) && resource.path === secretPath.permissionPaths[base] && resource.actions.includes(requiredAction)
1361
+ )
1362
+ );
1363
+ });
1364
+ }
1365
+ };
1366
+
1176
1367
  // src/TinyCloudNode.ts
1177
1368
  var DEFAULT_HOST = "https://node.tinycloud.xyz";
1178
1369
  var _TinyCloudNode = class _TinyCloudNode {
@@ -1204,6 +1395,31 @@ var _TinyCloudNode = class _TinyCloudNode {
1204
1395
  this.auth = null;
1205
1396
  this.tc = null;
1206
1397
  this._chainId = 1;
1398
+ this.runtimePermissionGrants = [];
1399
+ this.invokeWithRuntimePermissions = (session, service, path, action, facts) => {
1400
+ return this.wasmBindings.invoke(
1401
+ this.selectInvocationSession(session, service, path, action),
1402
+ service,
1403
+ path,
1404
+ action,
1405
+ facts
1406
+ );
1407
+ };
1408
+ this.invokeAnyWithRuntimePermissions = (session, entries, facts) => {
1409
+ if (!this.wasmBindings.invokeAny) {
1410
+ throw new Error("WASM binding does not support invokeAny");
1411
+ }
1412
+ const grant = this.findGrantForOperations(
1413
+ entries.map((entry) => ({
1414
+ spaceId: entry.spaceId,
1415
+ service: this.invocationServiceName(entry.service),
1416
+ path: entry.path,
1417
+ action: entry.action
1418
+ }))
1419
+ );
1420
+ return this.wasmBindings.invokeAny(grant?.session ?? session, entries, facts);
1421
+ };
1422
+ this.explicitHost = config.host;
1207
1423
  this.config = {
1208
1424
  ...config,
1209
1425
  host: config.host ?? DEFAULT_HOST
@@ -1238,7 +1454,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1238
1454
  this._sharingService = new SharingService({
1239
1455
  hosts: [this.config.host],
1240
1456
  // session: undefined - not needed for receive()
1241
- invoke: this.wasmBindings.invoke,
1457
+ invoke: this.invokeWithRuntimePermissions,
1242
1458
  fetch: globalThis.fetch.bind(globalThis),
1243
1459
  keyProvider: this._keyProvider,
1244
1460
  registry: this._capabilityRegistry,
@@ -1285,7 +1501,6 @@ var _TinyCloudNode = class _TinyCloudNode {
1285
1501
  * @internal
1286
1502
  */
1287
1503
  setupAuth(config) {
1288
- const host = this.config.host;
1289
1504
  this.auth = new NodeUserAuthorization({
1290
1505
  signer: this.signer,
1291
1506
  signStrategy: { type: "auto-sign" },
@@ -1294,7 +1509,9 @@ var _TinyCloudNode = class _TinyCloudNode {
1294
1509
  domain: this.siweDomain,
1295
1510
  spacePrefix: config.prefix,
1296
1511
  sessionExpirationMs: config.sessionExpirationMs ?? 60 * 60 * 1e3,
1297
- tinycloudHosts: [host],
1512
+ tinycloudHosts: this.explicitHost ? [this.explicitHost] : void 0,
1513
+ tinycloudRegistryUrl: config.tinycloudRegistryUrl,
1514
+ tinycloudFallbackHosts: config.tinycloudFallbackHosts,
1298
1515
  autoCreateSpace: config.autoCreateSpace,
1299
1516
  enablePublicSpace: config.enablePublicSpace ?? true,
1300
1517
  spaceCreationHandler: config.spaceCreationHandler,
@@ -1305,9 +1522,15 @@ var _TinyCloudNode = class _TinyCloudNode {
1305
1522
  includeAccountRegistryPermissions: config.includeAccountRegistryPermissions
1306
1523
  });
1307
1524
  this.tc = new TinyCloud(this.auth, {
1308
- invokeAny: this.wasmBindings.invokeAny
1525
+ invokeAny: this.invokeAnyWithRuntimePermissions
1309
1526
  });
1310
1527
  }
1528
+ syncResolvedHostFromAuth() {
1529
+ const host = this.auth?.hosts[0];
1530
+ if (host) {
1531
+ this.config.host = host;
1532
+ }
1533
+ }
1311
1534
  /**
1312
1535
  * Install or replace the manifest that drives the SIWE recap at
1313
1536
  * sign-in. Takes effect on the next `signIn()` call — the current
@@ -1345,6 +1568,10 @@ var _TinyCloudNode = class _TinyCloudNode {
1345
1568
  get capabilityRequest() {
1346
1569
  return this.auth?.capabilityRequest;
1347
1570
  }
1571
+ get hosts() {
1572
+ const authHosts = this.auth?.hosts ?? [];
1573
+ return authHosts.length > 0 ? authHosts : [this.config.host];
1574
+ }
1348
1575
  /**
1349
1576
  * Get the primary identity DID for this user.
1350
1577
  * - If wallet connected and signed in: returns PKH DID (did:pkh:eip155:{chainId}:{address})
@@ -1415,8 +1642,14 @@ var _TinyCloudNode = class _TinyCloudNode {
1415
1642
  this._sql = void 0;
1416
1643
  this._duckdb = void 0;
1417
1644
  this._hooks = void 0;
1645
+ this._vault = void 0;
1646
+ this._baseSecrets = void 0;
1647
+ this._secrets = void 0;
1648
+ this._spaceService = void 0;
1418
1649
  this._serviceContext = void 0;
1650
+ this.runtimePermissionGrants = [];
1419
1651
  await this.tc.signIn(options);
1652
+ this.syncResolvedHostFromAuth();
1420
1653
  this.initializeServices();
1421
1654
  await this.writeManifestRegistryRecords();
1422
1655
  this.notificationHandler.success("Successfully signed in");
@@ -1436,7 +1669,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1436
1669
  throw new Error("Manifest registry write requires wallet mode");
1437
1670
  }
1438
1671
  const accountSpaceId = this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE);
1439
- await this.auth.hostOwnedSpace(accountSpaceId);
1672
+ await this.ensureOwnedSpaceHosted(accountSpaceId);
1440
1673
  const accountKV = this.spaces.get(accountSpaceId).kv;
1441
1674
  for (const record of request.registryRecords) {
1442
1675
  const result = await accountKV.put(record.key, {
@@ -1451,6 +1684,39 @@ var _TinyCloudNode = class _TinyCloudNode {
1451
1684
  }
1452
1685
  }
1453
1686
  }
1687
+ async ensureOwnedSpaceHosted(spaceId) {
1688
+ if (!this.auth) {
1689
+ throw new Error("Owned space hosting requires wallet mode");
1690
+ }
1691
+ const session = this.auth.tinyCloudSession;
1692
+ if (!session) {
1693
+ throw new Error("Owned space hosting requires an active session");
1694
+ }
1695
+ const host = this.hosts[0] ?? this.config.host;
1696
+ if (!host) {
1697
+ throw new Error("Owned space hosting requires a TinyCloud host");
1698
+ }
1699
+ const activation = await activateSessionWithHost2(host, session.delegationHeader);
1700
+ if (activation.success && !activation.skipped?.includes(spaceId)) {
1701
+ return;
1702
+ }
1703
+ if (!activation.success && activation.status !== 404) {
1704
+ throw new Error(
1705
+ `Failed to check owned space ${spaceId}: ${activation.error ?? activation.status}`
1706
+ );
1707
+ }
1708
+ const created = await this.auth.hostOwnedSpace(spaceId);
1709
+ if (!created) {
1710
+ throw new Error(`Failed to create owned space: ${spaceId}`);
1711
+ }
1712
+ await new Promise((resolve) => setTimeout(resolve, 100));
1713
+ const retry = await activateSessionWithHost2(host, session.delegationHeader);
1714
+ if (!retry.success || retry.skipped?.includes(spaceId)) {
1715
+ throw new Error(
1716
+ `Failed to activate session after creating owned space ${spaceId}: ${retry.error ?? "space was skipped"}`
1717
+ );
1718
+ }
1719
+ }
1454
1720
  /**
1455
1721
  * Restore a previously established session from stored delegation data.
1456
1722
  *
@@ -1466,7 +1732,12 @@ var _TinyCloudNode = class _TinyCloudNode {
1466
1732
  this._sql = void 0;
1467
1733
  this._duckdb = void 0;
1468
1734
  this._hooks = void 0;
1735
+ this._vault = void 0;
1736
+ this._baseSecrets = void 0;
1737
+ this._secrets = void 0;
1738
+ this._spaceService = void 0;
1469
1739
  this._serviceContext = void 0;
1740
+ this.runtimePermissionGrants = [];
1470
1741
  if (sessionData.address) {
1471
1742
  this._address = sessionData.address;
1472
1743
  }
@@ -1474,8 +1745,8 @@ var _TinyCloudNode = class _TinyCloudNode {
1474
1745
  this._chainId = sessionData.chainId;
1475
1746
  }
1476
1747
  this._serviceContext = new ServiceContext2({
1477
- invoke: this.wasmBindings.invoke,
1478
- invokeAny: this.wasmBindings.invokeAny,
1748
+ invoke: this.invokeWithRuntimePermissions,
1749
+ invokeAny: this.invokeAnyWithRuntimePermissions,
1479
1750
  fetch: globalThis.fetch.bind(globalThis),
1480
1751
  hosts: [this.config.host]
1481
1752
  });
@@ -1499,41 +1770,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1499
1770
  jwk: sessionData.jwk
1500
1771
  };
1501
1772
  this._serviceContext.setSession(serviceSession);
1502
- const wasm = this.wasmBindings;
1503
- const vaultCrypto = createVaultCrypto({
1504
- vault_encrypt: wasm.vault_encrypt,
1505
- vault_decrypt: wasm.vault_decrypt,
1506
- vault_derive_key: wasm.vault_derive_key,
1507
- vault_x25519_from_seed: wasm.vault_x25519_from_seed,
1508
- vault_x25519_dh: wasm.vault_x25519_dh,
1509
- vault_random_bytes: wasm.vault_random_bytes,
1510
- vault_sha256: wasm.vault_sha256
1511
- });
1512
- const self = this;
1513
- this._vault = new DataVaultService({
1514
- spaceId: sessionData.spaceId,
1515
- crypto: vaultCrypto,
1516
- tc: {
1517
- kv: this._kv,
1518
- ensurePublicSpace: async () => {
1519
- try {
1520
- await self.ensurePublicSpace();
1521
- return { ok: true, data: void 0 };
1522
- } catch (error) {
1523
- return { ok: false, error: { code: "STORAGE_ERROR", message: error instanceof Error ? error.message : String(error), service: "vault" } };
1524
- }
1525
- },
1526
- get publicKV() {
1527
- return self._publicKV ?? self.tc.publicKV;
1528
- },
1529
- readPublicSpace: (host, spaceId, key) => TinyCloud.readPublicSpace(host, spaceId, key),
1530
- makePublicSpaceId: TinyCloud.makePublicSpaceId,
1531
- did: this.did,
1532
- address: sessionData.address ?? this._address ?? "",
1533
- chainId: sessionData.chainId ?? this._chainId,
1534
- hosts: [this.config.host]
1535
- }
1536
- });
1773
+ this._vault = this.createVaultService(sessionData.spaceId, this._kv);
1537
1774
  this._vault.initialize(this._serviceContext);
1538
1775
  this._serviceContext.registerService("vault", this._vault);
1539
1776
  this.initializeV2Services(serviceSession);
@@ -1568,7 +1805,6 @@ var _TinyCloudNode = class _TinyCloudNode {
1568
1805
  throw new Error("Wallet already connected. Cannot connect another wallet.");
1569
1806
  }
1570
1807
  const prefix = options?.prefix ?? "default";
1571
- const host = this.config.host;
1572
1808
  if (!_TinyCloudNode.nodeDefaults) {
1573
1809
  throw new Error(
1574
1810
  "connectWallet() requires PrivateKeySigner. Use connectSigner() instead, or import from '@tinycloud/node-sdk' (not '/core') for automatic Node.js defaults."
@@ -1583,7 +1819,9 @@ var _TinyCloudNode = class _TinyCloudNode {
1583
1819
  domain: this.siweDomain,
1584
1820
  spacePrefix: prefix,
1585
1821
  sessionExpirationMs: this.config.sessionExpirationMs ?? 60 * 60 * 1e3,
1586
- tinycloudHosts: [host],
1822
+ tinycloudHosts: this.explicitHost ? [this.explicitHost] : void 0,
1823
+ tinycloudRegistryUrl: this.config.tinycloudRegistryUrl,
1824
+ tinycloudFallbackHosts: this.config.tinycloudFallbackHosts,
1587
1825
  autoCreateSpace: this.config.autoCreateSpace,
1588
1826
  enablePublicSpace: this.config.enablePublicSpace ?? true,
1589
1827
  spaceCreationHandler: this.config.spaceCreationHandler,
@@ -1594,7 +1832,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1594
1832
  includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
1595
1833
  });
1596
1834
  this.tc = new TinyCloud(this.auth, {
1597
- invokeAny: this.wasmBindings.invokeAny
1835
+ invokeAny: this.invokeAnyWithRuntimePermissions
1598
1836
  });
1599
1837
  this.config.prefix = prefix;
1600
1838
  }
@@ -1616,7 +1854,6 @@ var _TinyCloudNode = class _TinyCloudNode {
1616
1854
  throw new Error("Signer already connected. Cannot connect another signer.");
1617
1855
  }
1618
1856
  const prefix = options?.prefix ?? "default";
1619
- const host = this.config.host;
1620
1857
  this.signer = signer;
1621
1858
  this.auth = new NodeUserAuthorization({
1622
1859
  signer: this.signer,
@@ -1626,7 +1863,9 @@ var _TinyCloudNode = class _TinyCloudNode {
1626
1863
  domain: this.siweDomain,
1627
1864
  spacePrefix: prefix,
1628
1865
  sessionExpirationMs: this.config.sessionExpirationMs ?? 60 * 60 * 1e3,
1629
- tinycloudHosts: [host],
1866
+ tinycloudHosts: this.explicitHost ? [this.explicitHost] : void 0,
1867
+ tinycloudRegistryUrl: this.config.tinycloudRegistryUrl,
1868
+ tinycloudFallbackHosts: this.config.tinycloudFallbackHosts,
1630
1869
  autoCreateSpace: this.config.autoCreateSpace,
1631
1870
  enablePublicSpace: this.config.enablePublicSpace ?? true,
1632
1871
  spaceCreationHandler: this.config.spaceCreationHandler,
@@ -1637,7 +1876,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1637
1876
  includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
1638
1877
  });
1639
1878
  this.tc = new TinyCloud(this.auth, {
1640
- invokeAny: this.wasmBindings.invokeAny
1879
+ invokeAny: this.invokeAnyWithRuntimePermissions
1641
1880
  });
1642
1881
  this.config.prefix = prefix;
1643
1882
  }
@@ -1650,10 +1889,10 @@ var _TinyCloudNode = class _TinyCloudNode {
1650
1889
  if (!session) {
1651
1890
  return;
1652
1891
  }
1653
- this.tc.initializeServices(this.wasmBindings.invoke, [this.config.host]);
1892
+ this.tc.initializeServices(this.invokeWithRuntimePermissions, [this.config.host]);
1654
1893
  this._serviceContext = new ServiceContext2({
1655
- invoke: this.wasmBindings.invoke,
1656
- invokeAny: this.wasmBindings.invokeAny,
1894
+ invoke: this.invokeWithRuntimePermissions,
1895
+ invokeAny: this.invokeAnyWithRuntimePermissions,
1657
1896
  fetch: globalThis.fetch.bind(globalThis),
1658
1897
  hosts: [this.config.host]
1659
1898
  });
@@ -1683,6 +1922,28 @@ var _TinyCloudNode = class _TinyCloudNode {
1683
1922
  };
1684
1923
  this._serviceContext.setSession(serviceSession);
1685
1924
  this.tc.serviceContext.setSession(serviceSession);
1925
+ this._vault = this.createVaultService(session.spaceId, this._kv);
1926
+ this._vault.initialize(this._serviceContext);
1927
+ this._serviceContext.registerService("vault", this._vault);
1928
+ this.initializeV2Services(serviceSession);
1929
+ }
1930
+ createSpaceScopedKVService(spaceId) {
1931
+ const kvService = new KVService2({});
1932
+ if (this._serviceContext) {
1933
+ const spaceScopedContext = new ServiceContext2({
1934
+ invoke: this._serviceContext.invoke,
1935
+ fetch: this._serviceContext.fetch,
1936
+ hosts: this._serviceContext.hosts
1937
+ });
1938
+ const session = this._serviceContext.session;
1939
+ if (session) {
1940
+ spaceScopedContext.setSession({ ...session, spaceId });
1941
+ }
1942
+ kvService.initialize(spaceScopedContext);
1943
+ }
1944
+ return kvService;
1945
+ }
1946
+ createVaultService(spaceId, kv) {
1686
1947
  const wasm = this.wasmBindings;
1687
1948
  const vaultCrypto = createVaultCrypto({
1688
1949
  vault_encrypt: wasm.vault_encrypt,
@@ -1694,11 +1955,11 @@ var _TinyCloudNode = class _TinyCloudNode {
1694
1955
  vault_sha256: wasm.vault_sha256
1695
1956
  });
1696
1957
  const self = this;
1697
- this._vault = new DataVaultService({
1698
- spaceId: session.spaceId,
1958
+ return new DataVaultService({
1959
+ spaceId,
1699
1960
  crypto: vaultCrypto,
1700
1961
  tc: {
1701
- kv: this._kv,
1962
+ kv,
1702
1963
  ensurePublicSpace: async () => {
1703
1964
  try {
1704
1965
  await self.ensurePublicSpace();
@@ -1710,17 +1971,14 @@ var _TinyCloudNode = class _TinyCloudNode {
1710
1971
  get publicKV() {
1711
1972
  return self._publicKV ?? self.tc.publicKV;
1712
1973
  },
1713
- readPublicSpace: (host, spaceId, key) => TinyCloud.readPublicSpace(host, spaceId, key),
1974
+ readPublicSpace: (host, targetSpaceId, key) => TinyCloud.readPublicSpace(host, targetSpaceId, key),
1714
1975
  makePublicSpaceId: TinyCloud.makePublicSpaceId,
1715
1976
  did: this.did,
1716
- address: this._address,
1977
+ address: this._address ?? "",
1717
1978
  chainId: this._chainId,
1718
1979
  hosts: [this.config.host]
1719
1980
  }
1720
1981
  });
1721
- this._vault.initialize(this._serviceContext);
1722
- this._serviceContext.registerService("vault", this._vault);
1723
- this.initializeV2Services(serviceSession);
1724
1982
  }
1725
1983
  /**
1726
1984
  * Initialize the v2 delegation system services.
@@ -1804,7 +2062,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1804
2062
  this._delegationManager = new DelegationManager({
1805
2063
  hosts: [this.config.host],
1806
2064
  session: serviceSession,
1807
- invoke: this.wasmBindings.invoke,
2065
+ invoke: this.invokeWithRuntimePermissions,
1808
2066
  fetch: globalThis.fetch.bind(globalThis)
1809
2067
  });
1810
2068
  this._spaceService = new SpaceService({
@@ -1815,20 +2073,15 @@ var _TinyCloudNode = class _TinyCloudNode {
1815
2073
  capabilityRegistry: this._capabilityRegistry,
1816
2074
  userDid: this.did,
1817
2075
  createKVService: (spaceId) => {
1818
- const kvService = new KVService2({});
2076
+ return this.createSpaceScopedKVService(spaceId);
2077
+ },
2078
+ createVaultService: (spaceId) => {
2079
+ const kvService = this.createSpaceScopedKVService(spaceId);
2080
+ const vaultService = this.createVaultService(spaceId, kvService);
1819
2081
  if (this._serviceContext) {
1820
- const spaceScopedContext = new ServiceContext2({
1821
- invoke: this._serviceContext.invoke,
1822
- fetch: this._serviceContext.fetch,
1823
- hosts: this._serviceContext.hosts
1824
- });
1825
- const session = this._serviceContext.session;
1826
- if (session) {
1827
- spaceScopedContext.setSession({ ...session, spaceId });
1828
- }
1829
- kvService.initialize(spaceScopedContext);
2082
+ vaultService.initialize(this._serviceContext);
1830
2083
  }
1831
- return kvService;
2084
+ return vaultService;
1832
2085
  },
1833
2086
  // Enable space.delegations.create() via SIWE-based delegation
1834
2087
  createDelegation: async (params) => {
@@ -2065,6 +2318,33 @@ var _TinyCloudNode = class _TinyCloudNode {
2065
2318
  }
2066
2319
  return this._vault;
2067
2320
  }
2321
+ /**
2322
+ * App-facing secrets API backed by the `secrets` space vault.
2323
+ */
2324
+ get secrets() {
2325
+ if (!this._spaceService) {
2326
+ throw new Error("Not signed in. Call signIn() first.");
2327
+ }
2328
+ if (!this._secrets) {
2329
+ this._secrets = new NodeSecretsService({
2330
+ getService: () => this.getBaseSecrets(),
2331
+ getManifest: () => this.manifest,
2332
+ grantPermissions: (additional) => this.grantRuntimePermissions(additional),
2333
+ canEscalate: () => this.signer !== void 0 && this.tc !== void 0,
2334
+ getUnlockSigner: () => this.signer ?? void 0
2335
+ });
2336
+ }
2337
+ return this._secrets;
2338
+ }
2339
+ getBaseSecrets() {
2340
+ if (!this._spaceService) {
2341
+ throw new Error("Not signed in. Call signIn() first.");
2342
+ }
2343
+ if (!this._baseSecrets) {
2344
+ this._baseSecrets = new SecretsService(() => this.space("secrets").vault);
2345
+ }
2346
+ return this._baseSecrets;
2347
+ }
2068
2348
  /**
2069
2349
  * Hooks write stream subscription API.
2070
2350
  */
@@ -2135,6 +2415,171 @@ var _TinyCloudNode = class _TinyCloudNode {
2135
2415
  }
2136
2416
  };
2137
2417
  }
2418
+ /**
2419
+ * Check whether the current session or an approved runtime delegation covers
2420
+ * every requested permission.
2421
+ */
2422
+ hasRuntimePermissions(permissions) {
2423
+ const session = this.auth?.tinyCloudSession;
2424
+ if (!session || !Array.isArray(permissions) || permissions.length === 0) {
2425
+ return false;
2426
+ }
2427
+ const expanded = this.expandPermissionEntries(permissions);
2428
+ if (this.sessionCoversPermissionEntries(session, expanded)) {
2429
+ return true;
2430
+ }
2431
+ return this.findRuntimeGrantsForPermissionEntries(expanded, session).length > 0;
2432
+ }
2433
+ /**
2434
+ * Return installed runtime permission delegations. When `permissions` is
2435
+ * provided, only delegations currently covering those permissions are
2436
+ * returned. Base-session manifest permissions are not represented here.
2437
+ */
2438
+ getRuntimePermissionDelegations(permissions) {
2439
+ this.pruneExpiredRuntimePermissionGrants();
2440
+ if (permissions === void 0) {
2441
+ return this.runtimePermissionGrants.map((grant) => grant.delegation);
2442
+ }
2443
+ const session = this.auth?.tinyCloudSession;
2444
+ if (!session || !Array.isArray(permissions) || permissions.length === 0) {
2445
+ return [];
2446
+ }
2447
+ const expanded = this.expandPermissionEntries(permissions);
2448
+ return this.findRuntimeGrantsForPermissionEntries(expanded, session).map(
2449
+ (grant) => grant.delegation
2450
+ );
2451
+ }
2452
+ /**
2453
+ * Install a portable runtime permission delegation into this SDK instance so
2454
+ * matching service calls and downstream `delegateTo()` calls can use it.
2455
+ */
2456
+ async useRuntimeDelegation(delegation) {
2457
+ const session = this.auth?.tinyCloudSession;
2458
+ if (!session) {
2459
+ throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
2460
+ }
2461
+ if (delegation.expiry.getTime() <= Date.now()) {
2462
+ throw new SessionExpiredError(delegation.expiry);
2463
+ }
2464
+ const expectedDids = /* @__PURE__ */ new Set([session.verificationMethod, this.sessionDid]);
2465
+ if (!expectedDids.has(delegation.delegateDID)) {
2466
+ throw new Error(
2467
+ `Runtime delegation targets ${delegation.delegateDID} but this session key is ${session.verificationMethod}.`
2468
+ );
2469
+ }
2470
+ const targetHost = delegation.host ?? this.config.host;
2471
+ const activateResult = await activateSessionWithHost2(
2472
+ targetHost,
2473
+ delegation.delegationHeader
2474
+ );
2475
+ if (!activateResult.success) {
2476
+ throw new Error(
2477
+ `Failed to activate runtime permission delegation: ${activateResult.error}`
2478
+ );
2479
+ }
2480
+ this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
2481
+ (grant) => grant.delegation.cid !== delegation.cid
2482
+ );
2483
+ this.runtimePermissionGrants.push(
2484
+ this.runtimeGrantFromDelegation(delegation, session)
2485
+ );
2486
+ }
2487
+ /**
2488
+ * Store additional permissions as narrow delegations to the current session
2489
+ * key. Future service invocations automatically use a stored delegation when
2490
+ * its `(space, service, path, action)` covers the request.
2491
+ */
2492
+ async grantRuntimePermissions(permissions, options) {
2493
+ if (!Array.isArray(permissions) || permissions.length === 0) {
2494
+ throw new Error("grantRuntimePermissions requires a non-empty permissions array");
2495
+ }
2496
+ const session = this.auth?.tinyCloudSession;
2497
+ if (!session) {
2498
+ throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
2499
+ }
2500
+ const sessionExpiry = extractSiweExpiration(session.siwe);
2501
+ if (sessionExpiry !== void 0) {
2502
+ const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
2503
+ if (sessionExpiry.getTime() <= Date.now() + marginMs) {
2504
+ throw new SessionExpiredError(sessionExpiry);
2505
+ }
2506
+ }
2507
+ const expanded = this.expandPermissionEntries(permissions);
2508
+ if (this.sessionCoversPermissionEntries(session, expanded)) {
2509
+ return [];
2510
+ }
2511
+ const existingGrants = this.findRuntimeGrantsForPermissionEntries(expanded, session);
2512
+ if (existingGrants.length > 0) {
2513
+ return existingGrants.map((grant) => grant.delegation);
2514
+ }
2515
+ if (!this.signer) {
2516
+ throw new Error(
2517
+ "grantRuntimePermissions requires wallet mode with a signer or privateKey."
2518
+ );
2519
+ }
2520
+ const bySpace = /* @__PURE__ */ new Map();
2521
+ for (const entry of expanded) {
2522
+ const spaceId = this.resolvePermissionSpace(entry.space, session);
2523
+ const current = bySpace.get(spaceId) ?? [];
2524
+ current.push(entry);
2525
+ bySpace.set(spaceId, current);
2526
+ }
2527
+ const now = /* @__PURE__ */ new Date();
2528
+ const requestedExpiryMs = resolveExpiryMs(options?.expiry);
2529
+ let expiresAt = new Date(now.getTime() + requestedExpiryMs);
2530
+ if (sessionExpiry !== void 0 && sessionExpiry < expiresAt) {
2531
+ expiresAt = sessionExpiry;
2532
+ }
2533
+ const delegations = [];
2534
+ for (const [spaceId, entries] of bySpace) {
2535
+ const abilities = this.permissionsToAbilities(entries);
2536
+ const prepared = this.wasmBindings.prepareSession({
2537
+ abilities,
2538
+ address: this.wasmBindings.ensureEip55(session.address),
2539
+ chainId: session.chainId,
2540
+ domain: this.siweDomain,
2541
+ issuedAt: now.toISOString(),
2542
+ expirationTime: expiresAt.toISOString(),
2543
+ spaceId,
2544
+ jwk: session.jwk
2545
+ });
2546
+ const signature = await this.signer.signMessage(prepared.siwe);
2547
+ const delegatedSession = this.wasmBindings.completeSessionSetup({
2548
+ ...prepared,
2549
+ signature
2550
+ });
2551
+ const activateResult = await activateSessionWithHost2(
2552
+ this.config.host,
2553
+ delegatedSession.delegationHeader
2554
+ );
2555
+ if (!activateResult.success) {
2556
+ throw new Error(
2557
+ `Failed to activate runtime permission delegation: ${activateResult.error}`
2558
+ );
2559
+ }
2560
+ const delegation = this.runtimeDelegationFromSession(
2561
+ delegatedSession,
2562
+ entries,
2563
+ spaceId,
2564
+ session,
2565
+ expiresAt
2566
+ );
2567
+ this.runtimePermissionGrants.push({
2568
+ session: {
2569
+ delegationHeader: delegatedSession.delegationHeader,
2570
+ delegationCid: delegatedSession.delegationCid,
2571
+ spaceId,
2572
+ verificationMethod: session.verificationMethod,
2573
+ jwk: session.jwk
2574
+ },
2575
+ delegation,
2576
+ operations: this.permissionOperations(entries, spaceId),
2577
+ expiresAt
2578
+ });
2579
+ delegations.push(delegation);
2580
+ }
2581
+ return delegations;
2582
+ }
2138
2583
  /**
2139
2584
  * Get the DelegationManager for delegation CRUD operations.
2140
2585
  *
@@ -2203,6 +2648,12 @@ var _TinyCloudNode = class _TinyCloudNode {
2203
2648
  get spaceService() {
2204
2649
  return this.spaces;
2205
2650
  }
2651
+ /**
2652
+ * Get a Space object by short name or full URI.
2653
+ */
2654
+ space(nameOrUri) {
2655
+ return this.spaces.get(nameOrUri);
2656
+ }
2206
2657
  /**
2207
2658
  * Get the SharingService for creating and receiving v2 sharing links.
2208
2659
  *
@@ -2317,7 +2768,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2317
2768
  if (this._serviceContext) {
2318
2769
  const publicKV = new KVService2({ prefix: "" });
2319
2770
  const publicContext = new ServiceContext2({
2320
- invoke: this.wasmBindings.invoke,
2771
+ invoke: this.invokeWithRuntimePermissions,
2321
2772
  fetch: this._serviceContext.fetch,
2322
2773
  hosts: this._serviceContext.hosts
2323
2774
  });
@@ -2405,8 +2856,9 @@ var _TinyCloudNode = class _TinyCloudNode {
2405
2856
  * Issue a delegation using the capability-chain flow.
2406
2857
  *
2407
2858
  * When every requested permission is a subset of the current
2408
- * session's recap, the delegation is signed by the session key via
2409
- * WASM no wallet prompt. When at least one is NOT derivable, a
2859
+ * session's recap, or of one installed runtime permission delegation,
2860
+ * the delegation is signed by the session key via WASM no wallet
2861
+ * prompt. When at least one is NOT derivable, a
2410
2862
  * {@link PermissionNotInManifestError} is raised (carrying the
2411
2863
  * missing entries) so the caller can trigger an escalation flow
2412
2864
  * (e.g. `TinyCloudWeb.requestPermissions`). Passing
@@ -2456,10 +2908,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2456
2908
  "delegateTo requires a non-empty permissions array"
2457
2909
  );
2458
2910
  }
2459
- const expandedEntries = permissions.map((entry) => ({
2460
- ...entry,
2461
- actions: expandActionShortNames(entry.service, entry.actions)
2462
- }));
2911
+ const expandedEntries = this.expandPermissionEntries(permissions);
2463
2912
  const now = /* @__PURE__ */ new Date();
2464
2913
  const expiryMs = resolveExpiryMs(options?.expiry);
2465
2914
  const expirationTime = new Date(now.getTime() + expiryMs);
@@ -2486,6 +2935,23 @@ var _TinyCloudNode = class _TinyCloudNode {
2486
2935
  );
2487
2936
  const { subset, missing } = isCapabilitySubset(expandedEntries, granted);
2488
2937
  if (!subset) {
2938
+ const runtimeGrant = this.findGrantForOperations(
2939
+ this.permissionEntriesToOperations(expandedEntries, session)
2940
+ );
2941
+ if (runtimeGrant) {
2942
+ const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
2943
+ if (runtimeGrant.expiresAt.getTime() <= Date.now() + marginMs) {
2944
+ throw new SessionExpiredError(runtimeGrant.expiresAt);
2945
+ }
2946
+ const runtimeExpiration = runtimeGrant.expiresAt < effectiveExpiration ? runtimeGrant.expiresAt : effectiveExpiration;
2947
+ const delegation2 = await this.createDelegationViaRuntimeGrant(
2948
+ did,
2949
+ expandedEntries,
2950
+ runtimeExpiration,
2951
+ runtimeGrant
2952
+ );
2953
+ return { delegation: delegation2, prompted: false };
2954
+ }
2489
2955
  throw new PermissionNotInManifestError(missing, granted);
2490
2956
  }
2491
2957
  const delegation = await this.createDelegationViaWasmPath(
@@ -2630,6 +3096,41 @@ var _TinyCloudNode = class _TinyCloudNode {
2630
3096
  host: this.config.host
2631
3097
  };
2632
3098
  }
3099
+ async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant) {
3100
+ const result = this.createDelegationWrapper({
3101
+ session: grant.session,
3102
+ delegateDID: did,
3103
+ spaceId: grant.session.spaceId,
3104
+ abilities: this.permissionsToAbilities(entries),
3105
+ expirationSecs: Math.floor(expirationTime.getTime() / 1e3)
3106
+ });
3107
+ const primary = result.resources[0];
3108
+ const delegationHeader = { Authorization: result.delegation };
3109
+ const targetHost = grant.delegation.host ?? this.config.host;
3110
+ const activateResult = await activateSessionWithHost2(
3111
+ targetHost,
3112
+ delegationHeader
3113
+ );
3114
+ if (!activateResult.success) {
3115
+ throw new Error(
3116
+ `Failed to activate delegation with host: ${activateResult.error}`
3117
+ );
3118
+ }
3119
+ return {
3120
+ cid: result.cid,
3121
+ delegationHeader,
3122
+ spaceId: grant.session.spaceId,
3123
+ path: primary.path,
3124
+ actions: primary.actions,
3125
+ resources: result.resources,
3126
+ disableSubDelegation: false,
3127
+ expiry: result.expiry,
3128
+ delegateDID: did,
3129
+ ownerAddress: grant.delegation.ownerAddress,
3130
+ chainId: grant.delegation.chainId,
3131
+ host: targetHost
3132
+ };
3133
+ }
2633
3134
  resolvePermissionSpace(space, session) {
2634
3135
  if (space === void 0) {
2635
3136
  return this.wasmBindings.makeSpaceId(
@@ -2646,6 +3147,220 @@ var _TinyCloudNode = class _TinyCloudNode {
2646
3147
  }
2647
3148
  return this.wasmBindings.makeSpaceId(session.address, session.chainId, space);
2648
3149
  }
3150
+ expandPermissionEntries(permissions) {
3151
+ return expandPermissionEntriesCore(permissions);
3152
+ }
3153
+ shortServiceName(service) {
3154
+ const short = SERVICE_LONG_TO_SHORT[service];
3155
+ if (short === void 0) {
3156
+ throw new Error(
3157
+ `unknown service '${service}' \u2014 no short-form mapping`
3158
+ );
3159
+ }
3160
+ return short;
3161
+ }
3162
+ permissionsToAbilities(entries) {
3163
+ const abilities = {};
3164
+ for (const entry of entries) {
3165
+ const service = this.shortServiceName(entry.service);
3166
+ abilities[service] ?? (abilities[service] = {});
3167
+ const existing = abilities[service][entry.path] ?? [];
3168
+ const seen = new Set(existing);
3169
+ for (const action of entry.actions) {
3170
+ if (!seen.has(action)) {
3171
+ existing.push(action);
3172
+ seen.add(action);
3173
+ }
3174
+ }
3175
+ abilities[service][entry.path] = existing;
3176
+ }
3177
+ return abilities;
3178
+ }
3179
+ permissionOperations(entries, spaceId) {
3180
+ return entries.flatMap((entry) => {
3181
+ const service = this.shortServiceName(entry.service);
3182
+ return entry.actions.map((action) => ({
3183
+ spaceId,
3184
+ service,
3185
+ path: entry.path,
3186
+ action
3187
+ }));
3188
+ });
3189
+ }
3190
+ sessionCoversPermissionEntries(session, entries) {
3191
+ try {
3192
+ const granted = parseRecapCapabilities(
3193
+ (siwe) => this.wasmBindings.parseRecapFromSiwe(siwe),
3194
+ session.siwe
3195
+ );
3196
+ return isCapabilitySubset(entries, granted).subset;
3197
+ } catch {
3198
+ return false;
3199
+ }
3200
+ }
3201
+ permissionEntriesToOperations(entries, session) {
3202
+ return entries.flatMap((entry) => {
3203
+ const spaceId = this.resolvePermissionSpace(entry.space, session);
3204
+ const service = this.shortServiceName(entry.service);
3205
+ return entry.actions.map((action) => ({
3206
+ spaceId,
3207
+ service,
3208
+ path: entry.path,
3209
+ action
3210
+ }));
3211
+ });
3212
+ }
3213
+ findRuntimeGrantsForPermissionEntries(entries, session) {
3214
+ const grants = [];
3215
+ const operations = this.permissionEntriesToOperations(entries, session);
3216
+ if (operations.length === 0) {
3217
+ return grants;
3218
+ }
3219
+ for (const operation of operations) {
3220
+ const grant = this.findGrantForOperation(operation);
3221
+ if (!grant) {
3222
+ return [];
3223
+ }
3224
+ if (!grants.includes(grant)) {
3225
+ grants.push(grant);
3226
+ }
3227
+ }
3228
+ return grants;
3229
+ }
3230
+ runtimeDelegationFromSession(delegatedSession, entries, spaceId, session, expiresAt) {
3231
+ const resources = this.delegatedResourcesForEntries(entries, spaceId);
3232
+ const primary = resources[0];
3233
+ return {
3234
+ cid: delegatedSession.delegationCid,
3235
+ delegationHeader: delegatedSession.delegationHeader,
3236
+ spaceId,
3237
+ path: primary.path,
3238
+ actions: primary.actions,
3239
+ resources,
3240
+ disableSubDelegation: false,
3241
+ expiry: expiresAt,
3242
+ delegateDID: session.verificationMethod,
3243
+ ownerAddress: session.address,
3244
+ chainId: session.chainId,
3245
+ host: this.config.host
3246
+ };
3247
+ }
3248
+ runtimeGrantFromDelegation(delegation, session) {
3249
+ const operations = this.operationsFromDelegation(delegation);
3250
+ return {
3251
+ session: {
3252
+ delegationHeader: delegation.delegationHeader,
3253
+ delegationCid: delegation.cid,
3254
+ spaceId: delegation.spaceId,
3255
+ verificationMethod: session.verificationMethod,
3256
+ jwk: session.jwk
3257
+ },
3258
+ delegation,
3259
+ operations,
3260
+ expiresAt: delegation.expiry
3261
+ };
3262
+ }
3263
+ delegatedResourcesForEntries(entries, spaceId) {
3264
+ return entries.map((entry) => ({
3265
+ service: this.shortServiceName(entry.service),
3266
+ space: spaceId,
3267
+ path: entry.path,
3268
+ actions: [...entry.actions]
3269
+ }));
3270
+ }
3271
+ operationsFromDelegation(delegation) {
3272
+ const resources = delegation.resources !== void 0 && delegation.resources.length > 0 ? delegation.resources : this.flatDelegationResources(delegation);
3273
+ return resources.flatMap(
3274
+ (resource) => resource.actions.map((action) => ({
3275
+ spaceId: resource.space,
3276
+ service: this.invocationServiceName(resource.service),
3277
+ path: resource.path,
3278
+ action
3279
+ }))
3280
+ );
3281
+ }
3282
+ flatDelegationResources(delegation) {
3283
+ const byService = /* @__PURE__ */ new Map();
3284
+ for (const action of delegation.actions) {
3285
+ const service = this.shortServiceName(action.split("/")[0]);
3286
+ const actions = byService.get(service) ?? [];
3287
+ actions.push(action);
3288
+ byService.set(service, actions);
3289
+ }
3290
+ return [...byService.entries()].map(([service, actions]) => ({
3291
+ service,
3292
+ space: delegation.spaceId,
3293
+ path: delegation.path,
3294
+ actions
3295
+ }));
3296
+ }
3297
+ selectInvocationSession(fallback, service, path, action) {
3298
+ const grant = this.findGrantForOperation({
3299
+ spaceId: fallback.spaceId,
3300
+ service: this.invocationServiceName(service),
3301
+ path,
3302
+ action
3303
+ });
3304
+ return grant?.session ?? fallback;
3305
+ }
3306
+ findGrantForOperations(operations) {
3307
+ if (operations.length === 0) {
3308
+ return void 0;
3309
+ }
3310
+ this.pruneExpiredRuntimePermissionGrants();
3311
+ return this.runtimePermissionGrants.find((grant) => {
3312
+ return operations.every(
3313
+ (operation) => grant.operations.some(
3314
+ (granted) => this.operationCovers(granted, operation)
3315
+ )
3316
+ );
3317
+ });
3318
+ }
3319
+ findGrantForOperation(operation) {
3320
+ return this.findGrantForOperations([operation]);
3321
+ }
3322
+ pruneExpiredRuntimePermissionGrants() {
3323
+ const now = Date.now();
3324
+ this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
3325
+ (grant) => grant.expiresAt.getTime() > now
3326
+ );
3327
+ }
3328
+ operationCovers(granted, requested) {
3329
+ return granted.spaceId === requested.spaceId && granted.service === requested.service && this.actionContains(granted.action, requested.action) && this.pathContains(granted.path, requested.path);
3330
+ }
3331
+ actionContains(grantedAction, requestedAction) {
3332
+ if (grantedAction === requestedAction) {
3333
+ return true;
3334
+ }
3335
+ if (grantedAction.endsWith("/*")) {
3336
+ const prefix = grantedAction.slice(0, -2);
3337
+ return requestedAction.startsWith(`${prefix}/`);
3338
+ }
3339
+ return false;
3340
+ }
3341
+ invocationServiceName(service) {
3342
+ return service.startsWith("tinycloud.") ? this.shortServiceName(service) : service;
3343
+ }
3344
+ pathContains(grantedPath, requestedPath) {
3345
+ if (grantedPath === "" || grantedPath === "/") {
3346
+ return true;
3347
+ }
3348
+ if (grantedPath.endsWith("/**")) {
3349
+ return requestedPath.startsWith(grantedPath.slice(0, -3));
3350
+ }
3351
+ if (grantedPath.endsWith("/*")) {
3352
+ const prefix = grantedPath.slice(0, -2);
3353
+ if (!requestedPath.startsWith(prefix)) {
3354
+ return false;
3355
+ }
3356
+ const remainder = requestedPath.slice(prefix.length);
3357
+ return !remainder.includes("/") || remainder === "/";
3358
+ }
3359
+ if (grantedPath.endsWith("/")) {
3360
+ return requestedPath.startsWith(grantedPath);
3361
+ }
3362
+ return grantedPath === requestedPath;
3363
+ }
2649
3364
  /**
2650
3365
  * Issue a delegation via the legacy wallet-signed SIWE path for a single
2651
3366
  * {@link PermissionEntry}. Shares the implementation with the public
@@ -3033,15 +3748,18 @@ import {
3033
3748
  ACCOUNT_REGISTRY_SPACE as ACCOUNT_REGISTRY_SPACE2,
3034
3749
  DEFAULT_MANIFEST_SPACE as DEFAULT_MANIFEST_SPACE2,
3035
3750
  DEFAULT_MANIFEST_VERSION,
3751
+ VAULT_PERMISSION_SERVICE,
3036
3752
  PermissionNotInManifestError as PermissionNotInManifestError2,
3037
3753
  SessionExpiredError as SessionExpiredError2,
3038
3754
  ManifestValidationError,
3039
3755
  composeManifestRequest as composeManifestRequest2,
3040
- resolveManifest,
3756
+ resolveManifest as resolveManifest2,
3041
3757
  validateManifest,
3042
3758
  loadManifest,
3043
3759
  isCapabilitySubset as isCapabilitySubset2,
3044
- expandActionShortNames as expandActionShortNames2,
3760
+ expandActionShortNames,
3761
+ expandPermissionEntries,
3762
+ expandPermissionEntry,
3045
3763
  parseExpiry as parseExpiry2,
3046
3764
  resourceCapabilitiesToSpaceAbilitiesMap as resourceCapabilitiesToSpaceAbilitiesMap2
3047
3765
  } from "@tinycloud/sdk-core";
@@ -3066,7 +3784,16 @@ function deserializeDelegation(data) {
3066
3784
  import { KVService as KVService3, PrefixedKVService } from "@tinycloud/sdk-core";
3067
3785
  import { SQLService as SQLService3, SQLAction, DatabaseHandle } from "@tinycloud/sdk-core";
3068
3786
  import { DuckDbService as DuckDbService3, DuckDbDatabaseHandle, DuckDbAction } from "@tinycloud/sdk-core";
3069
- import { DataVaultService as DataVaultService2, VaultHeaders, VaultPublicSpaceKVActions, createVaultCrypto as createVaultCrypto2 } from "@tinycloud/sdk-core";
3787
+ import {
3788
+ DataVaultService as DataVaultService2,
3789
+ VaultHeaders,
3790
+ VaultPublicSpaceKVActions,
3791
+ createVaultCrypto as createVaultCrypto2,
3792
+ SecretsService as SecretsService2,
3793
+ SECRET_NAME_RE,
3794
+ canonicalizeSecretScope,
3795
+ resolveSecretPath as resolveSecretPath2
3796
+ } from "@tinycloud/sdk-core";
3070
3797
  import {
3071
3798
  DelegationManager as DelegationManager2,
3072
3799
  SharingService as SharingService2,
@@ -3118,8 +3845,10 @@ export {
3118
3845
  PermissionNotInManifestError2 as PermissionNotInManifestError,
3119
3846
  PrefixedKVService,
3120
3847
  ProtocolMismatchError,
3848
+ SECRET_NAME_RE,
3121
3849
  SQLAction,
3122
3850
  SQLService3 as SQLService,
3851
+ SecretsService2 as SecretsService,
3123
3852
  ServiceContext3 as ServiceContext,
3124
3853
  SessionExpiredError2 as SessionExpiredError,
3125
3854
  SharingService2 as SharingService,
@@ -3130,11 +3859,13 @@ export {
3130
3859
  TinyCloud2 as TinyCloud,
3131
3860
  TinyCloudNode,
3132
3861
  UnsupportedFeatureError2 as UnsupportedFeatureError,
3862
+ VAULT_PERMISSION_SERVICE,
3133
3863
  VaultHeaders,
3134
3864
  VaultPublicSpaceKVActions,
3135
3865
  VersionCheckError,
3136
3866
  WasmKeyProvider,
3137
3867
  buildSpaceUri,
3868
+ canonicalizeSecretScope,
3138
3869
  checkNodeInfo2 as checkNodeInfo,
3139
3870
  composeManifestRequest2 as composeManifestRequest,
3140
3871
  createCapabilityKeyRegistry,
@@ -3145,13 +3876,16 @@ export {
3145
3876
  defaultSignStrategy,
3146
3877
  defaultSpaceCreationHandler,
3147
3878
  deserializeDelegation,
3148
- expandActionShortNames2 as expandActionShortNames,
3879
+ expandActionShortNames,
3880
+ expandPermissionEntries,
3881
+ expandPermissionEntry,
3149
3882
  isCapabilitySubset2 as isCapabilitySubset,
3150
3883
  loadManifest,
3151
3884
  makePublicSpaceId2 as makePublicSpaceId,
3152
3885
  parseExpiry2 as parseExpiry,
3153
3886
  parseSpaceUri,
3154
- resolveManifest,
3887
+ resolveManifest2 as resolveManifest,
3888
+ resolveSecretPath2 as resolveSecretPath,
3155
3889
  resourceCapabilitiesToSpaceAbilitiesMap2 as resourceCapabilitiesToSpaceAbilitiesMap,
3156
3890
  serializeDelegation,
3157
3891
  validateManifest