@tinycloud/sdk-core 2.11.0-beta.1 → 2.11.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -680,8 +680,11 @@ interface SubsetCheckResult {
680
680
  * - Action containment: every requested URN must be matched exactly or by a
681
681
  * service-scoped wildcard such as `tinycloud.kv/*`.
682
682
  *
683
- * Any `requested` entry that does not find a matching `granted` entry is
684
- * added to `missing` and the overall result is non-subset.
683
+ * Grant entries with the same service, space, path coverage, and caveats are
684
+ * considered together. This matters because ReCap parsers may return one
685
+ * entry per action even when the original manifest grouped those actions.
686
+ * Any `requested` entry whose actions are not collectively covered is added
687
+ * to `missing` and the overall result is non-subset.
685
688
  *
686
689
  * Both sides are expected to be in the canonical long-form shape (service
687
690
  * starts with `tinycloud.`, actions are full URNs). Use {@link parseRecapCapabilities}
@@ -2523,6 +2526,20 @@ interface AccountIndexedReadOptions {
2523
2526
  refreshIndex?: boolean;
2524
2527
  }
2525
2528
  type AccountApplicationListOptions = AccountIndexedReadOptions;
2529
+ interface AccountApplicationRegisterOptions {
2530
+ /**
2531
+ * Skip the `application_state` manifest-hash pre-read that normally
2532
+ * short-circuits a redundant write.
2533
+ *
2534
+ * The pre-read is a saving only when the application is already registered
2535
+ * with the same manifest hash. On a freshly bootstrapped account it is
2536
+ * definitionally a miss, so it costs a round trip and buys nothing — and the
2537
+ * write it guards is an `INSERT OR REPLACE`, so performing it unconditionally
2538
+ * is idempotent. Set this only when the caller knows the record cannot
2539
+ * already be current (i.e. the account bootstrap path).
2540
+ */
2541
+ assumeUnregistered?: boolean;
2542
+ }
2526
2543
  type AccountSpaceListOptions = AccountIndexedReadOptions;
2527
2544
  interface AccountDelegationListOptions {
2528
2545
  direction?: "granted" | "received" | "all";
@@ -2546,12 +2563,28 @@ interface AccountServiceConfig {
2546
2563
  }
2547
2564
  declare class AccountService {
2548
2565
  private readonly config;
2566
+ /**
2567
+ * Per-account-space memo for {@link ensureAccountIndex}.
2568
+ *
2569
+ * Applying the index migration is a no-op once the schema exists, but the
2570
+ * SQL service still spends two round trips discovering that (create the
2571
+ * migrations table, then read the applied-migration rows). Account bootstrap
2572
+ * touches the index eight times, so without a memo that is ~14 wasted
2573
+ * requests on the cold sign-in path. The in-flight dedupe inside SQLService
2574
+ * never helps here: the node SDK builds a fresh SQLService per
2575
+ * `getAccountDb()` call, so its lock map is always empty.
2576
+ *
2577
+ * Keyed by account space id, so a different account never reuses this entry.
2578
+ * Failures are evicted so a later call retries instead of inheriting a
2579
+ * permanently poisoned result.
2580
+ */
2581
+ private readonly accountIndexReady;
2549
2582
  constructor(config: AccountServiceConfig);
2550
2583
  status(): Promise<Result<AccountStatus>>;
2551
2584
  readonly applications: {
2552
2585
  list: (options?: AccountApplicationListOptions) => Promise<Result<AccountApplication[]>>;
2553
2586
  get: (appId: string) => Promise<Result<AccountApplication>>;
2554
- register: (manifest: Manifest | Manifest[]) => Promise<Result<AccountApplication>>;
2587
+ register: (manifest: Manifest | Manifest[], options?: AccountApplicationRegisterOptions) => Promise<Result<AccountApplication>>;
2555
2588
  remove: (appId: string) => Promise<Result<void>>;
2556
2589
  };
2557
2590
  readonly spaces: {
@@ -2596,7 +2629,19 @@ declare class AccountService {
2596
2629
  private replaceApplicationsIndexQuietly;
2597
2630
  private replaceSpacesIndexQuietly;
2598
2631
  private replaceDelegationsIndexQuietly;
2632
+ /**
2633
+ * Apply the account index schema at most once per account space.
2634
+ *
2635
+ * The index is a cache over canonical KV state, and every caller already
2636
+ * tolerates it being absent (`ignoreIndexFailure` / `isMissingIndexError`),
2637
+ * so a memo that is wrong in the "already applied" direction degrades to a
2638
+ * `no such table` read that the caller handles. A memo that is wrong in the
2639
+ * other direction would be a real bug, so failures are never cached.
2640
+ */
2599
2641
  private ensureAccountIndex;
2642
+ /** Drop the memo so the next {@link ensureAccountIndex} re-applies the schema. */
2643
+ private invalidateAccountIndexMemo;
2644
+ private applyAccountIndex;
2600
2645
  }
2601
2646
  interface AccountIndexStatus {
2602
2647
  database: string;
@@ -2731,7 +2776,7 @@ interface LocationRecord extends LocationRecordPayload {
2731
2776
  type LocationSource = "explicit" | "local-loopback" | "local-link" | "blockchain" | "centralized" | "fallback";
2732
2777
  declare const DEFAULT_TINYCLOUD_LOCATION_REGISTRY_URL = "https://registry.tinycloud.xyz";
2733
2778
  declare const DEFAULT_TINYCLOUD_FALLBACK_HOST = "https://node.tinycloud.xyz";
2734
- /** Default local loopback node URL probed before registry/fallback resolution. */
2779
+ /** Conventional local loopback node URL. Probed only when explicitly configured. */
2735
2780
  declare const DEFAULT_LOCAL_NODE_URL = "http://127.0.0.1:8000";
2736
2781
  /** Probe timeout for the local loopback candidate. No retries. */
2737
2782
  declare const LOCAL_LOOPBACK_PROBE_TIMEOUT_MS = 250;
@@ -2818,7 +2863,7 @@ interface DiscoverLocalTinyCloudNodeOptions {
2818
2863
  subject?: string;
2819
2864
  /** Enable local-node auto-discovery. Default true. */
2820
2865
  autoDiscoverLocalNode?: boolean;
2821
- /** Local loopback node URL to probe. Default http://127.0.0.1:8000. */
2866
+ /** Local loopback node URL to probe. Omit to avoid probing loopback. */
2822
2867
  localNodeUrl?: string;
2823
2868
  /** Known `*.local.tinycloud.link` subdomain name, probed directly. */
2824
2869
  localLinkName?: string;
@@ -2852,12 +2897,12 @@ interface ResolveTinyCloudHostsOptions {
2852
2897
  /** Verify centralized/blockchain record signatures. Default true. */
2853
2898
  verifyRecords?: boolean;
2854
2899
  /**
2855
- * Probe for a locally-running TinyCloud node before falling back to
2856
- * registry/hosted resolution. Default true. Setting false restores the
2857
- * exact pre-TC-106 resolution order (registry → fallback).
2900
+ * Probe configured and registered local TinyCloud node endpoints before
2901
+ * falling back to registry/hosted resolution. Default true. The loopback
2902
+ * URL is probed only when `localNodeUrl` is explicitly configured.
2858
2903
  */
2859
2904
  autoDiscoverLocalNode?: boolean;
2860
- /** Local loopback node URL to probe. Default http://127.0.0.1:8000. */
2905
+ /** Local loopback node URL to probe. Omit to avoid probing loopback. */
2861
2906
  localNodeUrl?: string;
2862
2907
  /** Known `*.local.tinycloud.link` subdomain name, probed directly. */
2863
2908
  localLinkName?: string;
@@ -2943,4 +2988,4 @@ declare const EXPIRY: {
2943
2988
  declare const DEFAULT_SIGNED_READ_URL_EXPIRY_MS: number;
2944
2989
  type ExpiryTier = keyof typeof EXPIRY;
2945
2990
 
2946
- export { ACCOUNT_REGISTRY_PATH, ACCOUNT_REGISTRY_SPACE, type AbilitiesMap, type AccountApplication, type AccountApplicationListOptions, type AccountDelegation, type AccountDelegationListOptions, AccountDelegationPage, AccountDelegationQueryOptions, type AccountDelegationRevokeOptions, type AccountIndexEnsureResult, type AccountIndexRebuildResult, type AccountIndexStatus, type AccountIndexedReadOptions, AccountService, type AccountServiceConfig, type AccountSpace, type AccountSpaceListOptions, type AccountStatus, AutoApproveSpaceCreationHandler, type AutoRejectStrategy, type AutoSignStrategy, type Bytes, type CallbackStrategy, type CanonicalAddress, type CanonicalParsedNetworkId, CaveatedDelegationUnsupportedError, type ClientSession, ClientSessionSchema, CloudLocationResolutionError, type ComposeManifestOptions, type ComposedManifestRequest, type CreateDelegationFunction, CreateDelegationParams, DEFAULT_DEFAULTS, DEFAULT_EXPIRY, DEFAULT_KNOWLEDGE_ROOT, DEFAULT_LOCAL_NODE_PIN_STORAGE_KEY, DEFAULT_LOCAL_NODE_URL, DEFAULT_MANIFEST_SPACE, DEFAULT_MANIFEST_VERSION, DEFAULT_SIGNED_READ_URL_EXPIRY_MS, DEFAULT_TINYCLOUD_FALLBACK_HOST, DEFAULT_TINYCLOUD_LOCATION_REGISTRY_URL, Delegation, DelegationManager, DelegationRevocationReceipt, type DidCacheKeyOptions, type DidEqualsOptions, type DiscoverLocalTinyCloudNodeOptions, type DiscoveredLocalTinyCloudNode, ENCRYPTION_MANIFEST_SPACE, ENCRYPTION_PERMISSION_SERVICE, EXPIRY, type EnsData, EnsDataSchema, type EventEmitterStrategy, type ExpiryTier, type Extension, GenerateShareParams, ICapabilityKeyRegistry, type IENSResolver, type INotificationHandler, type ISessionManager, type ISessionStorage, ISharingService, type ISigner, type ISpace, type ISpaceCreationHandler, type ISpaceScopedDelegations, type ISpaceScopedSharing, type ISpaceService, type IUserAuthorization, type IWasmBindings, IdentityParseError, LOCAL_LINK_HOST_SUFFIX, LOCAL_LINK_PROBE_TIMEOUT_MS, LOCAL_LOOPBACK_PROBE_TIMEOUT_MS, type LocalNodeIdentityStore, type LocationCandidate, type LocationCandidateInput, type LocationRecord, type LocationRecordPayload, type LocationRecordSigner, LocationRecordValidationError, type LocationResolutionAttempt, type LocationSource, type Manifest, type ManifestDefaults, type ManifestKnowledge, type ManifestRegistryRecord, type ManifestSecretActions, ManifestValidationError, type NodeInfo, type OpenKeyCallbackStrategy, type OpenKeySigningRequestBody, type OpenKeySigningResponseBody, type OpenKeySigningStrategyOptions, type ParseRecapFromSiwe, type PartialSiweMessage, type PermissionEntry, PermissionNotInManifestError, type PersistedSessionData, type PersistedSessionProof, type PersistedTinyCloudSession, type PkhDidParts, ProtocolMismatchError, type ResolveCloudLocationOptions, type ResolveTinyCloudHostsOptions, type ResolvedCapabilities, type ResolvedCloudLocation, type ResolvedDelegate, type ResolvedTinyCloudHosts, type ResourceCapability, SECRETS_SPACE, SERVICE_LONG_TO_SHORT, SERVICE_SHORT_TO_LONG, type ServerHost, SessionExpiredError, ShareLink, type SignCallback, type SignInOptions, type SignRequest, type SignResponse, type SignStrategy, SilentNotificationHandler, type SiweConfig, SiweConfigSchema, Space, type SpaceAbilitiesMap, type SpaceConfig, type SpaceCreationContext, type SpaceDelegationParams, type SpaceErrorCode, SpaceErrorCodes, type SpaceHostResult, SpaceInfo, SpaceService, type SpaceServiceConfig, type SubsetCheckResult, TinyCloud, type TinyCloudConfig, type TinyCloudSession, UnsupportedFeatureError, type UserAuthorizationConfig, VAULT_PERMISSION_SERVICE, type ValidatedPersistedSessionProof, type ValidationError, VersionCheckError, type WasmRecapEntry, type WebStorageLike, actionContains, activateSessionWithHost, addressStorageKey, applyPrefix, buildSpaceUri, canonicalLocationPayload, canonicalizeAddress, canonicalizeDid, canonicalizeDidUrl, canonicalizeNetworkId, canonicalizeRecapCaveats, checkNodeInfo, composeManifestRequest, createInMemoryLocalNodeIdentityStore, createLocalStorageLocalNodeIdentityStore, createOpenKeyCallbackSigningStrategy, createSpaceService, defaultSignStrategy, defaultSpaceCreationHandler, didCacheKey, didEquals, discoverLocalTinyCloudNode, expandActionShortNames, expandPermissionEntries, expandPermissionEntry, fetchLocationRecord, fetchPeerId, httpUrlToMultiaddr, isCapabilitySubset, isEvmAddress, loadManifest, locationPayloadForRecord, makePkhSpaceId, makePublicSpaceId, manifestAbilitiesUnion, multiaddrToHttpUrl, normalizeDefaults, parseCanonicalNetworkId, parseExpiry, parsePkhDid, parseRecapCapabilities, parseSpaceUri, pkhDid, principalDid, principalDidEquals, recapCaveatsEqual, resolveCloudLocation, resolveManifest, resolveManifestKnowledgeRoot, resolveTinyCloudHosts, resourceCapabilitiesToAbilitiesMap, resourceCapabilitiesToSpaceAbilitiesMap, signLocationRecord, submitHostDelegation, validateClientSession, validateLocationRecord, validateLocationRecordPayload, validateManifest, validatePersistedSessionData, verifyDidKeyEd25519Signature, verifyLocationRecord };
2991
+ export { ACCOUNT_REGISTRY_PATH, ACCOUNT_REGISTRY_SPACE, type AbilitiesMap, type AccountApplication, type AccountApplicationListOptions, type AccountApplicationRegisterOptions, type AccountDelegation, type AccountDelegationListOptions, AccountDelegationPage, AccountDelegationQueryOptions, type AccountDelegationRevokeOptions, type AccountIndexEnsureResult, type AccountIndexRebuildResult, type AccountIndexStatus, type AccountIndexedReadOptions, AccountService, type AccountServiceConfig, type AccountSpace, type AccountSpaceListOptions, type AccountStatus, AutoApproveSpaceCreationHandler, type AutoRejectStrategy, type AutoSignStrategy, type Bytes, type CallbackStrategy, type CanonicalAddress, type CanonicalParsedNetworkId, CaveatedDelegationUnsupportedError, type ClientSession, ClientSessionSchema, CloudLocationResolutionError, type ComposeManifestOptions, type ComposedManifestRequest, type CreateDelegationFunction, CreateDelegationParams, DEFAULT_DEFAULTS, DEFAULT_EXPIRY, DEFAULT_KNOWLEDGE_ROOT, DEFAULT_LOCAL_NODE_PIN_STORAGE_KEY, DEFAULT_LOCAL_NODE_URL, DEFAULT_MANIFEST_SPACE, DEFAULT_MANIFEST_VERSION, DEFAULT_SIGNED_READ_URL_EXPIRY_MS, DEFAULT_TINYCLOUD_FALLBACK_HOST, DEFAULT_TINYCLOUD_LOCATION_REGISTRY_URL, Delegation, DelegationManager, DelegationRevocationReceipt, type DidCacheKeyOptions, type DidEqualsOptions, type DiscoverLocalTinyCloudNodeOptions, type DiscoveredLocalTinyCloudNode, ENCRYPTION_MANIFEST_SPACE, ENCRYPTION_PERMISSION_SERVICE, EXPIRY, type EnsData, EnsDataSchema, type EventEmitterStrategy, type ExpiryTier, type Extension, GenerateShareParams, ICapabilityKeyRegistry, type IENSResolver, type INotificationHandler, type ISessionManager, type ISessionStorage, ISharingService, type ISigner, type ISpace, type ISpaceCreationHandler, type ISpaceScopedDelegations, type ISpaceScopedSharing, type ISpaceService, type IUserAuthorization, type IWasmBindings, IdentityParseError, LOCAL_LINK_HOST_SUFFIX, LOCAL_LINK_PROBE_TIMEOUT_MS, LOCAL_LOOPBACK_PROBE_TIMEOUT_MS, type LocalNodeIdentityStore, type LocationCandidate, type LocationCandidateInput, type LocationRecord, type LocationRecordPayload, type LocationRecordSigner, LocationRecordValidationError, type LocationResolutionAttempt, type LocationSource, type Manifest, type ManifestDefaults, type ManifestKnowledge, type ManifestRegistryRecord, type ManifestSecretActions, ManifestValidationError, type NodeInfo, type OpenKeyCallbackStrategy, type OpenKeySigningRequestBody, type OpenKeySigningResponseBody, type OpenKeySigningStrategyOptions, type ParseRecapFromSiwe, type PartialSiweMessage, type PermissionEntry, PermissionNotInManifestError, type PersistedSessionData, type PersistedSessionProof, type PersistedTinyCloudSession, type PkhDidParts, ProtocolMismatchError, type ResolveCloudLocationOptions, type ResolveTinyCloudHostsOptions, type ResolvedCapabilities, type ResolvedCloudLocation, type ResolvedDelegate, type ResolvedTinyCloudHosts, type ResourceCapability, SECRETS_SPACE, SERVICE_LONG_TO_SHORT, SERVICE_SHORT_TO_LONG, type ServerHost, SessionExpiredError, ShareLink, type SignCallback, type SignInOptions, type SignRequest, type SignResponse, type SignStrategy, SilentNotificationHandler, type SiweConfig, SiweConfigSchema, Space, type SpaceAbilitiesMap, type SpaceConfig, type SpaceCreationContext, type SpaceDelegationParams, type SpaceErrorCode, SpaceErrorCodes, type SpaceHostResult, SpaceInfo, SpaceService, type SpaceServiceConfig, type SubsetCheckResult, TinyCloud, type TinyCloudConfig, type TinyCloudSession, UnsupportedFeatureError, type UserAuthorizationConfig, VAULT_PERMISSION_SERVICE, type ValidatedPersistedSessionProof, type ValidationError, VersionCheckError, type WasmRecapEntry, type WebStorageLike, actionContains, activateSessionWithHost, addressStorageKey, applyPrefix, buildSpaceUri, canonicalLocationPayload, canonicalizeAddress, canonicalizeDid, canonicalizeDidUrl, canonicalizeNetworkId, canonicalizeRecapCaveats, checkNodeInfo, composeManifestRequest, createInMemoryLocalNodeIdentityStore, createLocalStorageLocalNodeIdentityStore, createOpenKeyCallbackSigningStrategy, createSpaceService, defaultSignStrategy, defaultSpaceCreationHandler, didCacheKey, didEquals, discoverLocalTinyCloudNode, expandActionShortNames, expandPermissionEntries, expandPermissionEntry, fetchLocationRecord, fetchPeerId, httpUrlToMultiaddr, isCapabilitySubset, isEvmAddress, loadManifest, locationPayloadForRecord, makePkhSpaceId, makePublicSpaceId, manifestAbilitiesUnion, multiaddrToHttpUrl, normalizeDefaults, parseCanonicalNetworkId, parseExpiry, parsePkhDid, parseRecapCapabilities, parseSpaceUri, pkhDid, principalDid, principalDidEquals, recapCaveatsEqual, resolveCloudLocation, resolveManifest, resolveManifestKnowledgeRoot, resolveTinyCloudHosts, resourceCapabilitiesToAbilitiesMap, resourceCapabilitiesToSpaceAbilitiesMap, signLocationRecord, submitHostDelegation, validateClientSession, validateLocationRecord, validateLocationRecordPayload, validateManifest, validatePersistedSessionData, verifyDidKeyEd25519Signature, verifyLocationRecord };
package/dist/index.d.ts CHANGED
@@ -680,8 +680,11 @@ interface SubsetCheckResult {
680
680
  * - Action containment: every requested URN must be matched exactly or by a
681
681
  * service-scoped wildcard such as `tinycloud.kv/*`.
682
682
  *
683
- * Any `requested` entry that does not find a matching `granted` entry is
684
- * added to `missing` and the overall result is non-subset.
683
+ * Grant entries with the same service, space, path coverage, and caveats are
684
+ * considered together. This matters because ReCap parsers may return one
685
+ * entry per action even when the original manifest grouped those actions.
686
+ * Any `requested` entry whose actions are not collectively covered is added
687
+ * to `missing` and the overall result is non-subset.
685
688
  *
686
689
  * Both sides are expected to be in the canonical long-form shape (service
687
690
  * starts with `tinycloud.`, actions are full URNs). Use {@link parseRecapCapabilities}
@@ -2523,6 +2526,20 @@ interface AccountIndexedReadOptions {
2523
2526
  refreshIndex?: boolean;
2524
2527
  }
2525
2528
  type AccountApplicationListOptions = AccountIndexedReadOptions;
2529
+ interface AccountApplicationRegisterOptions {
2530
+ /**
2531
+ * Skip the `application_state` manifest-hash pre-read that normally
2532
+ * short-circuits a redundant write.
2533
+ *
2534
+ * The pre-read is a saving only when the application is already registered
2535
+ * with the same manifest hash. On a freshly bootstrapped account it is
2536
+ * definitionally a miss, so it costs a round trip and buys nothing — and the
2537
+ * write it guards is an `INSERT OR REPLACE`, so performing it unconditionally
2538
+ * is idempotent. Set this only when the caller knows the record cannot
2539
+ * already be current (i.e. the account bootstrap path).
2540
+ */
2541
+ assumeUnregistered?: boolean;
2542
+ }
2526
2543
  type AccountSpaceListOptions = AccountIndexedReadOptions;
2527
2544
  interface AccountDelegationListOptions {
2528
2545
  direction?: "granted" | "received" | "all";
@@ -2546,12 +2563,28 @@ interface AccountServiceConfig {
2546
2563
  }
2547
2564
  declare class AccountService {
2548
2565
  private readonly config;
2566
+ /**
2567
+ * Per-account-space memo for {@link ensureAccountIndex}.
2568
+ *
2569
+ * Applying the index migration is a no-op once the schema exists, but the
2570
+ * SQL service still spends two round trips discovering that (create the
2571
+ * migrations table, then read the applied-migration rows). Account bootstrap
2572
+ * touches the index eight times, so without a memo that is ~14 wasted
2573
+ * requests on the cold sign-in path. The in-flight dedupe inside SQLService
2574
+ * never helps here: the node SDK builds a fresh SQLService per
2575
+ * `getAccountDb()` call, so its lock map is always empty.
2576
+ *
2577
+ * Keyed by account space id, so a different account never reuses this entry.
2578
+ * Failures are evicted so a later call retries instead of inheriting a
2579
+ * permanently poisoned result.
2580
+ */
2581
+ private readonly accountIndexReady;
2549
2582
  constructor(config: AccountServiceConfig);
2550
2583
  status(): Promise<Result<AccountStatus>>;
2551
2584
  readonly applications: {
2552
2585
  list: (options?: AccountApplicationListOptions) => Promise<Result<AccountApplication[]>>;
2553
2586
  get: (appId: string) => Promise<Result<AccountApplication>>;
2554
- register: (manifest: Manifest | Manifest[]) => Promise<Result<AccountApplication>>;
2587
+ register: (manifest: Manifest | Manifest[], options?: AccountApplicationRegisterOptions) => Promise<Result<AccountApplication>>;
2555
2588
  remove: (appId: string) => Promise<Result<void>>;
2556
2589
  };
2557
2590
  readonly spaces: {
@@ -2596,7 +2629,19 @@ declare class AccountService {
2596
2629
  private replaceApplicationsIndexQuietly;
2597
2630
  private replaceSpacesIndexQuietly;
2598
2631
  private replaceDelegationsIndexQuietly;
2632
+ /**
2633
+ * Apply the account index schema at most once per account space.
2634
+ *
2635
+ * The index is a cache over canonical KV state, and every caller already
2636
+ * tolerates it being absent (`ignoreIndexFailure` / `isMissingIndexError`),
2637
+ * so a memo that is wrong in the "already applied" direction degrades to a
2638
+ * `no such table` read that the caller handles. A memo that is wrong in the
2639
+ * other direction would be a real bug, so failures are never cached.
2640
+ */
2599
2641
  private ensureAccountIndex;
2642
+ /** Drop the memo so the next {@link ensureAccountIndex} re-applies the schema. */
2643
+ private invalidateAccountIndexMemo;
2644
+ private applyAccountIndex;
2600
2645
  }
2601
2646
  interface AccountIndexStatus {
2602
2647
  database: string;
@@ -2731,7 +2776,7 @@ interface LocationRecord extends LocationRecordPayload {
2731
2776
  type LocationSource = "explicit" | "local-loopback" | "local-link" | "blockchain" | "centralized" | "fallback";
2732
2777
  declare const DEFAULT_TINYCLOUD_LOCATION_REGISTRY_URL = "https://registry.tinycloud.xyz";
2733
2778
  declare const DEFAULT_TINYCLOUD_FALLBACK_HOST = "https://node.tinycloud.xyz";
2734
- /** Default local loopback node URL probed before registry/fallback resolution. */
2779
+ /** Conventional local loopback node URL. Probed only when explicitly configured. */
2735
2780
  declare const DEFAULT_LOCAL_NODE_URL = "http://127.0.0.1:8000";
2736
2781
  /** Probe timeout for the local loopback candidate. No retries. */
2737
2782
  declare const LOCAL_LOOPBACK_PROBE_TIMEOUT_MS = 250;
@@ -2818,7 +2863,7 @@ interface DiscoverLocalTinyCloudNodeOptions {
2818
2863
  subject?: string;
2819
2864
  /** Enable local-node auto-discovery. Default true. */
2820
2865
  autoDiscoverLocalNode?: boolean;
2821
- /** Local loopback node URL to probe. Default http://127.0.0.1:8000. */
2866
+ /** Local loopback node URL to probe. Omit to avoid probing loopback. */
2822
2867
  localNodeUrl?: string;
2823
2868
  /** Known `*.local.tinycloud.link` subdomain name, probed directly. */
2824
2869
  localLinkName?: string;
@@ -2852,12 +2897,12 @@ interface ResolveTinyCloudHostsOptions {
2852
2897
  /** Verify centralized/blockchain record signatures. Default true. */
2853
2898
  verifyRecords?: boolean;
2854
2899
  /**
2855
- * Probe for a locally-running TinyCloud node before falling back to
2856
- * registry/hosted resolution. Default true. Setting false restores the
2857
- * exact pre-TC-106 resolution order (registry → fallback).
2900
+ * Probe configured and registered local TinyCloud node endpoints before
2901
+ * falling back to registry/hosted resolution. Default true. The loopback
2902
+ * URL is probed only when `localNodeUrl` is explicitly configured.
2858
2903
  */
2859
2904
  autoDiscoverLocalNode?: boolean;
2860
- /** Local loopback node URL to probe. Default http://127.0.0.1:8000. */
2905
+ /** Local loopback node URL to probe. Omit to avoid probing loopback. */
2861
2906
  localNodeUrl?: string;
2862
2907
  /** Known `*.local.tinycloud.link` subdomain name, probed directly. */
2863
2908
  localLinkName?: string;
@@ -2943,4 +2988,4 @@ declare const EXPIRY: {
2943
2988
  declare const DEFAULT_SIGNED_READ_URL_EXPIRY_MS: number;
2944
2989
  type ExpiryTier = keyof typeof EXPIRY;
2945
2990
 
2946
- export { ACCOUNT_REGISTRY_PATH, ACCOUNT_REGISTRY_SPACE, type AbilitiesMap, type AccountApplication, type AccountApplicationListOptions, type AccountDelegation, type AccountDelegationListOptions, AccountDelegationPage, AccountDelegationQueryOptions, type AccountDelegationRevokeOptions, type AccountIndexEnsureResult, type AccountIndexRebuildResult, type AccountIndexStatus, type AccountIndexedReadOptions, AccountService, type AccountServiceConfig, type AccountSpace, type AccountSpaceListOptions, type AccountStatus, AutoApproveSpaceCreationHandler, type AutoRejectStrategy, type AutoSignStrategy, type Bytes, type CallbackStrategy, type CanonicalAddress, type CanonicalParsedNetworkId, CaveatedDelegationUnsupportedError, type ClientSession, ClientSessionSchema, CloudLocationResolutionError, type ComposeManifestOptions, type ComposedManifestRequest, type CreateDelegationFunction, CreateDelegationParams, DEFAULT_DEFAULTS, DEFAULT_EXPIRY, DEFAULT_KNOWLEDGE_ROOT, DEFAULT_LOCAL_NODE_PIN_STORAGE_KEY, DEFAULT_LOCAL_NODE_URL, DEFAULT_MANIFEST_SPACE, DEFAULT_MANIFEST_VERSION, DEFAULT_SIGNED_READ_URL_EXPIRY_MS, DEFAULT_TINYCLOUD_FALLBACK_HOST, DEFAULT_TINYCLOUD_LOCATION_REGISTRY_URL, Delegation, DelegationManager, DelegationRevocationReceipt, type DidCacheKeyOptions, type DidEqualsOptions, type DiscoverLocalTinyCloudNodeOptions, type DiscoveredLocalTinyCloudNode, ENCRYPTION_MANIFEST_SPACE, ENCRYPTION_PERMISSION_SERVICE, EXPIRY, type EnsData, EnsDataSchema, type EventEmitterStrategy, type ExpiryTier, type Extension, GenerateShareParams, ICapabilityKeyRegistry, type IENSResolver, type INotificationHandler, type ISessionManager, type ISessionStorage, ISharingService, type ISigner, type ISpace, type ISpaceCreationHandler, type ISpaceScopedDelegations, type ISpaceScopedSharing, type ISpaceService, type IUserAuthorization, type IWasmBindings, IdentityParseError, LOCAL_LINK_HOST_SUFFIX, LOCAL_LINK_PROBE_TIMEOUT_MS, LOCAL_LOOPBACK_PROBE_TIMEOUT_MS, type LocalNodeIdentityStore, type LocationCandidate, type LocationCandidateInput, type LocationRecord, type LocationRecordPayload, type LocationRecordSigner, LocationRecordValidationError, type LocationResolutionAttempt, type LocationSource, type Manifest, type ManifestDefaults, type ManifestKnowledge, type ManifestRegistryRecord, type ManifestSecretActions, ManifestValidationError, type NodeInfo, type OpenKeyCallbackStrategy, type OpenKeySigningRequestBody, type OpenKeySigningResponseBody, type OpenKeySigningStrategyOptions, type ParseRecapFromSiwe, type PartialSiweMessage, type PermissionEntry, PermissionNotInManifestError, type PersistedSessionData, type PersistedSessionProof, type PersistedTinyCloudSession, type PkhDidParts, ProtocolMismatchError, type ResolveCloudLocationOptions, type ResolveTinyCloudHostsOptions, type ResolvedCapabilities, type ResolvedCloudLocation, type ResolvedDelegate, type ResolvedTinyCloudHosts, type ResourceCapability, SECRETS_SPACE, SERVICE_LONG_TO_SHORT, SERVICE_SHORT_TO_LONG, type ServerHost, SessionExpiredError, ShareLink, type SignCallback, type SignInOptions, type SignRequest, type SignResponse, type SignStrategy, SilentNotificationHandler, type SiweConfig, SiweConfigSchema, Space, type SpaceAbilitiesMap, type SpaceConfig, type SpaceCreationContext, type SpaceDelegationParams, type SpaceErrorCode, SpaceErrorCodes, type SpaceHostResult, SpaceInfo, SpaceService, type SpaceServiceConfig, type SubsetCheckResult, TinyCloud, type TinyCloudConfig, type TinyCloudSession, UnsupportedFeatureError, type UserAuthorizationConfig, VAULT_PERMISSION_SERVICE, type ValidatedPersistedSessionProof, type ValidationError, VersionCheckError, type WasmRecapEntry, type WebStorageLike, actionContains, activateSessionWithHost, addressStorageKey, applyPrefix, buildSpaceUri, canonicalLocationPayload, canonicalizeAddress, canonicalizeDid, canonicalizeDidUrl, canonicalizeNetworkId, canonicalizeRecapCaveats, checkNodeInfo, composeManifestRequest, createInMemoryLocalNodeIdentityStore, createLocalStorageLocalNodeIdentityStore, createOpenKeyCallbackSigningStrategy, createSpaceService, defaultSignStrategy, defaultSpaceCreationHandler, didCacheKey, didEquals, discoverLocalTinyCloudNode, expandActionShortNames, expandPermissionEntries, expandPermissionEntry, fetchLocationRecord, fetchPeerId, httpUrlToMultiaddr, isCapabilitySubset, isEvmAddress, loadManifest, locationPayloadForRecord, makePkhSpaceId, makePublicSpaceId, manifestAbilitiesUnion, multiaddrToHttpUrl, normalizeDefaults, parseCanonicalNetworkId, parseExpiry, parsePkhDid, parseRecapCapabilities, parseSpaceUri, pkhDid, principalDid, principalDidEquals, recapCaveatsEqual, resolveCloudLocation, resolveManifest, resolveManifestKnowledgeRoot, resolveTinyCloudHosts, resourceCapabilitiesToAbilitiesMap, resourceCapabilitiesToSpaceAbilitiesMap, signLocationRecord, submitHostDelegation, validateClientSession, validateLocationRecord, validateLocationRecordPayload, validateManifest, validatePersistedSessionData, verifyDidKeyEd25519Signature, verifyLocationRecord };
2991
+ export { ACCOUNT_REGISTRY_PATH, ACCOUNT_REGISTRY_SPACE, type AbilitiesMap, type AccountApplication, type AccountApplicationListOptions, type AccountApplicationRegisterOptions, type AccountDelegation, type AccountDelegationListOptions, AccountDelegationPage, AccountDelegationQueryOptions, type AccountDelegationRevokeOptions, type AccountIndexEnsureResult, type AccountIndexRebuildResult, type AccountIndexStatus, type AccountIndexedReadOptions, AccountService, type AccountServiceConfig, type AccountSpace, type AccountSpaceListOptions, type AccountStatus, AutoApproveSpaceCreationHandler, type AutoRejectStrategy, type AutoSignStrategy, type Bytes, type CallbackStrategy, type CanonicalAddress, type CanonicalParsedNetworkId, CaveatedDelegationUnsupportedError, type ClientSession, ClientSessionSchema, CloudLocationResolutionError, type ComposeManifestOptions, type ComposedManifestRequest, type CreateDelegationFunction, CreateDelegationParams, DEFAULT_DEFAULTS, DEFAULT_EXPIRY, DEFAULT_KNOWLEDGE_ROOT, DEFAULT_LOCAL_NODE_PIN_STORAGE_KEY, DEFAULT_LOCAL_NODE_URL, DEFAULT_MANIFEST_SPACE, DEFAULT_MANIFEST_VERSION, DEFAULT_SIGNED_READ_URL_EXPIRY_MS, DEFAULT_TINYCLOUD_FALLBACK_HOST, DEFAULT_TINYCLOUD_LOCATION_REGISTRY_URL, Delegation, DelegationManager, DelegationRevocationReceipt, type DidCacheKeyOptions, type DidEqualsOptions, type DiscoverLocalTinyCloudNodeOptions, type DiscoveredLocalTinyCloudNode, ENCRYPTION_MANIFEST_SPACE, ENCRYPTION_PERMISSION_SERVICE, EXPIRY, type EnsData, EnsDataSchema, type EventEmitterStrategy, type ExpiryTier, type Extension, GenerateShareParams, ICapabilityKeyRegistry, type IENSResolver, type INotificationHandler, type ISessionManager, type ISessionStorage, ISharingService, type ISigner, type ISpace, type ISpaceCreationHandler, type ISpaceScopedDelegations, type ISpaceScopedSharing, type ISpaceService, type IUserAuthorization, type IWasmBindings, IdentityParseError, LOCAL_LINK_HOST_SUFFIX, LOCAL_LINK_PROBE_TIMEOUT_MS, LOCAL_LOOPBACK_PROBE_TIMEOUT_MS, type LocalNodeIdentityStore, type LocationCandidate, type LocationCandidateInput, type LocationRecord, type LocationRecordPayload, type LocationRecordSigner, LocationRecordValidationError, type LocationResolutionAttempt, type LocationSource, type Manifest, type ManifestDefaults, type ManifestKnowledge, type ManifestRegistryRecord, type ManifestSecretActions, ManifestValidationError, type NodeInfo, type OpenKeyCallbackStrategy, type OpenKeySigningRequestBody, type OpenKeySigningResponseBody, type OpenKeySigningStrategyOptions, type ParseRecapFromSiwe, type PartialSiweMessage, type PermissionEntry, PermissionNotInManifestError, type PersistedSessionData, type PersistedSessionProof, type PersistedTinyCloudSession, type PkhDidParts, ProtocolMismatchError, type ResolveCloudLocationOptions, type ResolveTinyCloudHostsOptions, type ResolvedCapabilities, type ResolvedCloudLocation, type ResolvedDelegate, type ResolvedTinyCloudHosts, type ResourceCapability, SECRETS_SPACE, SERVICE_LONG_TO_SHORT, SERVICE_SHORT_TO_LONG, type ServerHost, SessionExpiredError, ShareLink, type SignCallback, type SignInOptions, type SignRequest, type SignResponse, type SignStrategy, SilentNotificationHandler, type SiweConfig, SiweConfigSchema, Space, type SpaceAbilitiesMap, type SpaceConfig, type SpaceCreationContext, type SpaceDelegationParams, type SpaceErrorCode, SpaceErrorCodes, type SpaceHostResult, SpaceInfo, SpaceService, type SpaceServiceConfig, type SubsetCheckResult, TinyCloud, type TinyCloudConfig, type TinyCloudSession, UnsupportedFeatureError, type UserAuthorizationConfig, VAULT_PERMISSION_SERVICE, type ValidatedPersistedSessionProof, type ValidationError, VersionCheckError, type WasmRecapEntry, type WebStorageLike, actionContains, activateSessionWithHost, addressStorageKey, applyPrefix, buildSpaceUri, canonicalLocationPayload, canonicalizeAddress, canonicalizeDid, canonicalizeDidUrl, canonicalizeNetworkId, canonicalizeRecapCaveats, checkNodeInfo, composeManifestRequest, createInMemoryLocalNodeIdentityStore, createLocalStorageLocalNodeIdentityStore, createOpenKeyCallbackSigningStrategy, createSpaceService, defaultSignStrategy, defaultSpaceCreationHandler, didCacheKey, didEquals, discoverLocalTinyCloudNode, expandActionShortNames, expandPermissionEntries, expandPermissionEntry, fetchLocationRecord, fetchPeerId, httpUrlToMultiaddr, isCapabilitySubset, isEvmAddress, loadManifest, locationPayloadForRecord, makePkhSpaceId, makePublicSpaceId, manifestAbilitiesUnion, multiaddrToHttpUrl, normalizeDefaults, parseCanonicalNetworkId, parseExpiry, parsePkhDid, parseRecapCapabilities, parseSpaceUri, pkhDid, principalDid, principalDidEquals, recapCaveatsEqual, resolveCloudLocation, resolveManifest, resolveManifestKnowledgeRoot, resolveTinyCloudHosts, resourceCapabilitiesToAbilitiesMap, resourceCapabilitiesToSpaceAbilitiesMap, signLocationRecord, submitHostDelegation, validateClientSession, validateLocationRecord, validateLocationRecordPayload, validateManifest, validatePersistedSessionData, verifyDidKeyEd25519Signature, verifyLocationRecord };
package/dist/index.js CHANGED
@@ -6360,15 +6360,28 @@ function normalizeSpace(space) {
6360
6360
  function isCapabilitySubset(requested, granted) {
6361
6361
  const missing = [];
6362
6362
  for (const req of requested) {
6363
- const match = granted.find((g) => canonicalizeEntryMatches(req, g));
6364
- if (match === void 0) {
6363
+ const matchingScope = granted.filter(
6364
+ (entry) => canonicalizeEntryScopeMatches(req, entry)
6365
+ );
6366
+ const requestedActions = expandActionShortNames(
6367
+ req.service,
6368
+ req.actions
6369
+ );
6370
+ const grantedActions = matchingScope.flatMap(
6371
+ (entry) => expandActionShortNames(entry.service, entry.actions)
6372
+ );
6373
+ const covered = requestedActions.every(
6374
+ (requestedAction) => grantedActions.some(
6375
+ (grantedAction) => actionContains(grantedAction, requestedAction)
6376
+ )
6377
+ );
6378
+ if (!covered) {
6365
6379
  missing.push(cloneEntry(req));
6366
- continue;
6367
6380
  }
6368
6381
  }
6369
6382
  return { subset: missing.length === 0, missing };
6370
6383
  }
6371
- function canonicalizeEntryMatches(requested, granted) {
6384
+ function canonicalizeEntryScopeMatches(requested, granted) {
6372
6385
  if (requested.service !== granted.service) {
6373
6386
  return false;
6374
6387
  }
@@ -6378,15 +6391,6 @@ function canonicalizeEntryMatches(requested, granted) {
6378
6391
  if (!pathContains2(granted.path, requested.path)) {
6379
6392
  return false;
6380
6393
  }
6381
- const reqActions = new Set(
6382
- expandActionShortNames(requested.service, requested.actions)
6383
- );
6384
- const grantedActions = expandActionShortNames(granted.service, granted.actions);
6385
- for (const a of reqActions) {
6386
- if (!grantedActions.some((grantedAction) => actionContains(grantedAction, a))) {
6387
- return false;
6388
- }
6389
- }
6390
6394
  if (!recapCaveatsEqual(requested.caveats, granted.caveats)) {
6391
6395
  return false;
6392
6396
  }
@@ -11799,6 +11803,22 @@ var ACCOUNT_SPACES_PATH = "spaces/";
11799
11803
  var AccountService = class {
11800
11804
  constructor(config) {
11801
11805
  this.config = config;
11806
+ /**
11807
+ * Per-account-space memo for {@link ensureAccountIndex}.
11808
+ *
11809
+ * Applying the index migration is a no-op once the schema exists, but the
11810
+ * SQL service still spends two round trips discovering that (create the
11811
+ * migrations table, then read the applied-migration rows). Account bootstrap
11812
+ * touches the index eight times, so without a memo that is ~14 wasted
11813
+ * requests on the cold sign-in path. The in-flight dedupe inside SQLService
11814
+ * never helps here: the node SDK builds a fresh SQLService per
11815
+ * `getAccountDb()` call, so its lock map is always empty.
11816
+ *
11817
+ * Keyed by account space id, so a different account never reuses this entry.
11818
+ * Failures are evicted so a later call retries instead of inheriting a
11819
+ * permanently poisoned result.
11820
+ */
11821
+ this.accountIndexReady = /* @__PURE__ */ new Map();
11802
11822
  this.applications = {
11803
11823
  list: async (options2 = {}) => {
11804
11824
  if (options2.preferIndex) {
@@ -11832,7 +11852,7 @@ var AccountService = class {
11832
11852
  if (!loaded.ok) return accountErr(loaded.error);
11833
11853
  return ok3(applicationFromRecord(key, loaded.data.data));
11834
11854
  },
11835
- register: async (manifest) => {
11855
+ register: async (manifest, options2 = {}) => {
11836
11856
  const manifests = Array.isArray(manifest) ? manifest : [manifest];
11837
11857
  const request = composeManifestRequest(manifests);
11838
11858
  if (request.registryRecords.length === 0) {
@@ -11850,7 +11870,7 @@ var AccountService = class {
11850
11870
  let registered;
11851
11871
  for (const record of request.registryRecords) {
11852
11872
  const manifestHash = hashJson(record.manifests);
11853
- if (await this.indexHasApplicationHash(record.app_id, manifestHash)) {
11873
+ if (!options2.assumeUnregistered && await this.indexHasApplicationHash(record.app_id, manifestHash)) {
11854
11874
  registered = {
11855
11875
  appId: record.app_id,
11856
11876
  manifests: record.manifests,
@@ -12031,6 +12051,7 @@ var AccountService = class {
12031
12051
  const delegations = await this.delegations.list();
12032
12052
  if (!delegations.ok) return delegations;
12033
12053
  const syncedAt = (/* @__PURE__ */ new Date()).toISOString();
12054
+ this.invalidateAccountIndexMemo();
12034
12055
  const schema = await this.ensureAccountIndex(dbResult.data);
12035
12056
  if (!schema.ok) return schema;
12036
12057
  const statements = [
@@ -12430,7 +12451,41 @@ var AccountService = class {
12430
12451
  ]);
12431
12452
  });
12432
12453
  }
12454
+ /**
12455
+ * Apply the account index schema at most once per account space.
12456
+ *
12457
+ * The index is a cache over canonical KV state, and every caller already
12458
+ * tolerates it being absent (`ignoreIndexFailure` / `isMissingIndexError`),
12459
+ * so a memo that is wrong in the "already applied" direction degrades to a
12460
+ * `no such table` read that the caller handles. A memo that is wrong in the
12461
+ * other direction would be a real bug, so failures are never cached.
12462
+ */
12433
12463
  async ensureAccountIndex(db) {
12464
+ const key = this.config.getAccountSpaceId() ?? "";
12465
+ const cached = this.accountIndexReady.get(key);
12466
+ if (cached) return cached;
12467
+ const pending = this.applyAccountIndex(db);
12468
+ this.accountIndexReady.set(key, pending);
12469
+ const evict = () => {
12470
+ if (this.accountIndexReady.get(key) === pending) {
12471
+ this.accountIndexReady.delete(key);
12472
+ }
12473
+ };
12474
+ let settled;
12475
+ try {
12476
+ settled = await pending;
12477
+ } catch (error) {
12478
+ evict();
12479
+ throw error;
12480
+ }
12481
+ if (!settled.ok) evict();
12482
+ return settled;
12483
+ }
12484
+ /** Drop the memo so the next {@link ensureAccountIndex} re-applies the schema. */
12485
+ invalidateAccountIndexMemo() {
12486
+ this.accountIndexReady.delete(this.config.getAccountSpaceId() ?? "");
12487
+ }
12488
+ async applyAccountIndex(db) {
12434
12489
  const migrated = await db.migrations.apply({
12435
12490
  namespace: ACCOUNT_INDEX_NAMESPACE,
12436
12491
  migrations: [
@@ -15077,13 +15132,14 @@ async function discoverLocalTinyCloudNode(options2 = {}) {
15077
15132
  }
15078
15133
  const fetchFn = options2.fetch ?? globalThis.fetch;
15079
15134
  const identityStore = options2.identityStore ?? defaultLocalNodeIdentityStore;
15080
- const staticCandidates = [
15081
- {
15135
+ const staticCandidates = [];
15136
+ if (options2.localNodeUrl) {
15137
+ staticCandidates.push({
15082
15138
  source: "local-loopback",
15083
- url: options2.localNodeUrl ?? DEFAULT_LOCAL_NODE_URL,
15139
+ url: options2.localNodeUrl,
15084
15140
  timeoutMs: LOCAL_LOOPBACK_PROBE_TIMEOUT_MS
15085
- }
15086
- ];
15141
+ });
15142
+ }
15087
15143
  if (options2.localLinkName) {
15088
15144
  if (isValidDnsLabel(options2.localLinkName)) {
15089
15145
  staticCandidates.push({