@tinycloud/node-sdk 2.11.0-beta.0 → 2.11.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { ISessionStorage, PersistedSessionData, AutoSignStrategy, AutoRejectStrategy, CallbackStrategy, IUserAuthorization, ISigner, ISpaceCreationHandler, LocalNodeIdentityStore, IWasmBindings, ISessionManager, SiweConfig, Manifest, ComposedManifestRequest, ClientSession, TinyCloudSession, Extension, SignRequest, SignInOptions, Delegation, DelegatedResource, PermissionEntry, TelemetryConfig, IKVService, ISQLService, IDuckDbService, IHooksService, INotificationHandler, IENSResolver, AccountService, RegisterOwnerSharePolicyParams, OwnerSharePolicyRegistrationReceipt, ShareDeliveryAuthorizationReceipt, IDataVaultService, IEncryptionService, NetworkDescriptor, ISecretsService, ICapabilityKeyRegistry, DelegationManager, ISpaceService, ISpace, ISharingService, CreateDelegationParams, DelegationResult, DelegationRevocationReceipt, DelegationStatus, ResolvedDelegate, KeyProvider, JWK } from '@tinycloud/sdk-core';
1
+ import { ISessionStorage, PersistedSessionData, AutoSignStrategy, AutoRejectStrategy, CallbackStrategy, IUserAuthorization, ISigner, ISpaceCreationHandler, LocalNodeIdentityStore, IWasmBindings, ISessionManager, SiweConfig, Manifest, ComposedManifestRequest, ClientSession, TinyCloudSession, Extension, SignRequest, SignInOptions, Delegation, DelegatedResource, PermissionEntry, TelemetryConfig, IKVService, ISQLService, IDuckDbService, IHooksService, CreateOwnerDelegationParams as CreateOwnerDelegationParams$1, OwnerDelegationReceipt as OwnerDelegationReceipt$1, INotificationHandler, IENSResolver, AccountService, RegisterOwnerSharePolicyParams, OwnerSharePolicyRegistrationReceipt, ShareDeliveryAuthorizationReceipt, IDataVaultService, IEncryptionService, NetworkDescriptor, ISecretsService, ICapabilityKeyRegistry, DelegationManager, ISpaceService, ISpace, ISharingService, CreateDelegationParams, DelegationResult, DelegationRevocationReceipt, DelegationStatus, ResolvedDelegate, KeyProvider, JWK } from '@tinycloud/sdk-core';
2
2
  import { EventEmitter } from 'events';
3
3
  import { InvokeFunction, InvokeAnyFunction } from '@tinycloud/sdk-services';
4
4
 
@@ -319,6 +319,12 @@ declare class NodeUserAuthorization implements IUserAuthorization {
319
319
  private _address?;
320
320
  private _chainId?;
321
321
  private _nodeFeatures;
322
+ /**
323
+ * `nodeId` from the `GET /info` performed during sign-in/restore, tagged with
324
+ * the host it came from. Callers that need the node DID (encryption-network
325
+ * admin invocations) reuse this instead of re-fetching `/info`.
326
+ */
327
+ private _nodeInfoIdentity?;
322
328
  private _lastActivationSkippedSpaceIds;
323
329
  constructor(config: NodeUserAuthorizationConfig);
324
330
  /**
@@ -387,6 +393,13 @@ declare class NodeUserAuthorization implements IUserAuthorization {
387
393
  private requireTinyCloudHosts;
388
394
  private get primaryTinyCloudHost();
389
395
  get nodeFeatures(): string[];
396
+ /**
397
+ * The node DID reported by `GET /info` for `host`, if the sign-in (or
398
+ * restore) that populated it targeted that same host. Returns `undefined`
399
+ * for any other host, and for nodes whose `/info` omits `nodeId`, so callers
400
+ * fall back to fetching it themselves rather than using a foreign node's DID.
401
+ */
402
+ nodeIdForHost(host: string): string | undefined;
390
403
  get lastActivationSkippedSpaceIds(): string[];
391
404
  /**
392
405
  * Compute the `abilities` map the WASM `prepareSession` call should
@@ -856,19 +869,9 @@ type SecretReadResult = {
856
869
  } | {
857
870
  status: "invalid_payload";
858
871
  };
859
- interface CreateOwnerDelegationParams {
860
- readonly delegateDid: string;
861
- readonly spaceId: string;
862
- readonly path: string;
863
- readonly actions: readonly string[];
864
- readonly expiresAt: Date;
865
- }
866
- interface OwnerDelegationReceipt {
872
+ type CreateOwnerDelegationParams = CreateOwnerDelegationParams$1;
873
+ interface OwnerDelegationReceipt extends OwnerDelegationReceipt$1 {
867
874
  readonly delegation: Delegation;
868
- /** Exact signed DAG-CBOR bytes submitted in the Authorization header. */
869
- readonly signedDagCbor: Uint8Array;
870
- /** Locally derived by the node WASM implementation; this is delegation identity. */
871
- readonly delegationCid: string;
872
875
  readonly nodeReceipt: {
873
876
  /** Raw /delegate response CID: a commit-event id, not delegation identity. */
874
877
  readonly commitEventCid?: string;
@@ -997,6 +1000,21 @@ interface RuntimePermissionGrantOptions {
997
1000
  /** Override expiry. ms-format string ("7d", "1h") or raw milliseconds. */
998
1001
  expiry?: string | number;
999
1002
  }
1003
+ interface EnsureEncryptionNetworkOptions {
1004
+ /**
1005
+ * Skip the `GET /encryption/networks/{id}` existence probe and go straight
1006
+ * to the create.
1007
+ *
1008
+ * The probe pays for itself only when the network already exists. On a
1009
+ * freshly bootstrapped account it is a guaranteed 404, so it costs a round
1010
+ * trip on the cold sign-in path and saves nothing. Creating without it is
1011
+ * safe: the server answers `409 Conflict` if the network is already there,
1012
+ * which {@link TinyCloudNode.createEncryptionNetwork} resolves by reading the
1013
+ * existing descriptor. Set this only when the caller knows the account was
1014
+ * just provisioned.
1015
+ */
1016
+ assumeMissing?: boolean;
1017
+ }
1000
1018
  /**
1001
1019
  * High-level TinyCloud API for Node.js environments.
1002
1020
  *
@@ -1054,6 +1072,12 @@ declare class TinyCloudNode {
1054
1072
  * so this avoids re-parsing the recap on every registration.
1055
1073
  */
1056
1074
  private _recapOperationsCache?;
1075
+ /**
1076
+ * Owned space ids this sign-in has already confirmed are hosted. Consulted by
1077
+ * {@link ensureOwnedSpaceHostedById}; cleared on every {@link signIn} because
1078
+ * hosting is confirmed against the session that was active at the time.
1079
+ */
1080
+ private readonly confirmedHostedSpaceIds;
1057
1081
  /**
1058
1082
  * TinyCloudSession captured by {@link restoreSession} when there's no
1059
1083
  * auth-layer signer available (session-only mode used by OpenKey-backed
@@ -1262,6 +1286,22 @@ declare class TinyCloudNode {
1262
1286
  private withAccountRegistryRetry;
1263
1287
  private requestedEncryptionNetworkIds;
1264
1288
  private ensureRequestedEncryptionNetworks;
1289
+ /**
1290
+ * Ensure one of this user's owned spaces is hosted, at most once per space
1291
+ * per sign-in.
1292
+ *
1293
+ * Account bootstrap funnels six calls through here (via
1294
+ * `AccountService.ensureAccountSpaceHosted`), and each one re-submits
1295
+ * `activateSessionWithHost` with the *primary* session. That session's recap
1296
+ * covers only `default` and `secrets`, so the account space appears in
1297
+ * neither `activated` nor `skipped` and the guard below returns immediately
1298
+ * — the repeat calls cannot change the outcome, they only cost round trips.
1299
+ *
1300
+ * The memo is per sign-in ({@link confirmedHostedSpaceIds} is cleared in
1301
+ * `signIn`) and only records confirmed successes. A wrong skip is
1302
+ * self-revealing: the next write to the space fails immediately and loudly
1303
+ * with `404 Space not found`.
1304
+ */
1265
1305
  private ensureOwnedSpaceHostedById;
1266
1306
  /**
1267
1307
  * Host one of this user's owned spaces by name (e.g. `"applications"`).
@@ -1472,6 +1512,14 @@ declare class TinyCloudNode {
1472
1512
  */
1473
1513
  private requireEncryptionSession;
1474
1514
  private createEncryptionCrypto;
1515
+ /**
1516
+ * The node DID for {@link config.host}.
1517
+ *
1518
+ * Sign-in already performs `GET /info` for the protocol-version check, and
1519
+ * that response carries `nodeId`, so reuse it rather than fetching `/info` a
1520
+ * second time. Only a value recorded for this exact host qualifies; older
1521
+ * nodes that omit `nodeId` fall through to the fetch.
1522
+ */
1475
1523
  private fetchNodeId;
1476
1524
  private signRawNetworkAuthorization;
1477
1525
  private fetchEncryptionNetworkAt;
@@ -1596,7 +1644,13 @@ declare class TinyCloudNode {
1596
1644
  get encryption(): IEncryptionService;
1597
1645
  getEncryptionNetwork(nameOrNetworkId?: string): Promise<NetworkDescriptor | null>;
1598
1646
  createEncryptionNetwork(name?: string): Promise<NetworkDescriptor>;
1599
- ensureEncryptionNetwork(nameOrNetworkId?: string): Promise<NetworkDescriptor>;
1647
+ /**
1648
+ * Ensure an encryption network exists, creating it if necessary.
1649
+ *
1650
+ * @param nameOrNetworkId - Network name or full `urn:tinycloud:encryption:` id.
1651
+ * @param options - See {@link EnsureEncryptionNetworkOptions}.
1652
+ */
1653
+ ensureEncryptionNetwork(nameOrNetworkId?: string, options?: EnsureEncryptionNetworkOptions): Promise<NetworkDescriptor>;
1600
1654
  /**
1601
1655
  * App-facing secrets API backed by the `secrets` space vault.
1602
1656
  */
@@ -2178,4 +2232,4 @@ declare class WasmKeyProvider implements KeyProvider {
2178
2232
  */
2179
2233
  declare function createWasmKeyProvider(sessionManager: SessionManagerWithListing): WasmKeyProvider;
2180
2234
 
2181
- export { type AuthDelegationArtifact as A, type CreateOwnerDelegationParams as C, type DelegateToOptions as D, FileSessionStorage as F, MemorySessionStorage as M, type NodeEventEmitterStrategy as N, type OwnerDelegationReceipt as O, type PortableDelegation as P, type RestorableSession as R, type SecretPermissionHint as S, TinyCloudNode as T, UnsupportedSessionRestoreError as U, type ValidatedRuntimeDelegation as V, WasmKeyProvider as W, type AuthRequestArtifact as a, type DelegateToResult as b, DelegatedAccess as c, type DelegationAuthority as d, NodeUserAuthorization as e, type NodeUserAuthorizationConfig as f, type RuntimeDelegationActivator as g, type RuntimePermissionGrantOptions as h, type SecretReadInput as i, type SecretReadResult as j, type SignStrategy as k, type TinyCloudNodeConfig as l, type WasmKeyProviderConfig as m, activateValidatedRuntimeDelegation as n, createWasmKeyProvider as o, defaultSignStrategy as p, deserializeDelegation as q, grantAuthRequest as r, serializeDelegation as s };
2235
+ export { type AuthDelegationArtifact as A, type CreateOwnerDelegationParams as C, type DelegateToOptions as D, type EnsureEncryptionNetworkOptions as E, FileSessionStorage as F, MemorySessionStorage as M, type NodeEventEmitterStrategy as N, type OwnerDelegationReceipt as O, type PortableDelegation as P, type RestorableSession as R, type SecretPermissionHint as S, TinyCloudNode as T, UnsupportedSessionRestoreError as U, type ValidatedRuntimeDelegation as V, WasmKeyProvider as W, type AuthRequestArtifact as a, type DelegateToResult as b, DelegatedAccess as c, type DelegationAuthority as d, NodeUserAuthorization as e, type NodeUserAuthorizationConfig as f, type RuntimeDelegationActivator as g, type RuntimePermissionGrantOptions as h, type SecretReadInput as i, type SecretReadResult as j, type SignStrategy as k, type TinyCloudNodeConfig as l, type WasmKeyProviderConfig as m, activateValidatedRuntimeDelegation as n, createWasmKeyProvider as o, defaultSignStrategy as p, deserializeDelegation as q, grantAuthRequest as r, serializeDelegation as s };
@@ -1,4 +1,4 @@
1
- import { ISessionStorage, PersistedSessionData, AutoSignStrategy, AutoRejectStrategy, CallbackStrategy, IUserAuthorization, ISigner, ISpaceCreationHandler, LocalNodeIdentityStore, IWasmBindings, ISessionManager, SiweConfig, Manifest, ComposedManifestRequest, ClientSession, TinyCloudSession, Extension, SignRequest, SignInOptions, Delegation, DelegatedResource, PermissionEntry, TelemetryConfig, IKVService, ISQLService, IDuckDbService, IHooksService, INotificationHandler, IENSResolver, AccountService, RegisterOwnerSharePolicyParams, OwnerSharePolicyRegistrationReceipt, ShareDeliveryAuthorizationReceipt, IDataVaultService, IEncryptionService, NetworkDescriptor, ISecretsService, ICapabilityKeyRegistry, DelegationManager, ISpaceService, ISpace, ISharingService, CreateDelegationParams, DelegationResult, DelegationRevocationReceipt, DelegationStatus, ResolvedDelegate, KeyProvider, JWK } from '@tinycloud/sdk-core';
1
+ import { ISessionStorage, PersistedSessionData, AutoSignStrategy, AutoRejectStrategy, CallbackStrategy, IUserAuthorization, ISigner, ISpaceCreationHandler, LocalNodeIdentityStore, IWasmBindings, ISessionManager, SiweConfig, Manifest, ComposedManifestRequest, ClientSession, TinyCloudSession, Extension, SignRequest, SignInOptions, Delegation, DelegatedResource, PermissionEntry, TelemetryConfig, IKVService, ISQLService, IDuckDbService, IHooksService, CreateOwnerDelegationParams as CreateOwnerDelegationParams$1, OwnerDelegationReceipt as OwnerDelegationReceipt$1, INotificationHandler, IENSResolver, AccountService, RegisterOwnerSharePolicyParams, OwnerSharePolicyRegistrationReceipt, ShareDeliveryAuthorizationReceipt, IDataVaultService, IEncryptionService, NetworkDescriptor, ISecretsService, ICapabilityKeyRegistry, DelegationManager, ISpaceService, ISpace, ISharingService, CreateDelegationParams, DelegationResult, DelegationRevocationReceipt, DelegationStatus, ResolvedDelegate, KeyProvider, JWK } from '@tinycloud/sdk-core';
2
2
  import { EventEmitter } from 'events';
3
3
  import { InvokeFunction, InvokeAnyFunction } from '@tinycloud/sdk-services';
4
4
 
@@ -319,6 +319,12 @@ declare class NodeUserAuthorization implements IUserAuthorization {
319
319
  private _address?;
320
320
  private _chainId?;
321
321
  private _nodeFeatures;
322
+ /**
323
+ * `nodeId` from the `GET /info` performed during sign-in/restore, tagged with
324
+ * the host it came from. Callers that need the node DID (encryption-network
325
+ * admin invocations) reuse this instead of re-fetching `/info`.
326
+ */
327
+ private _nodeInfoIdentity?;
322
328
  private _lastActivationSkippedSpaceIds;
323
329
  constructor(config: NodeUserAuthorizationConfig);
324
330
  /**
@@ -387,6 +393,13 @@ declare class NodeUserAuthorization implements IUserAuthorization {
387
393
  private requireTinyCloudHosts;
388
394
  private get primaryTinyCloudHost();
389
395
  get nodeFeatures(): string[];
396
+ /**
397
+ * The node DID reported by `GET /info` for `host`, if the sign-in (or
398
+ * restore) that populated it targeted that same host. Returns `undefined`
399
+ * for any other host, and for nodes whose `/info` omits `nodeId`, so callers
400
+ * fall back to fetching it themselves rather than using a foreign node's DID.
401
+ */
402
+ nodeIdForHost(host: string): string | undefined;
390
403
  get lastActivationSkippedSpaceIds(): string[];
391
404
  /**
392
405
  * Compute the `abilities` map the WASM `prepareSession` call should
@@ -856,19 +869,9 @@ type SecretReadResult = {
856
869
  } | {
857
870
  status: "invalid_payload";
858
871
  };
859
- interface CreateOwnerDelegationParams {
860
- readonly delegateDid: string;
861
- readonly spaceId: string;
862
- readonly path: string;
863
- readonly actions: readonly string[];
864
- readonly expiresAt: Date;
865
- }
866
- interface OwnerDelegationReceipt {
872
+ type CreateOwnerDelegationParams = CreateOwnerDelegationParams$1;
873
+ interface OwnerDelegationReceipt extends OwnerDelegationReceipt$1 {
867
874
  readonly delegation: Delegation;
868
- /** Exact signed DAG-CBOR bytes submitted in the Authorization header. */
869
- readonly signedDagCbor: Uint8Array;
870
- /** Locally derived by the node WASM implementation; this is delegation identity. */
871
- readonly delegationCid: string;
872
875
  readonly nodeReceipt: {
873
876
  /** Raw /delegate response CID: a commit-event id, not delegation identity. */
874
877
  readonly commitEventCid?: string;
@@ -997,6 +1000,21 @@ interface RuntimePermissionGrantOptions {
997
1000
  /** Override expiry. ms-format string ("7d", "1h") or raw milliseconds. */
998
1001
  expiry?: string | number;
999
1002
  }
1003
+ interface EnsureEncryptionNetworkOptions {
1004
+ /**
1005
+ * Skip the `GET /encryption/networks/{id}` existence probe and go straight
1006
+ * to the create.
1007
+ *
1008
+ * The probe pays for itself only when the network already exists. On a
1009
+ * freshly bootstrapped account it is a guaranteed 404, so it costs a round
1010
+ * trip on the cold sign-in path and saves nothing. Creating without it is
1011
+ * safe: the server answers `409 Conflict` if the network is already there,
1012
+ * which {@link TinyCloudNode.createEncryptionNetwork} resolves by reading the
1013
+ * existing descriptor. Set this only when the caller knows the account was
1014
+ * just provisioned.
1015
+ */
1016
+ assumeMissing?: boolean;
1017
+ }
1000
1018
  /**
1001
1019
  * High-level TinyCloud API for Node.js environments.
1002
1020
  *
@@ -1054,6 +1072,12 @@ declare class TinyCloudNode {
1054
1072
  * so this avoids re-parsing the recap on every registration.
1055
1073
  */
1056
1074
  private _recapOperationsCache?;
1075
+ /**
1076
+ * Owned space ids this sign-in has already confirmed are hosted. Consulted by
1077
+ * {@link ensureOwnedSpaceHostedById}; cleared on every {@link signIn} because
1078
+ * hosting is confirmed against the session that was active at the time.
1079
+ */
1080
+ private readonly confirmedHostedSpaceIds;
1057
1081
  /**
1058
1082
  * TinyCloudSession captured by {@link restoreSession} when there's no
1059
1083
  * auth-layer signer available (session-only mode used by OpenKey-backed
@@ -1262,6 +1286,22 @@ declare class TinyCloudNode {
1262
1286
  private withAccountRegistryRetry;
1263
1287
  private requestedEncryptionNetworkIds;
1264
1288
  private ensureRequestedEncryptionNetworks;
1289
+ /**
1290
+ * Ensure one of this user's owned spaces is hosted, at most once per space
1291
+ * per sign-in.
1292
+ *
1293
+ * Account bootstrap funnels six calls through here (via
1294
+ * `AccountService.ensureAccountSpaceHosted`), and each one re-submits
1295
+ * `activateSessionWithHost` with the *primary* session. That session's recap
1296
+ * covers only `default` and `secrets`, so the account space appears in
1297
+ * neither `activated` nor `skipped` and the guard below returns immediately
1298
+ * — the repeat calls cannot change the outcome, they only cost round trips.
1299
+ *
1300
+ * The memo is per sign-in ({@link confirmedHostedSpaceIds} is cleared in
1301
+ * `signIn`) and only records confirmed successes. A wrong skip is
1302
+ * self-revealing: the next write to the space fails immediately and loudly
1303
+ * with `404 Space not found`.
1304
+ */
1265
1305
  private ensureOwnedSpaceHostedById;
1266
1306
  /**
1267
1307
  * Host one of this user's owned spaces by name (e.g. `"applications"`).
@@ -1472,6 +1512,14 @@ declare class TinyCloudNode {
1472
1512
  */
1473
1513
  private requireEncryptionSession;
1474
1514
  private createEncryptionCrypto;
1515
+ /**
1516
+ * The node DID for {@link config.host}.
1517
+ *
1518
+ * Sign-in already performs `GET /info` for the protocol-version check, and
1519
+ * that response carries `nodeId`, so reuse it rather than fetching `/info` a
1520
+ * second time. Only a value recorded for this exact host qualifies; older
1521
+ * nodes that omit `nodeId` fall through to the fetch.
1522
+ */
1475
1523
  private fetchNodeId;
1476
1524
  private signRawNetworkAuthorization;
1477
1525
  private fetchEncryptionNetworkAt;
@@ -1596,7 +1644,13 @@ declare class TinyCloudNode {
1596
1644
  get encryption(): IEncryptionService;
1597
1645
  getEncryptionNetwork(nameOrNetworkId?: string): Promise<NetworkDescriptor | null>;
1598
1646
  createEncryptionNetwork(name?: string): Promise<NetworkDescriptor>;
1599
- ensureEncryptionNetwork(nameOrNetworkId?: string): Promise<NetworkDescriptor>;
1647
+ /**
1648
+ * Ensure an encryption network exists, creating it if necessary.
1649
+ *
1650
+ * @param nameOrNetworkId - Network name or full `urn:tinycloud:encryption:` id.
1651
+ * @param options - See {@link EnsureEncryptionNetworkOptions}.
1652
+ */
1653
+ ensureEncryptionNetwork(nameOrNetworkId?: string, options?: EnsureEncryptionNetworkOptions): Promise<NetworkDescriptor>;
1600
1654
  /**
1601
1655
  * App-facing secrets API backed by the `secrets` space vault.
1602
1656
  */
@@ -2178,4 +2232,4 @@ declare class WasmKeyProvider implements KeyProvider {
2178
2232
  */
2179
2233
  declare function createWasmKeyProvider(sessionManager: SessionManagerWithListing): WasmKeyProvider;
2180
2234
 
2181
- export { type AuthDelegationArtifact as A, type CreateOwnerDelegationParams as C, type DelegateToOptions as D, FileSessionStorage as F, MemorySessionStorage as M, type NodeEventEmitterStrategy as N, type OwnerDelegationReceipt as O, type PortableDelegation as P, type RestorableSession as R, type SecretPermissionHint as S, TinyCloudNode as T, UnsupportedSessionRestoreError as U, type ValidatedRuntimeDelegation as V, WasmKeyProvider as W, type AuthRequestArtifact as a, type DelegateToResult as b, DelegatedAccess as c, type DelegationAuthority as d, NodeUserAuthorization as e, type NodeUserAuthorizationConfig as f, type RuntimeDelegationActivator as g, type RuntimePermissionGrantOptions as h, type SecretReadInput as i, type SecretReadResult as j, type SignStrategy as k, type TinyCloudNodeConfig as l, type WasmKeyProviderConfig as m, activateValidatedRuntimeDelegation as n, createWasmKeyProvider as o, defaultSignStrategy as p, deserializeDelegation as q, grantAuthRequest as r, serializeDelegation as s };
2235
+ export { type AuthDelegationArtifact as A, type CreateOwnerDelegationParams as C, type DelegateToOptions as D, type EnsureEncryptionNetworkOptions as E, FileSessionStorage as F, MemorySessionStorage as M, type NodeEventEmitterStrategy as N, type OwnerDelegationReceipt as O, type PortableDelegation as P, type RestorableSession as R, type SecretPermissionHint as S, TinyCloudNode as T, UnsupportedSessionRestoreError as U, type ValidatedRuntimeDelegation as V, WasmKeyProvider as W, type AuthRequestArtifact as a, type DelegateToResult as b, DelegatedAccess as c, type DelegationAuthority as d, NodeUserAuthorization as e, type NodeUserAuthorizationConfig as f, type RuntimeDelegationActivator as g, type RuntimePermissionGrantOptions as h, type SecretReadInput as i, type SecretReadResult as j, type SignStrategy as k, type TinyCloudNodeConfig as l, type WasmKeyProviderConfig as m, activateValidatedRuntimeDelegation as n, createWasmKeyProvider as o, defaultSignStrategy as p, deserializeDelegation as q, grantAuthRequest as r, serializeDelegation as s };
package/dist/core.cjs CHANGED
@@ -551,6 +551,15 @@ var NodeUserAuthorization = class {
551
551
  get nodeFeatures() {
552
552
  return this._nodeFeatures;
553
553
  }
554
+ /**
555
+ * The node DID reported by `GET /info` for `host`, if the sign-in (or
556
+ * restore) that populated it targeted that same host. Returns `undefined`
557
+ * for any other host, and for nodes whose `/info` omits `nodeId`, so callers
558
+ * fall back to fetching it themselves rather than using a foreign node's DID.
559
+ */
560
+ nodeIdForHost(host) {
561
+ return this._nodeInfoIdentity?.host === host ? this._nodeInfoIdentity.nodeId : void 0;
562
+ }
554
563
  get lastActivationSkippedSpaceIds() {
555
564
  return [...this._lastActivationSkippedSpaceIds];
556
565
  }
@@ -1020,11 +1029,13 @@ var NodeUserAuthorization = class {
1020
1029
  this._tinyCloudSession = tinyCloudSession;
1021
1030
  this._address = address;
1022
1031
  this._chainId = chainId;
1032
+ const infoHost = this.primaryTinyCloudHost;
1023
1033
  const nodeInfo = await (0, import_sdk_core3.checkNodeInfo)(
1024
- this.primaryTinyCloudHost,
1034
+ infoHost,
1025
1035
  this.wasm.protocolVersion()
1026
1036
  );
1027
1037
  this._nodeFeatures = nodeInfo.features;
1038
+ this._nodeInfoIdentity = nodeInfo.nodeId ? { host: infoHost, nodeId: nodeInfo.nodeId } : void 0;
1028
1039
  for (const ext of this.extensions) {
1029
1040
  if (ext.afterSignIn) {
1030
1041
  await ext.afterSignIn(clientSession);
@@ -1190,11 +1201,13 @@ var NodeUserAuthorization = class {
1190
1201
  this._tinyCloudSession = tinyCloudSession;
1191
1202
  this._address = address;
1192
1203
  this._chainId = chainId;
1204
+ const infoHost = this.primaryTinyCloudHost;
1193
1205
  const nodeInfo = await (0, import_sdk_core3.checkNodeInfo)(
1194
- this.primaryTinyCloudHost,
1206
+ infoHost,
1195
1207
  this.wasm.protocolVersion()
1196
1208
  );
1197
1209
  this._nodeFeatures = nodeInfo.features;
1210
+ this._nodeInfoIdentity = nodeInfo.nodeId ? { host: infoHost, nodeId: nodeInfo.nodeId } : void 0;
1198
1211
  for (const ext of this.extensions) {
1199
1212
  if (ext.afterSignIn) {
1200
1213
  await ext.afterSignIn(clientSession);
@@ -1890,19 +1903,87 @@ function persistedExpiry(value) {
1890
1903
  function sameInstant(left, right) {
1891
1904
  return left.getTime() === right.getTime();
1892
1905
  }
1893
- function sharingActionsToAbilities(path, actions) {
1894
- var _a;
1906
+ function ownerDelegationPermissions(params) {
1907
+ const hasPermissions = Object.prototype.hasOwnProperty.call(params, "permissions");
1908
+ const hasLegacy = Object.prototype.hasOwnProperty.call(params, "path") || Object.prototype.hasOwnProperty.call(params, "actions");
1909
+ if (hasPermissions === hasLegacy) {
1910
+ throw new Error("Owner delegation requires exactly one authority shape: path/actions or permissions");
1911
+ }
1912
+ const permissions = [];
1913
+ if (hasPermissions) {
1914
+ if (!Array.isArray(params.permissions)) {
1915
+ throw new Error("Owner delegation permissions are invalid");
1916
+ }
1917
+ permissions.push(...params.permissions.map((permission) => ({
1918
+ service: permission.service,
1919
+ path: permission.path,
1920
+ actions: [...permission.actions]
1921
+ })));
1922
+ } else {
1923
+ if (typeof params.path !== "string" || !Array.isArray(params.actions)) {
1924
+ throw new Error("Owner delegation requires bounded capabilities");
1925
+ }
1926
+ const byService = /* @__PURE__ */ new Map();
1927
+ for (const action of params.actions) {
1928
+ if (typeof action !== "string") {
1929
+ throw new Error("Owner delegation capabilities are unsupported");
1930
+ }
1931
+ const slash = action.indexOf("/");
1932
+ const service = slash === -1 ? "" : action.slice(0, slash);
1933
+ if (import_sdk_core8.SERVICE_LONG_TO_SHORT[service] === void 0) {
1934
+ throw new Error("Owner delegation capabilities are unsupported");
1935
+ }
1936
+ const actions = byService.get(service) ?? [];
1937
+ actions.push(action);
1938
+ byService.set(service, actions);
1939
+ }
1940
+ for (const [service, actions] of byService) {
1941
+ permissions.push({ service, path: params.path, actions });
1942
+ }
1943
+ }
1944
+ if (permissions.length === 0 || permissions.length > 16) {
1945
+ throw new Error("Owner delegation requires bounded capabilities");
1946
+ }
1947
+ const resources = /* @__PURE__ */ new Set();
1948
+ for (const permission of permissions) {
1949
+ if (typeof permission.service !== "string" || import_sdk_core8.SERVICE_LONG_TO_SHORT[permission.service] === void 0 || typeof permission.path !== "string" || permission.path.length === 0 || permission.path.trim() !== permission.path || /[\u0000-\u001f\u007f\\*]/.test(permission.path) || !Array.isArray(permission.actions) || permission.actions.length === 0 || permission.actions.length > 32) {
1950
+ throw new Error("Owner delegation requires bounded capabilities");
1951
+ }
1952
+ const resourceKey = `${permission.service}\0${permission.path}`;
1953
+ if (resources.has(resourceKey)) {
1954
+ throw new Error("Owner delegation permissions contain duplicate resources");
1955
+ }
1956
+ resources.add(resourceKey);
1957
+ const actions = /* @__PURE__ */ new Set();
1958
+ for (const action of permission.actions) {
1959
+ if (typeof action !== "string" || !action.startsWith(`${permission.service}/`) || action.length === permission.service.length + 1 || actions.has(action)) {
1960
+ throw new Error("Owner delegation capabilities are unsupported");
1961
+ }
1962
+ actions.add(action);
1963
+ }
1964
+ if (permission.service === import_sdk_core8.ENCRYPTION_PERMISSION_SERVICE) {
1965
+ try {
1966
+ (0, import_sdk_core8.parseNetworkId)(permission.path);
1967
+ } catch {
1968
+ throw new Error("Owner delegation encryption permission requires a canonical network URN");
1969
+ }
1970
+ }
1971
+ }
1972
+ return permissions;
1973
+ }
1974
+ function ownerPermissionsToAbilities(permissions) {
1895
1975
  const abilities = {};
1896
- for (const action of actions) {
1897
- const slash = action.indexOf("/");
1898
- if (slash === -1) return void 0;
1899
- const shortService = import_sdk_core8.SERVICE_LONG_TO_SHORT[action.slice(0, slash)];
1900
- if (shortService === void 0) return void 0;
1976
+ const rawAbilities = {};
1977
+ for (const permission of permissions) {
1978
+ if (permission.service === import_sdk_core8.ENCRYPTION_PERMISSION_SERVICE) {
1979
+ rawAbilities[permission.path] = [...permission.actions];
1980
+ continue;
1981
+ }
1982
+ const shortService = import_sdk_core8.SERVICE_LONG_TO_SHORT[permission.service];
1901
1983
  abilities[shortService] ?? (abilities[shortService] = {});
1902
- (_a = abilities[shortService])[path] ?? (_a[path] = []);
1903
- abilities[shortService][path].push(action);
1984
+ abilities[shortService][permission.path] = [...permission.actions];
1904
1985
  }
1905
- return Object.keys(abilities).length > 0 ? abilities : void 0;
1986
+ return { abilities, rawAbilities };
1906
1987
  }
1907
1988
  function base64UrlEncode(bytes) {
1908
1989
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
@@ -2020,6 +2101,12 @@ var _TinyCloudNode = class _TinyCloudNode {
2020
2101
  this._baseSecrets = /* @__PURE__ */ new Map();
2021
2102
  this._secrets = /* @__PURE__ */ new Map();
2022
2103
  this.runtimePermissionGrants = [];
2104
+ /**
2105
+ * Owned space ids this sign-in has already confirmed are hosted. Consulted by
2106
+ * {@link ensureOwnedSpaceHostedById}; cleared on every {@link signIn} because
2107
+ * hosting is confirmed against the session that was active at the time.
2108
+ */
2109
+ this.confirmedHostedSpaceIds = /* @__PURE__ */ new Set();
2023
2110
  /**
2024
2111
  * True when the last signIn() detected an interactive signer and skipped
2025
2112
  * client-side bootstrap. Apps can read this to know whether bootstrap was
@@ -2416,6 +2503,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2416
2503
  this._spaceService = void 0;
2417
2504
  this._serviceContext = void 0;
2418
2505
  this.runtimePermissionGrants = [];
2506
+ this.confirmedHostedSpaceIds.clear();
2419
2507
  await this.tc.signIn(options);
2420
2508
  this.syncResolvedHostFromAuth();
2421
2509
  const oldGraph = this._serviceGraph;
@@ -2598,14 +2686,18 @@ var _TinyCloudNode = class _TinyCloudNode {
2598
2686
  }
2599
2687
  if (step.kind === "seed-applications") {
2600
2688
  const registered = await this.account.applications.register(
2601
- step.manifests.length > 0 ? [...step.manifests] : import_sdk_core8.TINYCLOUD_SECRETS_BOOTSTRAP_MANIFEST
2689
+ step.manifests.length > 0 ? [...step.manifests] : import_sdk_core8.TINYCLOUD_SECRETS_BOOTSTRAP_MANIFEST,
2690
+ // Bootstrap only runs on an account with no registry records, so the
2691
+ // manifest-hash pre-read is definitionally a miss. Skip it; the write
2692
+ // it guards is an INSERT OR REPLACE and is safe to repeat.
2693
+ { assumeUnregistered: true }
2602
2694
  );
2603
2695
  if (!registered.ok) {
2604
2696
  throw new Error(`Failed to seed bootstrap applications: ${registered.error.message}`);
2605
2697
  }
2606
2698
  }
2607
2699
  if (step.kind === "encryption-network-create") {
2608
- await this.ensureEncryptionNetwork(step.networkId);
2700
+ await this.ensureEncryptionNetwork(step.networkId, { assumeMissing: true });
2609
2701
  }
2610
2702
  if (step.kind === "secret-records-schema") {
2611
2703
  const db = this.sqlForSpace(step.spaceId).db(step.database);
@@ -2884,6 +2976,22 @@ var _TinyCloudNode = class _TinyCloudNode {
2884
2976
  await this.ensureEncryptionNetwork(networkId);
2885
2977
  }
2886
2978
  }
2979
+ /**
2980
+ * Ensure one of this user's owned spaces is hosted, at most once per space
2981
+ * per sign-in.
2982
+ *
2983
+ * Account bootstrap funnels six calls through here (via
2984
+ * `AccountService.ensureAccountSpaceHosted`), and each one re-submits
2985
+ * `activateSessionWithHost` with the *primary* session. That session's recap
2986
+ * covers only `default` and `secrets`, so the account space appears in
2987
+ * neither `activated` nor `skipped` and the guard below returns immediately
2988
+ * — the repeat calls cannot change the outcome, they only cost round trips.
2989
+ *
2990
+ * The memo is per sign-in ({@link confirmedHostedSpaceIds} is cleared in
2991
+ * `signIn`) and only records confirmed successes. A wrong skip is
2992
+ * self-revealing: the next write to the space fails immediately and loudly
2993
+ * with `404 Space not found`.
2994
+ */
2887
2995
  async ensureOwnedSpaceHostedById(spaceId) {
2888
2996
  if (!this.auth) {
2889
2997
  throw new Error("Owned space hosting requires wallet mode");
@@ -2892,12 +3000,16 @@ var _TinyCloudNode = class _TinyCloudNode {
2892
3000
  if (!session) {
2893
3001
  throw new Error("Owned space hosting requires an active session");
2894
3002
  }
3003
+ if (this.confirmedHostedSpaceIds.has(spaceId)) {
3004
+ return;
3005
+ }
2895
3006
  const host = this.hosts[0] ?? this.config.host;
2896
3007
  if (!host) {
2897
3008
  throw new Error("Owned space hosting requires a TinyCloud host");
2898
3009
  }
2899
3010
  const activation = await (0, import_sdk_core8.activateSessionWithHost)(host, session.delegationHeader);
2900
3011
  if (activation.success && !activation.skipped?.includes(spaceId)) {
3012
+ this.confirmedHostedSpaceIds.add(spaceId);
2901
3013
  return;
2902
3014
  }
2903
3015
  if (!activation.success && activation.status !== 404) {
@@ -2916,6 +3028,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2916
3028
  `Failed to activate session after creating owned space ${spaceId}: ${retry.error ?? "space was skipped"}`
2917
3029
  );
2918
3030
  }
3031
+ this.confirmedHostedSpaceIds.add(spaceId);
2919
3032
  }
2920
3033
  /**
2921
3034
  * Host one of this user's owned spaces by name (e.g. `"applications"`).
@@ -3238,6 +3351,7 @@ var _TinyCloudNode = class _TinyCloudNode {
3238
3351
  this.tc = stagedGraph.core;
3239
3352
  this._recapOperationsCache = stagedPrimary.cache;
3240
3353
  this.runtimePermissionGrants = stagedPrimary.grants;
3354
+ this.confirmedHostedSpaceIds.clear();
3241
3355
  if (this.auth) {
3242
3356
  this.auth.installRestoredSession(stagedManager, stagedTcSession, [stagedHost]);
3243
3357
  this._restoredTcSession = void 0;
@@ -3820,7 +3934,17 @@ var _TinyCloudNode = class _TinyCloudNode {
3820
3934
  verifyNodeSignature: (nodeId, message, signature) => (0, import_sdk_core8.verifyDidKeyEd25519Signature)(nodeId, message, signature)
3821
3935
  };
3822
3936
  }
3937
+ /**
3938
+ * The node DID for {@link config.host}.
3939
+ *
3940
+ * Sign-in already performs `GET /info` for the protocol-version check, and
3941
+ * that response carries `nodeId`, so reuse it rather than fetching `/info` a
3942
+ * second time. Only a value recorded for this exact host qualifies; older
3943
+ * nodes that omit `nodeId` fall through to the fetch.
3944
+ */
3823
3945
  async fetchNodeId() {
3946
+ const cached = this.auth?.nodeIdForHost(this.config.host);
3947
+ if (cached) return cached;
3824
3948
  const response = await fetch(`${this.config.host}/info`);
3825
3949
  if (!response.ok) {
3826
3950
  throw new Error(`Failed to fetch node info: HTTP ${response.status}`);
@@ -4276,21 +4400,29 @@ var _TinyCloudNode = class _TinyCloudNode {
4276
4400
  async createOwnerDelegation(params, assertActive) {
4277
4401
  const assertOwnerGraphActive = assertActive ?? this._serviceGraph.assertActive.bind(this._serviceGraph);
4278
4402
  assertOwnerGraphActive();
4279
- if (!params.delegateDid.startsWith("did:key:") || params.actions.length === 0 || params.path.length === 0) {
4403
+ if (!params.delegateDid.startsWith("did:key:")) {
4280
4404
  throw new Error("Owner delegation requires an external did:key audience and bounded capabilities");
4281
4405
  }
4406
+ const permissions = ownerDelegationPermissions(params);
4282
4407
  const now = /* @__PURE__ */ new Date();
4283
- if (params.expiresAt.getTime() <= now.getTime() || params.expiresAt.getTime() - now.getTime() > import_sdk_core8.EXPIRY.MAX_MS) {
4408
+ if (!(params.expiresAt instanceof Date) || params.expiresAt.getTime() <= now.getTime() || params.expiresAt.getTime() - now.getTime() > import_sdk_core8.EXPIRY.MAX_MS) {
4284
4409
  throw new Error("Owner delegation expiry must be explicit, future, and within EXPIRY.MAX_MS");
4285
4410
  }
4286
4411
  if (!this.signer) throw new Error("Owner wallet signer is required");
4287
4412
  const session = this.currentTinyCloudSession();
4288
4413
  if (!session) throw new Error("Owner session is required");
4289
- const abilities = sharingActionsToAbilities(params.path, [...params.actions]);
4290
- if (!abilities) throw new Error("Owner delegation capabilities are unsupported");
4414
+ const ownerDid = (0, import_sdk_core8.pkhDid)(session.address, session.chainId);
4415
+ for (const permission of permissions) {
4416
+ if (permission.service === import_sdk_core8.ENCRYPTION_PERMISSION_SERVICE && !(0, import_sdk_core8.principalDidEquals)((0, import_sdk_core8.parseNetworkId)(permission.path).ownerDid, ownerDid)) {
4417
+ throw new Error("Owner delegation cannot grant a foreign encryption network");
4418
+ }
4419
+ }
4420
+ const { abilities, rawAbilities } = ownerPermissionsToAbilities(permissions);
4421
+ const primary = permissions[0];
4291
4422
  const host = this.config.host;
4292
4423
  const prepared = this.wasmBindings.prepareSession({
4293
4424
  abilities,
4425
+ ...Object.keys(rawAbilities).length === 0 ? {} : { rawAbilities },
4294
4426
  address: this.wasmBindings.ensureEip55(session.address),
4295
4427
  chainId: session.chainId,
4296
4428
  domain: this.siweDomain,
@@ -4310,10 +4442,10 @@ var _TinyCloudNode = class _TinyCloudNode {
4310
4442
  const delegation = {
4311
4443
  cid: delegationSession.delegationCid,
4312
4444
  delegateDID: params.delegateDid,
4313
- delegatorDID: (0, import_sdk_core8.pkhDid)(session.address, session.chainId),
4445
+ delegatorDID: ownerDid,
4314
4446
  spaceId: params.spaceId,
4315
- path: params.path,
4316
- actions: [...params.actions],
4447
+ path: primary.path,
4448
+ actions: [...primary.actions],
4317
4449
  expiry: params.expiresAt,
4318
4450
  isRevoked: false,
4319
4451
  allowSubDelegation: true,
@@ -4324,6 +4456,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4324
4456
  delegation,
4325
4457
  signedDagCbor: decodeAuthorizationBytes(delegationSession.delegationHeader.Authorization),
4326
4458
  delegationCid: delegationSession.delegationCid,
4459
+ permissions,
4327
4460
  nodeReceipt: {
4328
4461
  commitEventCid: activation.commitEventCid,
4329
4462
  activated: activation.activated ?? [],
@@ -4583,6 +4716,13 @@ var _TinyCloudNode = class _TinyCloudNode {
4583
4716
  body
4584
4717
  )
4585
4718
  });
4719
+ if (response.status === 409) {
4720
+ const existing = await this.getEncryptionNetwork(networkId);
4721
+ if (existing) return existing;
4722
+ throw new Error(
4723
+ `Encryption network ${networkId} already exists but could not be read back`
4724
+ );
4725
+ }
4586
4726
  if (!response.ok) {
4587
4727
  throw new Error(
4588
4728
  `Failed to create encryption network ${networkId}: HTTP ${response.status} ${await response.text()}`
@@ -4591,11 +4731,19 @@ var _TinyCloudNode = class _TinyCloudNode {
4591
4731
  const created = await response.json();
4592
4732
  return created.descriptor;
4593
4733
  }
4594
- async ensureEncryptionNetwork(nameOrNetworkId = DEFAULT_ENCRYPTION_NETWORK_NAME) {
4734
+ /**
4735
+ * Ensure an encryption network exists, creating it if necessary.
4736
+ *
4737
+ * @param nameOrNetworkId - Network name or full `urn:tinycloud:encryption:` id.
4738
+ * @param options - See {@link EnsureEncryptionNetworkOptions}.
4739
+ */
4740
+ async ensureEncryptionNetwork(nameOrNetworkId = DEFAULT_ENCRYPTION_NETWORK_NAME, options = {}) {
4595
4741
  const networkId = nameOrNetworkId.startsWith("urn:tinycloud:encryption:") ? nameOrNetworkId : this.getDefaultEncryptionNetworkId(nameOrNetworkId);
4596
- const existing = await this.getEncryptionNetwork(networkId);
4597
- if (existing) {
4598
- return existing;
4742
+ if (!options.assumeMissing) {
4743
+ const existing = await this.getEncryptionNetwork(networkId);
4744
+ if (existing) {
4745
+ return existing;
4746
+ }
4599
4747
  }
4600
4748
  const parsed = (0, import_sdk_core8.parseNetworkId)(networkId);
4601
4749
  if (!didPrincipalMatches2(parsed.ownerDid, this.did)) {