@tinycloud/node-sdk 2.2.0-beta.1 → 2.2.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/core.js CHANGED
@@ -207,7 +207,9 @@ import {
207
207
  DEFAULT_MANIFEST_SPACE,
208
208
  composeManifestRequest,
209
209
  resourceCapabilitiesToAbilitiesMap,
210
- resourceCapabilitiesToSpaceAbilitiesMap
210
+ resourceCapabilitiesToSpaceAbilitiesMap,
211
+ resolveTinyCloudHosts,
212
+ EXPIRY
211
213
  } from "@tinycloud/sdk-core";
212
214
 
213
215
  // src/authorization/strategies.ts
@@ -267,12 +269,12 @@ var NodeUserAuthorization = class {
267
269
  ]
268
270
  }
269
271
  };
270
- this.sessionExpirationMs = config.sessionExpirationMs ?? 60 * 60 * 1e3;
272
+ this.sessionExpirationMs = config.sessionExpirationMs ?? EXPIRY.SESSION_MS;
271
273
  this.autoCreateSpace = config.autoCreateSpace ?? false;
272
274
  this.spaceCreationHandler = config.spaceCreationHandler;
273
- this.tinycloudHosts = config.tinycloudHosts ?? [
274
- "https://node.tinycloud.xyz"
275
- ];
275
+ this.tinycloudHosts = config.tinycloudHosts;
276
+ this.tinycloudRegistryUrl = config.tinycloudRegistryUrl;
277
+ this.tinycloudFallbackHosts = config.tinycloudFallbackHosts;
276
278
  this.enablePublicSpace = config.enablePublicSpace ?? true;
277
279
  this.nonce = config.nonce;
278
280
  this.siweConfig = config.siweConfig;
@@ -293,6 +295,9 @@ var NodeUserAuthorization = class {
293
295
  get capabilityRequest() {
294
296
  return this.getCapabilityRequest();
295
297
  }
298
+ get hosts() {
299
+ return this.tinycloudHosts ? [...this.tinycloudHosts] : [];
300
+ }
296
301
  /**
297
302
  * Install or replace the stored manifest. Takes effect on the next
298
303
  * `signIn()` call — the current session (if any) is not touched.
@@ -317,6 +322,26 @@ var NodeUserAuthorization = class {
317
322
  get tinyCloudSession() {
318
323
  return this._tinyCloudSession;
319
324
  }
325
+ async resolveTinyCloudHostsForSignIn(address, chainId) {
326
+ if (this.tinycloudHosts && this.tinycloudHosts.length > 0) {
327
+ return;
328
+ }
329
+ const subject = `did:pkh:eip155:${chainId}:${address}`;
330
+ const resolved = await resolveTinyCloudHosts(subject, {
331
+ registryUrl: this.tinycloudRegistryUrl,
332
+ fallbackHosts: this.tinycloudFallbackHosts
333
+ });
334
+ this.tinycloudHosts = resolved.hosts;
335
+ }
336
+ requireTinyCloudHosts() {
337
+ if (!this.tinycloudHosts || this.tinycloudHosts.length === 0) {
338
+ throw new Error("TinyCloud hosts have not been resolved. Call signIn() first.");
339
+ }
340
+ return this.tinycloudHosts;
341
+ }
342
+ get primaryTinyCloudHost() {
343
+ return this.requireTinyCloudHosts()[0];
344
+ }
320
345
  get nodeFeatures() {
321
346
  return this._nodeFeatures;
322
347
  }
@@ -448,7 +473,7 @@ var NodeUserAuthorization = class {
448
473
  if (!this._tinyCloudSession || !this._address || !this._chainId) {
449
474
  throw new Error("Must be signed in to host space");
450
475
  }
451
- const host = this.tinycloudHosts[0];
476
+ const host = this.primaryTinyCloudHost;
452
477
  const spaceId = targetSpaceId ?? this._tinyCloudSession.spaceId;
453
478
  const peerId = await fetchPeerId(host, spaceId);
454
479
  const siwe = this.wasm.generateHostSIWEMessage({
@@ -490,7 +515,7 @@ var NodeUserAuthorization = class {
490
515
  if (!this._tinyCloudSession) {
491
516
  throw new Error("Must be signed in to ensure space exists");
492
517
  }
493
- const host = this.tinycloudHosts[0];
518
+ const host = this.primaryTinyCloudHost;
494
519
  const primarySpaceId = this._tinyCloudSession.spaceId;
495
520
  const result = await activateSessionWithHost(
496
521
  host,
@@ -599,6 +624,7 @@ var NodeUserAuthorization = class {
599
624
  this._chainId = await this.signer.getChainId();
600
625
  const address = this.wasm.ensureEip55(this._address);
601
626
  const chainId = this._chainId;
627
+ await this.resolveTinyCloudHostsForSignIn(address, chainId);
602
628
  const keyId = `session-${Date.now()}`;
603
629
  this.sessionManager.renameSessionKeyId("default", keyId);
604
630
  const jwkString = this.sessionManager.jwk(keyId);
@@ -677,7 +703,7 @@ var NodeUserAuthorization = class {
677
703
  this._address = address;
678
704
  this._chainId = chainId;
679
705
  const nodeInfo = await checkNodeInfo(
680
- this.tinycloudHosts[0],
706
+ this.primaryTinyCloudHost,
681
707
  this.wasm.protocolVersion()
682
708
  );
683
709
  this._nodeFeatures = nodeInfo.features;
@@ -793,6 +819,7 @@ var NodeUserAuthorization = class {
793
819
  });
794
820
  const address = this.wasm.ensureEip55(await this.signer.getAddress());
795
821
  const chainId = await this.signer.getChainId();
822
+ await this.resolveTinyCloudHostsForSignIn(address, chainId);
796
823
  const clientSession = {
797
824
  address,
798
825
  walletAddress: address,
@@ -842,7 +869,7 @@ var NodeUserAuthorization = class {
842
869
  this._address = address;
843
870
  this._chainId = chainId;
844
871
  const nodeInfo = await checkNodeInfo(
845
- this.tinycloudHosts[0],
872
+ this.primaryTinyCloudHost,
846
873
  this.wasm.protocolVersion()
847
874
  );
848
875
  this._nodeFeatures = nodeInfo.features;
@@ -933,6 +960,7 @@ import {
933
960
  DuckDbService as DuckDbService2,
934
961
  HooksService as HooksService2,
935
962
  DataVaultService,
963
+ SecretsService,
936
964
  createVaultCrypto,
937
965
  ServiceContext as ServiceContext2,
938
966
  SilentNotificationHandler,
@@ -945,10 +973,11 @@ import {
945
973
  ACCOUNT_REGISTRY_SPACE,
946
974
  PermissionNotInManifestError,
947
975
  SessionExpiredError,
948
- expandActionShortNames,
976
+ expandPermissionEntries as expandPermissionEntriesCore,
949
977
  isCapabilitySubset,
950
978
  parseRecapCapabilities,
951
- SERVICE_LONG_TO_SHORT
979
+ SERVICE_LONG_TO_SHORT,
980
+ EXPIRY as EXPIRY3
952
981
  } from "@tinycloud/sdk-core";
953
982
 
954
983
  // src/DelegatedAccess.ts
@@ -1033,6 +1062,24 @@ var DelegatedAccess = class {
1033
1062
  get hooks() {
1034
1063
  return this._hooks;
1035
1064
  }
1065
+ /**
1066
+ * Export the handles needed to rehydrate this activated delegation via
1067
+ * `TinyCloudNode.restoreSession(...)` in another process or after a
1068
+ * restart.
1069
+ *
1070
+ * See `RestorableSession` for lifetime caveats.
1071
+ */
1072
+ get restorable() {
1073
+ return {
1074
+ delegationHeader: this.session.delegationHeader,
1075
+ delegationCid: this.session.delegationCid,
1076
+ spaceId: this.session.spaceId,
1077
+ jwk: this.session.jwk,
1078
+ verificationMethod: this.session.verificationMethod,
1079
+ address: this.session.address,
1080
+ chainId: this.session.chainId
1081
+ };
1082
+ }
1036
1083
  };
1037
1084
 
1038
1085
  // src/keys/WasmKeyProvider.ts
@@ -1113,7 +1160,8 @@ function createWasmKeyProvider(sessionManager) {
1113
1160
  // src/delegateToHelpers.ts
1114
1161
  import {
1115
1162
  parseExpiry,
1116
- SiweMessage
1163
+ SiweMessage,
1164
+ EXPIRY as EXPIRY2
1117
1165
  } from "@tinycloud/sdk-core";
1118
1166
  function legacyParamsToPermissionEntries(actions, path, spaceIdOverride) {
1119
1167
  const byService = /* @__PURE__ */ new Map();
@@ -1145,9 +1193,10 @@ function legacyParamsToPermissionEntries(actions, path, spaceIdOverride) {
1145
1193
  }
1146
1194
  return entries;
1147
1195
  }
1196
+ var DEFAULT_DELEGATION_EXPIRY_MS = EXPIRY2.SESSION_MS;
1148
1197
  function resolveExpiryMs(expiry) {
1149
1198
  if (expiry === void 0) {
1150
- return 60 * 60 * 1e3;
1199
+ return DEFAULT_DELEGATION_EXPIRY_MS;
1151
1200
  }
1152
1201
  if (typeof expiry === "number") {
1153
1202
  if (!Number.isFinite(expiry) || expiry <= 0) {
@@ -1173,8 +1222,155 @@ function extractSiweExpiration(siwe) {
1173
1222
  return d;
1174
1223
  }
1175
1224
 
1225
+ // src/NodeSecretsService.ts
1226
+ import {
1227
+ ErrorCodes,
1228
+ resolveSecretPath,
1229
+ resolveManifest
1230
+ } from "@tinycloud/sdk-core";
1231
+ var SECRETS_SPACE = "secrets";
1232
+ function ok() {
1233
+ return { ok: true, data: void 0 };
1234
+ }
1235
+ function secretsError(code, message, cause) {
1236
+ return {
1237
+ ok: false,
1238
+ error: {
1239
+ code,
1240
+ service: "secrets",
1241
+ message,
1242
+ ...cause ? { cause } : {}
1243
+ }
1244
+ };
1245
+ }
1246
+ function displayActionUrn(action) {
1247
+ return action === "put" ? "tinycloud.vault/write" : "tinycloud.vault/delete";
1248
+ }
1249
+ function kvActionUrn(action) {
1250
+ return `tinycloud.kv/${action}`;
1251
+ }
1252
+ function vaultMutationAction(action) {
1253
+ return action === "put" ? "write" : "delete";
1254
+ }
1255
+ function secretPermissionEntries(name, options, action) {
1256
+ const secretPath = resolveSecretPath(name, options);
1257
+ return [
1258
+ {
1259
+ service: "tinycloud.vault",
1260
+ space: SECRETS_SPACE,
1261
+ path: secretPath.vaultKey,
1262
+ actions: [vaultMutationAction(action)],
1263
+ skipPrefix: true
1264
+ }
1265
+ ];
1266
+ }
1267
+ function isSecretsSpace(space) {
1268
+ return space === SECRETS_SPACE || space.endsWith(`:${SECRETS_SPACE}`);
1269
+ }
1270
+ var NodeSecretsService = class {
1271
+ constructor(config) {
1272
+ this.config = config;
1273
+ this.shouldRestoreUnlock = false;
1274
+ }
1275
+ get vault() {
1276
+ return this.service.vault;
1277
+ }
1278
+ get isUnlocked() {
1279
+ return this.service.isUnlocked;
1280
+ }
1281
+ async unlock(signer) {
1282
+ const effectiveSigner = signer ?? this.config.getUnlockSigner?.();
1283
+ if (effectiveSigner !== void 0) {
1284
+ this.unlockSigner = effectiveSigner;
1285
+ }
1286
+ const result = await this.service.unlock(effectiveSigner);
1287
+ if (result.ok) {
1288
+ this.shouldRestoreUnlock = true;
1289
+ }
1290
+ return result;
1291
+ }
1292
+ lock() {
1293
+ this.shouldRestoreUnlock = false;
1294
+ this.service.lock();
1295
+ }
1296
+ get(name, options) {
1297
+ return options === void 0 ? this.service.get(name) : this.service.get(name, options);
1298
+ }
1299
+ async put(name, value, options) {
1300
+ const permission = await this.ensureMutationPermission(name, options, "put");
1301
+ if (!permission.ok) return permission;
1302
+ return options === void 0 ? this.service.put(name, value) : this.service.put(name, value, options);
1303
+ }
1304
+ async delete(name, options) {
1305
+ const permission = await this.ensureMutationPermission(name, options, "del");
1306
+ if (!permission.ok) return permission;
1307
+ return options === void 0 ? this.service.delete(name) : this.service.delete(name, options);
1308
+ }
1309
+ list(options) {
1310
+ return options === void 0 ? this.service.list() : this.service.list(options);
1311
+ }
1312
+ get service() {
1313
+ return this.config.getService();
1314
+ }
1315
+ async ensureMutationPermission(name, options, action) {
1316
+ let permissionEntries;
1317
+ try {
1318
+ permissionEntries = secretPermissionEntries(name, options, action);
1319
+ } catch (error) {
1320
+ return secretsError(
1321
+ ErrorCodes.INVALID_INPUT,
1322
+ error instanceof Error ? error.message : String(error),
1323
+ error instanceof Error ? error : void 0
1324
+ );
1325
+ }
1326
+ if (this.hasMutationPermission(name, options, action)) {
1327
+ return ok();
1328
+ }
1329
+ if (!this.config.canEscalate()) {
1330
+ return secretsError(
1331
+ ErrorCodes.PERMISSION_DENIED,
1332
+ `Cannot autosign ${displayActionUrn(action)} for ${name}; TinyCloudNode needs wallet mode with a signer or privateKey.`
1333
+ );
1334
+ }
1335
+ try {
1336
+ await this.config.grantPermissions(permissionEntries);
1337
+ return this.restoreUnlockAfterEscalation();
1338
+ } catch (error) {
1339
+ return secretsError(
1340
+ ErrorCodes.PERMISSION_DENIED,
1341
+ error instanceof Error ? error.message : `Autosign escalation for ${displayActionUrn(action)} on ${name} failed.`,
1342
+ error instanceof Error ? error : void 0
1343
+ );
1344
+ }
1345
+ }
1346
+ async restoreUnlockAfterEscalation() {
1347
+ if (!this.shouldRestoreUnlock) {
1348
+ return ok();
1349
+ }
1350
+ return this.service.unlock(this.unlockSigner);
1351
+ }
1352
+ hasMutationPermission(name, options, action) {
1353
+ const manifest = this.config.getManifest();
1354
+ if (manifest === void 0) {
1355
+ return false;
1356
+ }
1357
+ const manifests = Array.isArray(manifest) ? manifest : [manifest];
1358
+ const requiredAction = kvActionUrn(action);
1359
+ const secretPath = resolveSecretPath(name, options);
1360
+ return manifests.some((entry) => {
1361
+ const resolved = resolveManifest(entry);
1362
+ return ["keys", "vault"].every(
1363
+ (base) => resolved.resources.some(
1364
+ (resource) => resource.service === "tinycloud.kv" && isSecretsSpace(resource.space) && resource.path === secretPath.permissionPaths[base] && resource.actions.includes(requiredAction)
1365
+ )
1366
+ );
1367
+ });
1368
+ }
1369
+ };
1370
+
1176
1371
  // src/TinyCloudNode.ts
1177
1372
  var DEFAULT_HOST = "https://node.tinycloud.xyz";
1373
+ var DEFAULT_SESSION_EXPIRATION_MS = EXPIRY3.SESSION_MS;
1178
1374
  var _TinyCloudNode = class _TinyCloudNode {
1179
1375
  /**
1180
1376
  * Create a new TinyCloudNode instance.
@@ -1204,6 +1400,31 @@ var _TinyCloudNode = class _TinyCloudNode {
1204
1400
  this.auth = null;
1205
1401
  this.tc = null;
1206
1402
  this._chainId = 1;
1403
+ this.runtimePermissionGrants = [];
1404
+ this.invokeWithRuntimePermissions = (session, service, path, action, facts) => {
1405
+ return this.wasmBindings.invoke(
1406
+ this.selectInvocationSession(session, service, path, action),
1407
+ service,
1408
+ path,
1409
+ action,
1410
+ facts
1411
+ );
1412
+ };
1413
+ this.invokeAnyWithRuntimePermissions = (session, entries, facts) => {
1414
+ if (!this.wasmBindings.invokeAny) {
1415
+ throw new Error("WASM binding does not support invokeAny");
1416
+ }
1417
+ const grant = this.findGrantForOperations(
1418
+ entries.map((entry) => ({
1419
+ spaceId: entry.spaceId,
1420
+ service: this.invocationServiceName(entry.service),
1421
+ path: entry.path,
1422
+ action: entry.action
1423
+ }))
1424
+ );
1425
+ return this.wasmBindings.invokeAny(grant?.session ?? session, entries, facts);
1426
+ };
1427
+ this.explicitHost = config.host;
1207
1428
  this.config = {
1208
1429
  ...config,
1209
1430
  host: config.host ?? DEFAULT_HOST
@@ -1238,7 +1459,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1238
1459
  this._sharingService = new SharingService({
1239
1460
  hosts: [this.config.host],
1240
1461
  // session: undefined - not needed for receive()
1241
- invoke: this.wasmBindings.invoke,
1462
+ invoke: this.invokeWithRuntimePermissions,
1242
1463
  fetch: globalThis.fetch.bind(globalThis),
1243
1464
  keyProvider: this._keyProvider,
1244
1465
  registry: this._capabilityRegistry,
@@ -1285,7 +1506,6 @@ var _TinyCloudNode = class _TinyCloudNode {
1285
1506
  * @internal
1286
1507
  */
1287
1508
  setupAuth(config) {
1288
- const host = this.config.host;
1289
1509
  this.auth = new NodeUserAuthorization({
1290
1510
  signer: this.signer,
1291
1511
  signStrategy: { type: "auto-sign" },
@@ -1293,8 +1513,10 @@ var _TinyCloudNode = class _TinyCloudNode {
1293
1513
  sessionStorage: config.sessionStorage ?? new MemorySessionStorage(),
1294
1514
  domain: this.siweDomain,
1295
1515
  spacePrefix: config.prefix,
1296
- sessionExpirationMs: config.sessionExpirationMs ?? 60 * 60 * 1e3,
1297
- tinycloudHosts: [host],
1516
+ sessionExpirationMs: config.sessionExpirationMs ?? DEFAULT_SESSION_EXPIRATION_MS,
1517
+ tinycloudHosts: this.explicitHost ? [this.explicitHost] : void 0,
1518
+ tinycloudRegistryUrl: config.tinycloudRegistryUrl,
1519
+ tinycloudFallbackHosts: config.tinycloudFallbackHosts,
1298
1520
  autoCreateSpace: config.autoCreateSpace,
1299
1521
  enablePublicSpace: config.enablePublicSpace ?? true,
1300
1522
  spaceCreationHandler: config.spaceCreationHandler,
@@ -1305,9 +1527,15 @@ var _TinyCloudNode = class _TinyCloudNode {
1305
1527
  includeAccountRegistryPermissions: config.includeAccountRegistryPermissions
1306
1528
  });
1307
1529
  this.tc = new TinyCloud(this.auth, {
1308
- invokeAny: this.wasmBindings.invokeAny
1530
+ invokeAny: this.invokeAnyWithRuntimePermissions
1309
1531
  });
1310
1532
  }
1533
+ syncResolvedHostFromAuth() {
1534
+ const host = this.auth?.hosts[0];
1535
+ if (host) {
1536
+ this.config.host = host;
1537
+ }
1538
+ }
1311
1539
  /**
1312
1540
  * Install or replace the manifest that drives the SIWE recap at
1313
1541
  * sign-in. Takes effect on the next `signIn()` call — the current
@@ -1345,6 +1573,10 @@ var _TinyCloudNode = class _TinyCloudNode {
1345
1573
  get capabilityRequest() {
1346
1574
  return this.auth?.capabilityRequest;
1347
1575
  }
1576
+ get hosts() {
1577
+ const authHosts = this.auth?.hosts ?? [];
1578
+ return authHosts.length > 0 ? authHosts : [this.config.host];
1579
+ }
1348
1580
  /**
1349
1581
  * Get the primary identity DID for this user.
1350
1582
  * - If wallet connected and signed in: returns PKH DID (did:pkh:eip155:{chainId}:{address})
@@ -1415,8 +1647,14 @@ var _TinyCloudNode = class _TinyCloudNode {
1415
1647
  this._sql = void 0;
1416
1648
  this._duckdb = void 0;
1417
1649
  this._hooks = void 0;
1650
+ this._vault = void 0;
1651
+ this._baseSecrets = void 0;
1652
+ this._secrets = void 0;
1653
+ this._spaceService = void 0;
1418
1654
  this._serviceContext = void 0;
1655
+ this.runtimePermissionGrants = [];
1419
1656
  await this.tc.signIn(options);
1657
+ this.syncResolvedHostFromAuth();
1420
1658
  this.initializeServices();
1421
1659
  await this.writeManifestRegistryRecords();
1422
1660
  this.notificationHandler.success("Successfully signed in");
@@ -1436,7 +1674,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1436
1674
  throw new Error("Manifest registry write requires wallet mode");
1437
1675
  }
1438
1676
  const accountSpaceId = this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE);
1439
- await this.auth.hostOwnedSpace(accountSpaceId);
1677
+ await this.ensureOwnedSpaceHosted(accountSpaceId);
1440
1678
  const accountKV = this.spaces.get(accountSpaceId).kv;
1441
1679
  for (const record of request.registryRecords) {
1442
1680
  const result = await accountKV.put(record.key, {
@@ -1451,6 +1689,39 @@ var _TinyCloudNode = class _TinyCloudNode {
1451
1689
  }
1452
1690
  }
1453
1691
  }
1692
+ async ensureOwnedSpaceHosted(spaceId) {
1693
+ if (!this.auth) {
1694
+ throw new Error("Owned space hosting requires wallet mode");
1695
+ }
1696
+ const session = this.auth.tinyCloudSession;
1697
+ if (!session) {
1698
+ throw new Error("Owned space hosting requires an active session");
1699
+ }
1700
+ const host = this.hosts[0] ?? this.config.host;
1701
+ if (!host) {
1702
+ throw new Error("Owned space hosting requires a TinyCloud host");
1703
+ }
1704
+ const activation = await activateSessionWithHost2(host, session.delegationHeader);
1705
+ if (activation.success && !activation.skipped?.includes(spaceId)) {
1706
+ return;
1707
+ }
1708
+ if (!activation.success && activation.status !== 404) {
1709
+ throw new Error(
1710
+ `Failed to check owned space ${spaceId}: ${activation.error ?? activation.status}`
1711
+ );
1712
+ }
1713
+ const created = await this.auth.hostOwnedSpace(spaceId);
1714
+ if (!created) {
1715
+ throw new Error(`Failed to create owned space: ${spaceId}`);
1716
+ }
1717
+ await new Promise((resolve) => setTimeout(resolve, 100));
1718
+ const retry = await activateSessionWithHost2(host, session.delegationHeader);
1719
+ if (!retry.success || retry.skipped?.includes(spaceId)) {
1720
+ throw new Error(
1721
+ `Failed to activate session after creating owned space ${spaceId}: ${retry.error ?? "space was skipped"}`
1722
+ );
1723
+ }
1724
+ }
1454
1725
  /**
1455
1726
  * Restore a previously established session from stored delegation data.
1456
1727
  *
@@ -1466,7 +1737,12 @@ var _TinyCloudNode = class _TinyCloudNode {
1466
1737
  this._sql = void 0;
1467
1738
  this._duckdb = void 0;
1468
1739
  this._hooks = void 0;
1740
+ this._vault = void 0;
1741
+ this._baseSecrets = void 0;
1742
+ this._secrets = void 0;
1743
+ this._spaceService = void 0;
1469
1744
  this._serviceContext = void 0;
1745
+ this.runtimePermissionGrants = [];
1470
1746
  if (sessionData.address) {
1471
1747
  this._address = sessionData.address;
1472
1748
  }
@@ -1474,8 +1750,8 @@ var _TinyCloudNode = class _TinyCloudNode {
1474
1750
  this._chainId = sessionData.chainId;
1475
1751
  }
1476
1752
  this._serviceContext = new ServiceContext2({
1477
- invoke: this.wasmBindings.invoke,
1478
- invokeAny: this.wasmBindings.invokeAny,
1753
+ invoke: this.invokeWithRuntimePermissions,
1754
+ invokeAny: this.invokeAnyWithRuntimePermissions,
1479
1755
  fetch: globalThis.fetch.bind(globalThis),
1480
1756
  hosts: [this.config.host]
1481
1757
  });
@@ -1499,41 +1775,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1499
1775
  jwk: sessionData.jwk
1500
1776
  };
1501
1777
  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
- });
1778
+ this._vault = this.createVaultService(sessionData.spaceId, this._kv);
1537
1779
  this._vault.initialize(this._serviceContext);
1538
1780
  this._serviceContext.registerService("vault", this._vault);
1539
1781
  this.initializeV2Services(serviceSession);
@@ -1568,7 +1810,6 @@ var _TinyCloudNode = class _TinyCloudNode {
1568
1810
  throw new Error("Wallet already connected. Cannot connect another wallet.");
1569
1811
  }
1570
1812
  const prefix = options?.prefix ?? "default";
1571
- const host = this.config.host;
1572
1813
  if (!_TinyCloudNode.nodeDefaults) {
1573
1814
  throw new Error(
1574
1815
  "connectWallet() requires PrivateKeySigner. Use connectSigner() instead, or import from '@tinycloud/node-sdk' (not '/core') for automatic Node.js defaults."
@@ -1582,8 +1823,10 @@ var _TinyCloudNode = class _TinyCloudNode {
1582
1823
  sessionStorage: options?.sessionStorage ?? this.config.sessionStorage ?? new MemorySessionStorage(),
1583
1824
  domain: this.siweDomain,
1584
1825
  spacePrefix: prefix,
1585
- sessionExpirationMs: this.config.sessionExpirationMs ?? 60 * 60 * 1e3,
1586
- tinycloudHosts: [host],
1826
+ sessionExpirationMs: this.config.sessionExpirationMs ?? DEFAULT_SESSION_EXPIRATION_MS,
1827
+ tinycloudHosts: this.explicitHost ? [this.explicitHost] : void 0,
1828
+ tinycloudRegistryUrl: this.config.tinycloudRegistryUrl,
1829
+ tinycloudFallbackHosts: this.config.tinycloudFallbackHosts,
1587
1830
  autoCreateSpace: this.config.autoCreateSpace,
1588
1831
  enablePublicSpace: this.config.enablePublicSpace ?? true,
1589
1832
  spaceCreationHandler: this.config.spaceCreationHandler,
@@ -1594,7 +1837,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1594
1837
  includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
1595
1838
  });
1596
1839
  this.tc = new TinyCloud(this.auth, {
1597
- invokeAny: this.wasmBindings.invokeAny
1840
+ invokeAny: this.invokeAnyWithRuntimePermissions
1598
1841
  });
1599
1842
  this.config.prefix = prefix;
1600
1843
  }
@@ -1616,7 +1859,6 @@ var _TinyCloudNode = class _TinyCloudNode {
1616
1859
  throw new Error("Signer already connected. Cannot connect another signer.");
1617
1860
  }
1618
1861
  const prefix = options?.prefix ?? "default";
1619
- const host = this.config.host;
1620
1862
  this.signer = signer;
1621
1863
  this.auth = new NodeUserAuthorization({
1622
1864
  signer: this.signer,
@@ -1625,8 +1867,10 @@ var _TinyCloudNode = class _TinyCloudNode {
1625
1867
  sessionStorage: options?.sessionStorage ?? this.config.sessionStorage ?? new MemorySessionStorage(),
1626
1868
  domain: this.siweDomain,
1627
1869
  spacePrefix: prefix,
1628
- sessionExpirationMs: this.config.sessionExpirationMs ?? 60 * 60 * 1e3,
1629
- tinycloudHosts: [host],
1870
+ sessionExpirationMs: this.config.sessionExpirationMs ?? DEFAULT_SESSION_EXPIRATION_MS,
1871
+ tinycloudHosts: this.explicitHost ? [this.explicitHost] : void 0,
1872
+ tinycloudRegistryUrl: this.config.tinycloudRegistryUrl,
1873
+ tinycloudFallbackHosts: this.config.tinycloudFallbackHosts,
1630
1874
  autoCreateSpace: this.config.autoCreateSpace,
1631
1875
  enablePublicSpace: this.config.enablePublicSpace ?? true,
1632
1876
  spaceCreationHandler: this.config.spaceCreationHandler,
@@ -1637,7 +1881,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1637
1881
  includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
1638
1882
  });
1639
1883
  this.tc = new TinyCloud(this.auth, {
1640
- invokeAny: this.wasmBindings.invokeAny
1884
+ invokeAny: this.invokeAnyWithRuntimePermissions
1641
1885
  });
1642
1886
  this.config.prefix = prefix;
1643
1887
  }
@@ -1650,10 +1894,10 @@ var _TinyCloudNode = class _TinyCloudNode {
1650
1894
  if (!session) {
1651
1895
  return;
1652
1896
  }
1653
- this.tc.initializeServices(this.wasmBindings.invoke, [this.config.host]);
1897
+ this.tc.initializeServices(this.invokeWithRuntimePermissions, [this.config.host]);
1654
1898
  this._serviceContext = new ServiceContext2({
1655
- invoke: this.wasmBindings.invoke,
1656
- invokeAny: this.wasmBindings.invokeAny,
1899
+ invoke: this.invokeWithRuntimePermissions,
1900
+ invokeAny: this.invokeAnyWithRuntimePermissions,
1657
1901
  fetch: globalThis.fetch.bind(globalThis),
1658
1902
  hosts: [this.config.host]
1659
1903
  });
@@ -1683,6 +1927,28 @@ var _TinyCloudNode = class _TinyCloudNode {
1683
1927
  };
1684
1928
  this._serviceContext.setSession(serviceSession);
1685
1929
  this.tc.serviceContext.setSession(serviceSession);
1930
+ this._vault = this.createVaultService(session.spaceId, this._kv);
1931
+ this._vault.initialize(this._serviceContext);
1932
+ this._serviceContext.registerService("vault", this._vault);
1933
+ this.initializeV2Services(serviceSession);
1934
+ }
1935
+ createSpaceScopedKVService(spaceId) {
1936
+ const kvService = new KVService2({});
1937
+ if (this._serviceContext) {
1938
+ const spaceScopedContext = new ServiceContext2({
1939
+ invoke: this._serviceContext.invoke,
1940
+ fetch: this._serviceContext.fetch,
1941
+ hosts: this._serviceContext.hosts
1942
+ });
1943
+ const session = this._serviceContext.session;
1944
+ if (session) {
1945
+ spaceScopedContext.setSession({ ...session, spaceId });
1946
+ }
1947
+ kvService.initialize(spaceScopedContext);
1948
+ }
1949
+ return kvService;
1950
+ }
1951
+ createVaultService(spaceId, kv) {
1686
1952
  const wasm = this.wasmBindings;
1687
1953
  const vaultCrypto = createVaultCrypto({
1688
1954
  vault_encrypt: wasm.vault_encrypt,
@@ -1694,11 +1960,11 @@ var _TinyCloudNode = class _TinyCloudNode {
1694
1960
  vault_sha256: wasm.vault_sha256
1695
1961
  });
1696
1962
  const self = this;
1697
- this._vault = new DataVaultService({
1698
- spaceId: session.spaceId,
1963
+ return new DataVaultService({
1964
+ spaceId,
1699
1965
  crypto: vaultCrypto,
1700
1966
  tc: {
1701
- kv: this._kv,
1967
+ kv,
1702
1968
  ensurePublicSpace: async () => {
1703
1969
  try {
1704
1970
  await self.ensurePublicSpace();
@@ -1710,17 +1976,14 @@ var _TinyCloudNode = class _TinyCloudNode {
1710
1976
  get publicKV() {
1711
1977
  return self._publicKV ?? self.tc.publicKV;
1712
1978
  },
1713
- readPublicSpace: (host, spaceId, key) => TinyCloud.readPublicSpace(host, spaceId, key),
1979
+ readPublicSpace: (host, targetSpaceId, key) => TinyCloud.readPublicSpace(host, targetSpaceId, key),
1714
1980
  makePublicSpaceId: TinyCloud.makePublicSpaceId,
1715
1981
  did: this.did,
1716
- address: this._address,
1982
+ address: this._address ?? "",
1717
1983
  chainId: this._chainId,
1718
1984
  hosts: [this.config.host]
1719
1985
  }
1720
1986
  });
1721
- this._vault.initialize(this._serviceContext);
1722
- this._serviceContext.registerService("vault", this._vault);
1723
- this.initializeV2Services(serviceSession);
1724
1987
  }
1725
1988
  /**
1726
1989
  * Initialize the v2 delegation system services.
@@ -1804,7 +2067,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1804
2067
  this._delegationManager = new DelegationManager({
1805
2068
  hosts: [this.config.host],
1806
2069
  session: serviceSession,
1807
- invoke: this.wasmBindings.invoke,
2070
+ invoke: this.invokeWithRuntimePermissions,
1808
2071
  fetch: globalThis.fetch.bind(globalThis)
1809
2072
  });
1810
2073
  this._spaceService = new SpaceService({
@@ -1815,20 +2078,15 @@ var _TinyCloudNode = class _TinyCloudNode {
1815
2078
  capabilityRegistry: this._capabilityRegistry,
1816
2079
  userDid: this.did,
1817
2080
  createKVService: (spaceId) => {
1818
- const kvService = new KVService2({});
2081
+ return this.createSpaceScopedKVService(spaceId);
2082
+ },
2083
+ createVaultService: (spaceId) => {
2084
+ const kvService = this.createSpaceScopedKVService(spaceId);
2085
+ const vaultService = this.createVaultService(spaceId, kvService);
1819
2086
  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);
2087
+ vaultService.initialize(this._serviceContext);
1830
2088
  }
1831
- return kvService;
2089
+ return vaultService;
1832
2090
  },
1833
2091
  // Enable space.delegations.create() via SIWE-based delegation
1834
2092
  createDelegation: async (params) => {
@@ -1885,7 +2143,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1885
2143
  * @internal
1886
2144
  */
1887
2145
  getSessionExpiry() {
1888
- const expirationMs = this.config.sessionExpirationMs ?? 60 * 60 * 1e3;
2146
+ const expirationMs = this.config.sessionExpirationMs ?? DEFAULT_SESSION_EXPIRATION_MS;
1889
2147
  return new Date(Date.now() + expirationMs);
1890
2148
  }
1891
2149
  /**
@@ -2042,6 +2300,34 @@ var _TinyCloudNode = class _TinyCloudNode {
2042
2300
  }
2043
2301
  return this._sql;
2044
2302
  }
2303
+ /**
2304
+ * Get an SQL service scoped to a specific space.
2305
+ *
2306
+ * Mirrors {@link SpaceService}'s per-space KV factory: clones the active
2307
+ * service context and overrides its session's spaceId so that subsequent
2308
+ * `sql/<dbName>/<action>` invocations route to that space. Useful when
2309
+ * the caller already holds a delegation covering the target space (e.g.
2310
+ * via {@link grantRuntimePermissions} or {@link useRuntimeDelegation})
2311
+ * but the SDK's per-space SQL surface isn't otherwise exposed.
2312
+ *
2313
+ * Does NOT auto-create the space.
2314
+ *
2315
+ * @param spaceId - Full space URI (`tinycloud:pkh:eip155:<chain>:<addr>:<name>`).
2316
+ */
2317
+ sqlForSpace(spaceId) {
2318
+ if (!this._serviceContext || !this._serviceContext.session) {
2319
+ throw new Error("Not signed in. Call signIn() first.");
2320
+ }
2321
+ const sql = new SQLService2({});
2322
+ const spaceScopedContext = new ServiceContext2({
2323
+ invoke: this._serviceContext.invoke,
2324
+ fetch: this._serviceContext.fetch,
2325
+ hosts: this._serviceContext.hosts
2326
+ });
2327
+ spaceScopedContext.setSession({ ...this._serviceContext.session, spaceId });
2328
+ sql.initialize(spaceScopedContext);
2329
+ return sql;
2330
+ }
2045
2331
  /**
2046
2332
  * DuckDB database operations on this user's space.
2047
2333
  */
@@ -2065,6 +2351,33 @@ var _TinyCloudNode = class _TinyCloudNode {
2065
2351
  }
2066
2352
  return this._vault;
2067
2353
  }
2354
+ /**
2355
+ * App-facing secrets API backed by the `secrets` space vault.
2356
+ */
2357
+ get secrets() {
2358
+ if (!this._spaceService) {
2359
+ throw new Error("Not signed in. Call signIn() first.");
2360
+ }
2361
+ if (!this._secrets) {
2362
+ this._secrets = new NodeSecretsService({
2363
+ getService: () => this.getBaseSecrets(),
2364
+ getManifest: () => this.manifest,
2365
+ grantPermissions: (additional) => this.grantRuntimePermissions(additional),
2366
+ canEscalate: () => this.signer !== void 0 && this.tc !== void 0,
2367
+ getUnlockSigner: () => this.signer ?? void 0
2368
+ });
2369
+ }
2370
+ return this._secrets;
2371
+ }
2372
+ getBaseSecrets() {
2373
+ if (!this._spaceService) {
2374
+ throw new Error("Not signed in. Call signIn() first.");
2375
+ }
2376
+ if (!this._baseSecrets) {
2377
+ this._baseSecrets = new SecretsService(() => this.space("secrets").vault);
2378
+ }
2379
+ return this._baseSecrets;
2380
+ }
2068
2381
  /**
2069
2382
  * Hooks write stream subscription API.
2070
2383
  */
@@ -2135,6 +2448,171 @@ var _TinyCloudNode = class _TinyCloudNode {
2135
2448
  }
2136
2449
  };
2137
2450
  }
2451
+ /**
2452
+ * Check whether the current session or an approved runtime delegation covers
2453
+ * every requested permission.
2454
+ */
2455
+ hasRuntimePermissions(permissions) {
2456
+ const session = this.auth?.tinyCloudSession;
2457
+ if (!session || !Array.isArray(permissions) || permissions.length === 0) {
2458
+ return false;
2459
+ }
2460
+ const expanded = this.expandPermissionEntries(permissions);
2461
+ if (this.sessionCoversPermissionEntries(session, expanded)) {
2462
+ return true;
2463
+ }
2464
+ return this.findRuntimeGrantsForPermissionEntries(expanded, session).length > 0;
2465
+ }
2466
+ /**
2467
+ * Return installed runtime permission delegations. When `permissions` is
2468
+ * provided, only delegations currently covering those permissions are
2469
+ * returned. Base-session manifest permissions are not represented here.
2470
+ */
2471
+ getRuntimePermissionDelegations(permissions) {
2472
+ this.pruneExpiredRuntimePermissionGrants();
2473
+ if (permissions === void 0) {
2474
+ return this.runtimePermissionGrants.map((grant) => grant.delegation);
2475
+ }
2476
+ const session = this.auth?.tinyCloudSession;
2477
+ if (!session || !Array.isArray(permissions) || permissions.length === 0) {
2478
+ return [];
2479
+ }
2480
+ const expanded = this.expandPermissionEntries(permissions);
2481
+ return this.findRuntimeGrantsForPermissionEntries(expanded, session).map(
2482
+ (grant) => grant.delegation
2483
+ );
2484
+ }
2485
+ /**
2486
+ * Install a portable runtime permission delegation into this SDK instance so
2487
+ * matching service calls and downstream `delegateTo()` calls can use it.
2488
+ */
2489
+ async useRuntimeDelegation(delegation) {
2490
+ const session = this.auth?.tinyCloudSession;
2491
+ if (!session) {
2492
+ throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
2493
+ }
2494
+ if (delegation.expiry.getTime() <= Date.now()) {
2495
+ throw new SessionExpiredError(delegation.expiry);
2496
+ }
2497
+ const expectedDids = /* @__PURE__ */ new Set([session.verificationMethod, this.sessionDid]);
2498
+ if (!expectedDids.has(delegation.delegateDID)) {
2499
+ throw new Error(
2500
+ `Runtime delegation targets ${delegation.delegateDID} but this session key is ${session.verificationMethod}.`
2501
+ );
2502
+ }
2503
+ const targetHost = delegation.host ?? this.config.host;
2504
+ const activateResult = await activateSessionWithHost2(
2505
+ targetHost,
2506
+ delegation.delegationHeader
2507
+ );
2508
+ if (!activateResult.success) {
2509
+ throw new Error(
2510
+ `Failed to activate runtime permission delegation: ${activateResult.error}`
2511
+ );
2512
+ }
2513
+ this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
2514
+ (grant) => grant.delegation.cid !== delegation.cid
2515
+ );
2516
+ this.runtimePermissionGrants.push(
2517
+ this.runtimeGrantFromDelegation(delegation, session)
2518
+ );
2519
+ }
2520
+ /**
2521
+ * Store additional permissions as narrow delegations to the current session
2522
+ * key. Future service invocations automatically use a stored delegation when
2523
+ * its `(space, service, path, action)` covers the request.
2524
+ */
2525
+ async grantRuntimePermissions(permissions, options) {
2526
+ if (!Array.isArray(permissions) || permissions.length === 0) {
2527
+ throw new Error("grantRuntimePermissions requires a non-empty permissions array");
2528
+ }
2529
+ const session = this.auth?.tinyCloudSession;
2530
+ if (!session) {
2531
+ throw new SessionExpiredError(/* @__PURE__ */ new Date(0));
2532
+ }
2533
+ const sessionExpiry = extractSiweExpiration(session.siwe);
2534
+ if (sessionExpiry !== void 0) {
2535
+ const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
2536
+ if (sessionExpiry.getTime() <= Date.now() + marginMs) {
2537
+ throw new SessionExpiredError(sessionExpiry);
2538
+ }
2539
+ }
2540
+ const expanded = this.expandPermissionEntries(permissions);
2541
+ if (this.sessionCoversPermissionEntries(session, expanded)) {
2542
+ return [];
2543
+ }
2544
+ const existingGrants = this.findRuntimeGrantsForPermissionEntries(expanded, session);
2545
+ if (existingGrants.length > 0) {
2546
+ return existingGrants.map((grant) => grant.delegation);
2547
+ }
2548
+ if (!this.signer) {
2549
+ throw new Error(
2550
+ "grantRuntimePermissions requires wallet mode with a signer or privateKey."
2551
+ );
2552
+ }
2553
+ const bySpace = /* @__PURE__ */ new Map();
2554
+ for (const entry of expanded) {
2555
+ const spaceId = this.resolvePermissionSpace(entry.space, session);
2556
+ const current = bySpace.get(spaceId) ?? [];
2557
+ current.push(entry);
2558
+ bySpace.set(spaceId, current);
2559
+ }
2560
+ const now = /* @__PURE__ */ new Date();
2561
+ const requestedExpiryMs = resolveExpiryMs(options?.expiry);
2562
+ let expiresAt = new Date(now.getTime() + requestedExpiryMs);
2563
+ if (sessionExpiry !== void 0 && sessionExpiry < expiresAt) {
2564
+ expiresAt = sessionExpiry;
2565
+ }
2566
+ const delegations = [];
2567
+ for (const [spaceId, entries] of bySpace) {
2568
+ const abilities = this.permissionsToAbilities(entries);
2569
+ const prepared = this.wasmBindings.prepareSession({
2570
+ abilities,
2571
+ address: this.wasmBindings.ensureEip55(session.address),
2572
+ chainId: session.chainId,
2573
+ domain: this.siweDomain,
2574
+ issuedAt: now.toISOString(),
2575
+ expirationTime: expiresAt.toISOString(),
2576
+ spaceId,
2577
+ jwk: session.jwk
2578
+ });
2579
+ const signature = await this.signer.signMessage(prepared.siwe);
2580
+ const delegatedSession = this.wasmBindings.completeSessionSetup({
2581
+ ...prepared,
2582
+ signature
2583
+ });
2584
+ const activateResult = await activateSessionWithHost2(
2585
+ this.config.host,
2586
+ delegatedSession.delegationHeader
2587
+ );
2588
+ if (!activateResult.success) {
2589
+ throw new Error(
2590
+ `Failed to activate runtime permission delegation: ${activateResult.error}`
2591
+ );
2592
+ }
2593
+ const delegation = this.runtimeDelegationFromSession(
2594
+ delegatedSession,
2595
+ entries,
2596
+ spaceId,
2597
+ session,
2598
+ expiresAt
2599
+ );
2600
+ this.runtimePermissionGrants.push({
2601
+ session: {
2602
+ delegationHeader: delegatedSession.delegationHeader,
2603
+ delegationCid: delegatedSession.delegationCid,
2604
+ spaceId,
2605
+ verificationMethod: session.verificationMethod,
2606
+ jwk: session.jwk
2607
+ },
2608
+ delegation,
2609
+ operations: this.permissionOperations(entries, spaceId),
2610
+ expiresAt
2611
+ });
2612
+ delegations.push(delegation);
2613
+ }
2614
+ return delegations;
2615
+ }
2138
2616
  /**
2139
2617
  * Get the DelegationManager for delegation CRUD operations.
2140
2618
  *
@@ -2203,6 +2681,12 @@ var _TinyCloudNode = class _TinyCloudNode {
2203
2681
  get spaceService() {
2204
2682
  return this.spaces;
2205
2683
  }
2684
+ /**
2685
+ * Get a Space object by short name or full URI.
2686
+ */
2687
+ space(nameOrUri) {
2688
+ return this.spaces.get(nameOrUri);
2689
+ }
2206
2690
  /**
2207
2691
  * Get the SharingService for creating and receiving v2 sharing links.
2208
2692
  *
@@ -2270,7 +2754,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2270
2754
  ];
2271
2755
  const abilities = { kv: { "": kvActions } };
2272
2756
  const now = /* @__PURE__ */ new Date();
2273
- const expiryMs = 60 * 60 * 1e3;
2757
+ const expiryMs = EXPIRY3.EPHEMERAL_MS;
2274
2758
  const expirationTime = new Date(now.getTime() + expiryMs);
2275
2759
  const prepared = this.wasmBindings.prepareSession({
2276
2760
  abilities,
@@ -2317,7 +2801,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2317
2801
  if (this._serviceContext) {
2318
2802
  const publicKV = new KVService2({ prefix: "" });
2319
2803
  const publicContext = new ServiceContext2({
2320
- invoke: this.wasmBindings.invoke,
2804
+ invoke: this.invokeWithRuntimePermissions,
2321
2805
  fetch: this._serviceContext.fetch,
2322
2806
  hosts: this._serviceContext.hosts
2323
2807
  });
@@ -2405,8 +2889,9 @@ var _TinyCloudNode = class _TinyCloudNode {
2405
2889
  * Issue a delegation using the capability-chain flow.
2406
2890
  *
2407
2891
  * 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
2892
+ * session's recap, or of one installed runtime permission delegation,
2893
+ * the delegation is signed by the session key via WASM no wallet
2894
+ * prompt. When at least one is NOT derivable, a
2410
2895
  * {@link PermissionNotInManifestError} is raised (carrying the
2411
2896
  * missing entries) so the caller can trigger an escalation flow
2412
2897
  * (e.g. `TinyCloudWeb.requestPermissions`). Passing
@@ -2456,10 +2941,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2456
2941
  "delegateTo requires a non-empty permissions array"
2457
2942
  );
2458
2943
  }
2459
- const expandedEntries = permissions.map((entry) => ({
2460
- ...entry,
2461
- actions: expandActionShortNames(entry.service, entry.actions)
2462
- }));
2944
+ const expandedEntries = this.expandPermissionEntries(permissions);
2463
2945
  const now = /* @__PURE__ */ new Date();
2464
2946
  const expiryMs = resolveExpiryMs(options?.expiry);
2465
2947
  const expirationTime = new Date(now.getTime() + expiryMs);
@@ -2486,6 +2968,23 @@ var _TinyCloudNode = class _TinyCloudNode {
2486
2968
  );
2487
2969
  const { subset, missing } = isCapabilitySubset(expandedEntries, granted);
2488
2970
  if (!subset) {
2971
+ const runtimeGrant = this.findGrantForOperations(
2972
+ this.permissionEntriesToOperations(expandedEntries, session)
2973
+ );
2974
+ if (runtimeGrant) {
2975
+ const marginMs = _TinyCloudNode.SESSION_EXPIRY_SAFETY_MARGIN_MS;
2976
+ if (runtimeGrant.expiresAt.getTime() <= Date.now() + marginMs) {
2977
+ throw new SessionExpiredError(runtimeGrant.expiresAt);
2978
+ }
2979
+ const runtimeExpiration = runtimeGrant.expiresAt < effectiveExpiration ? runtimeGrant.expiresAt : effectiveExpiration;
2980
+ const delegation2 = await this.createDelegationViaRuntimeGrant(
2981
+ did,
2982
+ expandedEntries,
2983
+ runtimeExpiration,
2984
+ runtimeGrant
2985
+ );
2986
+ return { delegation: delegation2, prompted: false };
2987
+ }
2489
2988
  throw new PermissionNotInManifestError(missing, granted);
2490
2989
  }
2491
2990
  const delegation = await this.createDelegationViaWasmPath(
@@ -2630,6 +3129,41 @@ var _TinyCloudNode = class _TinyCloudNode {
2630
3129
  host: this.config.host
2631
3130
  };
2632
3131
  }
3132
+ async createDelegationViaRuntimeGrant(did, entries, expirationTime, grant) {
3133
+ const result = this.createDelegationWrapper({
3134
+ session: grant.session,
3135
+ delegateDID: did,
3136
+ spaceId: grant.session.spaceId,
3137
+ abilities: this.permissionsToAbilities(entries),
3138
+ expirationSecs: Math.floor(expirationTime.getTime() / 1e3)
3139
+ });
3140
+ const primary = result.resources[0];
3141
+ const delegationHeader = { Authorization: result.delegation };
3142
+ const targetHost = grant.delegation.host ?? this.config.host;
3143
+ const activateResult = await activateSessionWithHost2(
3144
+ targetHost,
3145
+ delegationHeader
3146
+ );
3147
+ if (!activateResult.success) {
3148
+ throw new Error(
3149
+ `Failed to activate delegation with host: ${activateResult.error}`
3150
+ );
3151
+ }
3152
+ return {
3153
+ cid: result.cid,
3154
+ delegationHeader,
3155
+ spaceId: grant.session.spaceId,
3156
+ path: primary.path,
3157
+ actions: primary.actions,
3158
+ resources: result.resources,
3159
+ disableSubDelegation: false,
3160
+ expiry: result.expiry,
3161
+ delegateDID: did,
3162
+ ownerAddress: grant.delegation.ownerAddress,
3163
+ chainId: grant.delegation.chainId,
3164
+ host: targetHost
3165
+ };
3166
+ }
2633
3167
  resolvePermissionSpace(space, session) {
2634
3168
  if (space === void 0) {
2635
3169
  return this.wasmBindings.makeSpaceId(
@@ -2646,6 +3180,220 @@ var _TinyCloudNode = class _TinyCloudNode {
2646
3180
  }
2647
3181
  return this.wasmBindings.makeSpaceId(session.address, session.chainId, space);
2648
3182
  }
3183
+ expandPermissionEntries(permissions) {
3184
+ return expandPermissionEntriesCore(permissions);
3185
+ }
3186
+ shortServiceName(service) {
3187
+ const short = SERVICE_LONG_TO_SHORT[service];
3188
+ if (short === void 0) {
3189
+ throw new Error(
3190
+ `unknown service '${service}' \u2014 no short-form mapping`
3191
+ );
3192
+ }
3193
+ return short;
3194
+ }
3195
+ permissionsToAbilities(entries) {
3196
+ const abilities = {};
3197
+ for (const entry of entries) {
3198
+ const service = this.shortServiceName(entry.service);
3199
+ abilities[service] ?? (abilities[service] = {});
3200
+ const existing = abilities[service][entry.path] ?? [];
3201
+ const seen = new Set(existing);
3202
+ for (const action of entry.actions) {
3203
+ if (!seen.has(action)) {
3204
+ existing.push(action);
3205
+ seen.add(action);
3206
+ }
3207
+ }
3208
+ abilities[service][entry.path] = existing;
3209
+ }
3210
+ return abilities;
3211
+ }
3212
+ permissionOperations(entries, spaceId) {
3213
+ return entries.flatMap((entry) => {
3214
+ const service = this.shortServiceName(entry.service);
3215
+ return entry.actions.map((action) => ({
3216
+ spaceId,
3217
+ service,
3218
+ path: entry.path,
3219
+ action
3220
+ }));
3221
+ });
3222
+ }
3223
+ sessionCoversPermissionEntries(session, entries) {
3224
+ try {
3225
+ const granted = parseRecapCapabilities(
3226
+ (siwe) => this.wasmBindings.parseRecapFromSiwe(siwe),
3227
+ session.siwe
3228
+ );
3229
+ return isCapabilitySubset(entries, granted).subset;
3230
+ } catch {
3231
+ return false;
3232
+ }
3233
+ }
3234
+ permissionEntriesToOperations(entries, session) {
3235
+ return entries.flatMap((entry) => {
3236
+ const spaceId = this.resolvePermissionSpace(entry.space, session);
3237
+ const service = this.shortServiceName(entry.service);
3238
+ return entry.actions.map((action) => ({
3239
+ spaceId,
3240
+ service,
3241
+ path: entry.path,
3242
+ action
3243
+ }));
3244
+ });
3245
+ }
3246
+ findRuntimeGrantsForPermissionEntries(entries, session) {
3247
+ const grants = [];
3248
+ const operations = this.permissionEntriesToOperations(entries, session);
3249
+ if (operations.length === 0) {
3250
+ return grants;
3251
+ }
3252
+ for (const operation of operations) {
3253
+ const grant = this.findGrantForOperation(operation);
3254
+ if (!grant) {
3255
+ return [];
3256
+ }
3257
+ if (!grants.includes(grant)) {
3258
+ grants.push(grant);
3259
+ }
3260
+ }
3261
+ return grants;
3262
+ }
3263
+ runtimeDelegationFromSession(delegatedSession, entries, spaceId, session, expiresAt) {
3264
+ const resources = this.delegatedResourcesForEntries(entries, spaceId);
3265
+ const primary = resources[0];
3266
+ return {
3267
+ cid: delegatedSession.delegationCid,
3268
+ delegationHeader: delegatedSession.delegationHeader,
3269
+ spaceId,
3270
+ path: primary.path,
3271
+ actions: primary.actions,
3272
+ resources,
3273
+ disableSubDelegation: false,
3274
+ expiry: expiresAt,
3275
+ delegateDID: session.verificationMethod,
3276
+ ownerAddress: session.address,
3277
+ chainId: session.chainId,
3278
+ host: this.config.host
3279
+ };
3280
+ }
3281
+ runtimeGrantFromDelegation(delegation, session) {
3282
+ const operations = this.operationsFromDelegation(delegation);
3283
+ return {
3284
+ session: {
3285
+ delegationHeader: delegation.delegationHeader,
3286
+ delegationCid: delegation.cid,
3287
+ spaceId: delegation.spaceId,
3288
+ verificationMethod: session.verificationMethod,
3289
+ jwk: session.jwk
3290
+ },
3291
+ delegation,
3292
+ operations,
3293
+ expiresAt: delegation.expiry
3294
+ };
3295
+ }
3296
+ delegatedResourcesForEntries(entries, spaceId) {
3297
+ return entries.map((entry) => ({
3298
+ service: this.shortServiceName(entry.service),
3299
+ space: spaceId,
3300
+ path: entry.path,
3301
+ actions: [...entry.actions]
3302
+ }));
3303
+ }
3304
+ operationsFromDelegation(delegation) {
3305
+ const resources = delegation.resources !== void 0 && delegation.resources.length > 0 ? delegation.resources : this.flatDelegationResources(delegation);
3306
+ return resources.flatMap(
3307
+ (resource) => resource.actions.map((action) => ({
3308
+ spaceId: resource.space,
3309
+ service: this.invocationServiceName(resource.service),
3310
+ path: resource.path,
3311
+ action
3312
+ }))
3313
+ );
3314
+ }
3315
+ flatDelegationResources(delegation) {
3316
+ const byService = /* @__PURE__ */ new Map();
3317
+ for (const action of delegation.actions) {
3318
+ const service = this.shortServiceName(action.split("/")[0]);
3319
+ const actions = byService.get(service) ?? [];
3320
+ actions.push(action);
3321
+ byService.set(service, actions);
3322
+ }
3323
+ return [...byService.entries()].map(([service, actions]) => ({
3324
+ service,
3325
+ space: delegation.spaceId,
3326
+ path: delegation.path,
3327
+ actions
3328
+ }));
3329
+ }
3330
+ selectInvocationSession(fallback, service, path, action) {
3331
+ const grant = this.findGrantForOperation({
3332
+ spaceId: fallback.spaceId,
3333
+ service: this.invocationServiceName(service),
3334
+ path,
3335
+ action
3336
+ });
3337
+ return grant?.session ?? fallback;
3338
+ }
3339
+ findGrantForOperations(operations) {
3340
+ if (operations.length === 0) {
3341
+ return void 0;
3342
+ }
3343
+ this.pruneExpiredRuntimePermissionGrants();
3344
+ return this.runtimePermissionGrants.find((grant) => {
3345
+ return operations.every(
3346
+ (operation) => grant.operations.some(
3347
+ (granted) => this.operationCovers(granted, operation)
3348
+ )
3349
+ );
3350
+ });
3351
+ }
3352
+ findGrantForOperation(operation) {
3353
+ return this.findGrantForOperations([operation]);
3354
+ }
3355
+ pruneExpiredRuntimePermissionGrants() {
3356
+ const now = Date.now();
3357
+ this.runtimePermissionGrants = this.runtimePermissionGrants.filter(
3358
+ (grant) => grant.expiresAt.getTime() > now
3359
+ );
3360
+ }
3361
+ operationCovers(granted, requested) {
3362
+ return granted.spaceId === requested.spaceId && granted.service === requested.service && this.actionContains(granted.action, requested.action) && this.pathContains(granted.path, requested.path);
3363
+ }
3364
+ actionContains(grantedAction, requestedAction) {
3365
+ if (grantedAction === requestedAction) {
3366
+ return true;
3367
+ }
3368
+ if (grantedAction.endsWith("/*")) {
3369
+ const prefix = grantedAction.slice(0, -2);
3370
+ return requestedAction.startsWith(`${prefix}/`);
3371
+ }
3372
+ return false;
3373
+ }
3374
+ invocationServiceName(service) {
3375
+ return service.startsWith("tinycloud.") ? this.shortServiceName(service) : service;
3376
+ }
3377
+ pathContains(grantedPath, requestedPath) {
3378
+ if (grantedPath === "" || grantedPath === "/") {
3379
+ return true;
3380
+ }
3381
+ if (grantedPath.endsWith("/**")) {
3382
+ return requestedPath.startsWith(grantedPath.slice(0, -3));
3383
+ }
3384
+ if (grantedPath.endsWith("/*")) {
3385
+ const prefix = grantedPath.slice(0, -2);
3386
+ if (!requestedPath.startsWith(prefix)) {
3387
+ return false;
3388
+ }
3389
+ const remainder = requestedPath.slice(prefix.length);
3390
+ return !remainder.includes("/") || remainder === "/";
3391
+ }
3392
+ if (grantedPath.endsWith("/")) {
3393
+ return requestedPath.startsWith(grantedPath);
3394
+ }
3395
+ return grantedPath === requestedPath;
3396
+ }
2649
3397
  /**
2650
3398
  * Issue a delegation via the legacy wallet-signed SIWE path for a single
2651
3399
  * {@link PermissionEntry}. Shares the implementation with the public
@@ -3033,15 +3781,18 @@ import {
3033
3781
  ACCOUNT_REGISTRY_SPACE as ACCOUNT_REGISTRY_SPACE2,
3034
3782
  DEFAULT_MANIFEST_SPACE as DEFAULT_MANIFEST_SPACE2,
3035
3783
  DEFAULT_MANIFEST_VERSION,
3784
+ VAULT_PERMISSION_SERVICE,
3036
3785
  PermissionNotInManifestError as PermissionNotInManifestError2,
3037
3786
  SessionExpiredError as SessionExpiredError2,
3038
3787
  ManifestValidationError,
3039
3788
  composeManifestRequest as composeManifestRequest2,
3040
- resolveManifest,
3789
+ resolveManifest as resolveManifest2,
3041
3790
  validateManifest,
3042
3791
  loadManifest,
3043
3792
  isCapabilitySubset as isCapabilitySubset2,
3044
- expandActionShortNames as expandActionShortNames2,
3793
+ expandActionShortNames,
3794
+ expandPermissionEntries,
3795
+ expandPermissionEntry,
3045
3796
  parseExpiry as parseExpiry2,
3046
3797
  resourceCapabilitiesToSpaceAbilitiesMap as resourceCapabilitiesToSpaceAbilitiesMap2
3047
3798
  } from "@tinycloud/sdk-core";
@@ -3066,7 +3817,16 @@ function deserializeDelegation(data) {
3066
3817
  import { KVService as KVService3, PrefixedKVService } from "@tinycloud/sdk-core";
3067
3818
  import { SQLService as SQLService3, SQLAction, DatabaseHandle } from "@tinycloud/sdk-core";
3068
3819
  import { DuckDbService as DuckDbService3, DuckDbDatabaseHandle, DuckDbAction } from "@tinycloud/sdk-core";
3069
- import { DataVaultService as DataVaultService2, VaultHeaders, VaultPublicSpaceKVActions, createVaultCrypto as createVaultCrypto2 } from "@tinycloud/sdk-core";
3820
+ import {
3821
+ DataVaultService as DataVaultService2,
3822
+ VaultHeaders,
3823
+ VaultPublicSpaceKVActions,
3824
+ createVaultCrypto as createVaultCrypto2,
3825
+ SecretsService as SecretsService2,
3826
+ SECRET_NAME_RE,
3827
+ canonicalizeSecretScope,
3828
+ resolveSecretPath as resolveSecretPath2
3829
+ } from "@tinycloud/sdk-core";
3070
3830
  import {
3071
3831
  DelegationManager as DelegationManager2,
3072
3832
  SharingService as SharingService2,
@@ -3118,8 +3878,10 @@ export {
3118
3878
  PermissionNotInManifestError2 as PermissionNotInManifestError,
3119
3879
  PrefixedKVService,
3120
3880
  ProtocolMismatchError,
3881
+ SECRET_NAME_RE,
3121
3882
  SQLAction,
3122
3883
  SQLService3 as SQLService,
3884
+ SecretsService2 as SecretsService,
3123
3885
  ServiceContext3 as ServiceContext,
3124
3886
  SessionExpiredError2 as SessionExpiredError,
3125
3887
  SharingService2 as SharingService,
@@ -3130,11 +3892,13 @@ export {
3130
3892
  TinyCloud2 as TinyCloud,
3131
3893
  TinyCloudNode,
3132
3894
  UnsupportedFeatureError2 as UnsupportedFeatureError,
3895
+ VAULT_PERMISSION_SERVICE,
3133
3896
  VaultHeaders,
3134
3897
  VaultPublicSpaceKVActions,
3135
3898
  VersionCheckError,
3136
3899
  WasmKeyProvider,
3137
3900
  buildSpaceUri,
3901
+ canonicalizeSecretScope,
3138
3902
  checkNodeInfo2 as checkNodeInfo,
3139
3903
  composeManifestRequest2 as composeManifestRequest,
3140
3904
  createCapabilityKeyRegistry,
@@ -3145,13 +3909,16 @@ export {
3145
3909
  defaultSignStrategy,
3146
3910
  defaultSpaceCreationHandler,
3147
3911
  deserializeDelegation,
3148
- expandActionShortNames2 as expandActionShortNames,
3912
+ expandActionShortNames,
3913
+ expandPermissionEntries,
3914
+ expandPermissionEntry,
3149
3915
  isCapabilitySubset2 as isCapabilitySubset,
3150
3916
  loadManifest,
3151
3917
  makePublicSpaceId2 as makePublicSpaceId,
3152
3918
  parseExpiry2 as parseExpiry,
3153
3919
  parseSpaceUri,
3154
- resolveManifest,
3920
+ resolveManifest2 as resolveManifest,
3921
+ resolveSecretPath2 as resolveSecretPath,
3155
3922
  resourceCapabilitiesToSpaceAbilitiesMap2 as resourceCapabilitiesToSpaceAbilitiesMap,
3156
3923
  serializeDelegation,
3157
3924
  validateManifest