@tinycloud/node-sdk 2.4.0-beta.9 → 2.4.0

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
@@ -18,7 +18,14 @@ import {
18
18
  pkhDid as pkhDid3,
19
19
  principalDid,
20
20
  principalDidEquals as principalDidEquals3,
21
- parseCanonicalNetworkId
21
+ parseCanonicalNetworkId,
22
+ TinyCloudDebugLogger,
23
+ tinyCloudDebugLogger,
24
+ enableTinyCloudDebug,
25
+ disableTinyCloudDebug,
26
+ getTinyCloudDebugLogs,
27
+ clearTinyCloudDebugLogs,
28
+ installTinyCloudDebugGlobals
22
29
  } from "@tinycloud/sdk-core";
23
30
 
24
31
  // src/storage/MemorySessionStorage.ts
@@ -234,6 +241,7 @@ import {
234
241
  } from "@tinycloud/sdk-core";
235
242
 
236
243
  // src/authorization/strategies.ts
244
+ import { createOpenKeyCallbackSigningStrategy } from "@tinycloud/sdk-core";
237
245
  var defaultSignStrategy = { type: "auto-sign" };
238
246
 
239
247
  // src/authorization/NodeUserAuthorization.ts
@@ -260,6 +268,7 @@ var NodeUserAuthorization = class {
260
268
  constructor(config) {
261
269
  this.extensions = [];
262
270
  this._nodeFeatures = [];
271
+ this._lastActivationSkippedSpaceIds = [];
263
272
  this.wasm = config.wasmBindings;
264
273
  this.signer = config.signer;
265
274
  this.signStrategy = config.signStrategy ?? defaultSignStrategy;
@@ -282,7 +291,7 @@ var NodeUserAuthorization = class {
282
291
  "": [
283
292
  "tinycloud.sql/read",
284
293
  "tinycloud.sql/write",
285
- "tinycloud.sql/ddl",
294
+ "tinycloud.sql/schema",
286
295
  "tinycloud.sql/admin",
287
296
  "tinycloud.sql/export"
288
297
  ]
@@ -374,12 +383,48 @@ var NodeUserAuthorization = class {
374
383
  * `siwe` is the load-bearing one because `extractSiweExpiration` returns
375
384
  * undefined for missing SIWEs and the SDK then treats the session as
376
385
  * expired-at-epoch-zero.
386
+ *
387
+ * @param hosts - The TinyCloud hosts this session was created against,
388
+ * as persisted in {@link PersistedSessionData.tinycloudHosts}. When
389
+ * present (and non-empty) they are adopted directly so the restored
390
+ * session resolves to the same node as the original sign-in without
391
+ * re-running registry/fallback resolution. When absent (old session)
392
+ * hosts are resolved lazily on the first host-needing call via
393
+ * {@link ensureTinyCloudHosts}.
377
394
  */
378
- setRestoredTinyCloudSession(session) {
395
+ setRestoredTinyCloudSession(session, hosts) {
379
396
  const address = canonicalizeAddress(session.address);
380
397
  this._tinyCloudSession = { ...session, address };
381
398
  this._address = address;
382
399
  this._chainId = session.chainId;
400
+ if ((!this.tinycloudHosts || this.tinycloudHosts.length === 0) && hosts && hosts.length > 0) {
401
+ this.tinycloudHosts = [...hosts];
402
+ }
403
+ }
404
+ /**
405
+ * Ensure `tinycloudHosts` are resolved before a host-needing call.
406
+ *
407
+ * Fresh sign-in resolves hosts up front; a restored session may not have
408
+ * (old persisted sessions predate {@link PersistedSessionData.tinycloudHosts}).
409
+ * This guard makes a restored session resolve the node exactly like a fresh
410
+ * sign-in — persisted hosts are preferred (already set by
411
+ * {@link setRestoredTinyCloudSession}), otherwise the registry/fallback
412
+ * resolution runs lazily here. Idempotent: {@link resolveTinyCloudHostsForSignIn}
413
+ * early-returns when hosts are already set.
414
+ *
415
+ * Throws if hosts are unset and the restored session has no address/chainId
416
+ * to resolve from — a real failure that must surface, not be masked.
417
+ */
418
+ async ensureTinyCloudHosts() {
419
+ if (this.tinycloudHosts && this.tinycloudHosts.length > 0) {
420
+ return;
421
+ }
422
+ if (this._address === void 0 || this._chainId === void 0) {
423
+ throw new Error(
424
+ "Cannot resolve TinyCloud hosts: no address/chainId available. Sign in or restore a session first."
425
+ );
426
+ }
427
+ await this.resolveTinyCloudHostsForSignIn(this._address, this._chainId);
383
428
  }
384
429
  async resolveTinyCloudHostsForSignIn(address, chainId) {
385
430
  if (this.tinycloudHosts && this.tinycloudHosts.length > 0) {
@@ -404,6 +449,9 @@ var NodeUserAuthorization = class {
404
449
  get nodeFeatures() {
405
450
  return this._nodeFeatures;
406
451
  }
452
+ get lastActivationSkippedSpaceIds() {
453
+ return [...this._lastActivationSkippedSpaceIds];
454
+ }
407
455
  /**
408
456
  * Compute the `abilities` map the WASM `prepareSession` call should
409
457
  * see at sign-in time.
@@ -576,6 +624,7 @@ var NodeUserAuthorization = class {
576
624
  if (!this._tinyCloudSession || !this._address || !this._chainId) {
577
625
  throw new Error("Must be signed in to host space");
578
626
  }
627
+ await this.ensureTinyCloudHosts();
579
628
  const host = this.primaryTinyCloudHost;
580
629
  const spaceId = targetSpaceId ?? this._tinyCloudSession.spaceId;
581
630
  const peerId = await fetchPeerId(host, spaceId);
@@ -618,12 +667,14 @@ var NodeUserAuthorization = class {
618
667
  if (!this._tinyCloudSession) {
619
668
  throw new Error("Must be signed in to ensure space exists");
620
669
  }
670
+ await this.ensureTinyCloudHosts();
621
671
  const host = this.primaryTinyCloudHost;
622
672
  const primarySpaceId = this._tinyCloudSession.spaceId;
623
673
  const result = await activateSessionWithHost(
624
674
  host,
625
675
  this._tinyCloudSession.delegationHeader
626
676
  );
677
+ this.recordActivationSkippedSpaces(result, primarySpaceId);
627
678
  const handler = this.spaceCreationHandler ?? (this.autoCreateSpace ? new AutoApproveSpaceCreationHandler() : void 0);
628
679
  const creationContext = {
629
680
  spaceId: primarySpaceId,
@@ -711,6 +762,66 @@ var NodeUserAuthorization = class {
711
762
  }
712
763
  throw new Error(`Failed to activate session: ${result.error}`);
713
764
  }
765
+ recordActivationSkippedSpaces(result, primarySpaceId) {
766
+ if (result.success) {
767
+ this._lastActivationSkippedSpaceIds = result.skipped ?? [];
768
+ return;
769
+ }
770
+ this._lastActivationSkippedSpaceIds = result.status === 404 ? [primarySpaceId] : [];
771
+ }
772
+ async createBootstrapSession(options) {
773
+ if (!this._address || !this._chainId) {
774
+ throw new Error("Must be signed in before creating bootstrap sessions");
775
+ }
776
+ const address = this._address;
777
+ const chainId = this._chainId;
778
+ const keyId = `bootstrap-${Date.now()}-${Math.random().toString(36).slice(2)}`;
779
+ this.sessionManager.createSessionKey(keyId);
780
+ const jwkString = this.sessionManager.jwk(keyId);
781
+ if (!jwkString) {
782
+ throw new Error("Failed to create bootstrap session key");
783
+ }
784
+ const jwk = JSON.parse(jwkString);
785
+ const now = /* @__PURE__ */ new Date();
786
+ const expirationTime = new Date(now.getTime() + this.sessionExpirationMs);
787
+ const abilities = resourceCapabilitiesToAbilitiesMap(
788
+ options.capabilityRequest.resources
789
+ );
790
+ const prepared = this.wasm.prepareSession({
791
+ abilities,
792
+ ...options.rawAbilities !== void 0 ? { rawAbilities: options.rawAbilities } : {},
793
+ address,
794
+ chainId,
795
+ domain: this.domain,
796
+ issuedAt: now.toISOString(),
797
+ expirationTime: expirationTime.toISOString(),
798
+ spaceId: options.spaceId,
799
+ jwk,
800
+ ...this.buildSiweOverrides()
801
+ });
802
+ const signature = await this.requestSignature({
803
+ address,
804
+ chainId,
805
+ message: prepared.siwe,
806
+ type: "siwe"
807
+ });
808
+ const session = this.wasm.completeSessionSetup({
809
+ ...prepared,
810
+ signature
811
+ });
812
+ return {
813
+ address,
814
+ chainId,
815
+ sessionKey: keyId,
816
+ spaceId: options.spaceId,
817
+ delegationCid: session.delegationCid,
818
+ delegationHeader: session.delegationHeader,
819
+ verificationMethod: this.sessionManager.getDID(keyId),
820
+ jwk,
821
+ siwe: prepared.siwe,
822
+ signature
823
+ };
824
+ }
714
825
  /**
715
826
  * Sign in and create a new session.
716
827
  *
@@ -799,7 +910,8 @@ var NodeUserAuthorization = class {
799
910
  },
800
911
  expiresAt: expirationTime.toISOString(),
801
912
  createdAt: now.toISOString(),
802
- version: "1.0"
913
+ version: "1.0",
914
+ tinycloudHosts: this.tinycloudHosts
803
915
  };
804
916
  await this.sessionStorage.save(address, persistedData);
805
917
  this._session = clientSession;
@@ -966,7 +1078,8 @@ var NodeUserAuthorization = class {
966
1078
  },
967
1079
  expiresAt,
968
1080
  createdAt,
969
- version: "1.0"
1081
+ version: "1.0",
1082
+ tinycloudHosts: this.tinycloudHosts
970
1083
  };
971
1084
  await this.sessionStorage.save(address, persistedData);
972
1085
  this._session = clientSession;
@@ -1077,6 +1190,11 @@ import {
1077
1190
  UnsupportedFeatureError,
1078
1191
  makePublicSpaceId,
1079
1192
  ACCOUNT_REGISTRY_SPACE,
1193
+ BOOTSTRAP_SESSION_REQUESTS,
1194
+ SECRET_RECORDS_SCHEMA,
1195
+ SECRETS_SPACE as SECRETS_SPACE2,
1196
+ TINYCLOUD_SECRETS_BOOTSTRAP_MANIFEST,
1197
+ bootstrapSteps,
1080
1198
  ENCRYPTION_PERMISSION_SERVICE as ENCRYPTION_PERMISSION_SERVICE2,
1081
1199
  PermissionNotInManifestError,
1082
1200
  SessionExpiredError,
@@ -1090,6 +1208,7 @@ import {
1090
1208
  verifyDidKeyEd25519Signature,
1091
1209
  canonicalizeAddress as canonicalizeAddress2,
1092
1210
  pkhDid as pkhDid2,
1211
+ resolveTinyCloudHosts as resolveTinyCloudHosts2,
1093
1212
  principalDidEquals as principalDidEquals2,
1094
1213
  parseNetworkId as parseNetworkId2
1095
1214
  } from "@tinycloud/sdk-core";
@@ -1106,12 +1225,13 @@ import {
1106
1225
  ServiceContext
1107
1226
  } from "@tinycloud/sdk-core";
1108
1227
  var DelegatedAccess = class {
1109
- constructor(session, delegation, host, invoke, telemetry) {
1228
+ constructor(session, delegation, host, invoke, invokeAny, telemetry) {
1110
1229
  this.session = session;
1111
1230
  this._delegation = delegation;
1112
1231
  this.host = host;
1113
1232
  this._serviceContext = new ServiceContext({
1114
1233
  invoke,
1234
+ invokeAny,
1115
1235
  fetch: globalThis.fetch.bind(globalThis),
1116
1236
  hosts: [host],
1117
1237
  telemetry
@@ -1518,6 +1638,9 @@ var NETWORK_CREATE_ACTION2 = "tinycloud.encryption/network.create";
1518
1638
  var DECRYPT_ACTION2 = "tinycloud.encryption/decrypt";
1519
1639
  var NETWORK_ADMIN_TYPE = "tinycloud.encryption.network-admin/v1";
1520
1640
  var DEFAULT_SESSION_EXPIRATION_MS = EXPIRY3.SESSION_MS;
1641
+ function isOpenKeyAutoSignStrategy(strategy) {
1642
+ return strategy?.openKeyAutoSign === true;
1643
+ }
1521
1644
  function didPrincipalMatches2(actual, expected) {
1522
1645
  try {
1523
1646
  return principalDidEquals2(actual, expected);
@@ -1742,9 +1865,10 @@ var _TinyCloudNode = class _TinyCloudNode {
1742
1865
  * @internal
1743
1866
  */
1744
1867
  setupAuth(config) {
1868
+ const useBootstrapSignInRequest = this.shouldUseBootstrapSignInRequest(config);
1745
1869
  this.auth = new NodeUserAuthorization({
1746
1870
  signer: this.signer,
1747
- signStrategy: { type: "auto-sign" },
1871
+ signStrategy: config.signStrategy ?? { type: "auto-sign" },
1748
1872
  wasmBindings: this.wasmBindings,
1749
1873
  sessionStorage: config.sessionStorage ?? new MemorySessionStorage(),
1750
1874
  domain: this.siweDomain,
@@ -1753,20 +1877,23 @@ var _TinyCloudNode = class _TinyCloudNode {
1753
1877
  tinycloudHosts: this.explicitHost ? [this.explicitHost] : void 0,
1754
1878
  tinycloudRegistryUrl: config.tinycloudRegistryUrl,
1755
1879
  tinycloudFallbackHosts: config.tinycloudFallbackHosts,
1756
- autoCreateSpace: config.autoCreateSpace,
1880
+ autoCreateSpace: useBootstrapSignInRequest ? false : config.autoCreateSpace,
1757
1881
  enablePublicSpace: config.enablePublicSpace ?? true,
1758
- spaceCreationHandler: config.spaceCreationHandler,
1882
+ spaceCreationHandler: useBootstrapSignInRequest ? void 0 : config.spaceCreationHandler,
1759
1883
  nonce: config.nonce,
1760
1884
  siweConfig: config.siweConfig,
1761
- manifest: config.manifest,
1762
- capabilityRequest: config.capabilityRequest,
1763
- includeAccountRegistryPermissions: config.includeAccountRegistryPermissions
1885
+ manifest: useBootstrapSignInRequest ? void 0 : config.manifest,
1886
+ capabilityRequest: useBootstrapSignInRequest ? BOOTSTRAP_SESSION_REQUESTS.default : config.capabilityRequest,
1887
+ includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : config.includeAccountRegistryPermissions
1764
1888
  });
1765
1889
  this.tc = new TinyCloud(this.auth, {
1766
1890
  invokeAny: this.invokeAnyWithRuntimePermissions,
1767
1891
  telemetry: config.telemetry
1768
1892
  });
1769
1893
  }
1894
+ shouldUseBootstrapSignInRequest(config) {
1895
+ return config.autoBootstrapAccount !== false && config.manifest === void 0 && config.capabilityRequest === void 0 && (config.prefix ?? "default") === "default" && isOpenKeyAutoSignStrategy(config.signStrategy);
1896
+ }
1770
1897
  syncResolvedHostFromAuth() {
1771
1898
  const host = this.auth?.hosts[0];
1772
1899
  if (host) {
@@ -1881,7 +2008,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1881
2008
  getAccountDb: () => this.accountSpaceId ? this.sqlForSpace(this.accountSpaceId).db("account") : void 0,
1882
2009
  ensureAccountSpaceHosted: async () => {
1883
2010
  if (this.accountSpaceId && this.auth) {
1884
- await this.ensureOwnedSpaceHosted(this.accountSpaceId);
2011
+ await this.ensureOwnedSpaceHostedById(this.accountSpaceId);
1885
2012
  }
1886
2013
  }
1887
2014
  });
@@ -1925,11 +2052,14 @@ var _TinyCloudNode = class _TinyCloudNode {
1925
2052
  await this.tc.signIn(options);
1926
2053
  this.syncResolvedHostFromAuth();
1927
2054
  this.initializeServices();
2055
+ const bootstrapped = await this.bootstrapAccountIfNeeded();
1928
2056
  await this.ensureRequestedEncryptionNetworks();
1929
- if (this.config.manifest === void 0 && this.config.capabilityRequest === void 0) {
1930
- await this.ensureOwnedSpaceHosted(this.ownedSpaceId("secrets"));
2057
+ if (!bootstrapped && this.config.manifest === void 0 && this.config.capabilityRequest === void 0) {
2058
+ await this.ensureOwnedSpaceHostedById(this.ownedSpaceId(SECRETS_SPACE2));
2059
+ }
2060
+ if (!bootstrapped) {
2061
+ this.scheduleAccountRegistrySync();
1931
2062
  }
1932
- this.scheduleAccountRegistrySync();
1933
2063
  this.notificationHandler.success("Successfully signed in");
1934
2064
  }
1935
2065
  ownedSpaceId(name) {
@@ -1938,6 +2068,208 @@ var _TinyCloudNode = class _TinyCloudNode {
1938
2068
  }
1939
2069
  return this.wasmBindings.makeSpaceId(this._address, this._chainId, name);
1940
2070
  }
2071
+ async bootstrapAccountIfNeeded() {
2072
+ if (this.config.autoBootstrapAccount === false) {
2073
+ return false;
2074
+ }
2075
+ if (!this.auth || !this._address) {
2076
+ return false;
2077
+ }
2078
+ const steps = bootstrapSteps(this._address, this._chainId);
2079
+ if (!await this.isFreshBootstrapAccount(steps)) {
2080
+ return false;
2081
+ }
2082
+ await this.runAccountBootstrap(steps);
2083
+ return true;
2084
+ }
2085
+ async isFreshBootstrapAccount(steps) {
2086
+ const enshrinedSpaceIds = /* @__PURE__ */ new Set();
2087
+ for (const step of steps) {
2088
+ if (step.kind === "session") {
2089
+ enshrinedSpaceIds.add(step.spaceId);
2090
+ }
2091
+ }
2092
+ const skipped = this.auth.lastActivationSkippedSpaceIds;
2093
+ if (skipped.some((spaceId) => enshrinedSpaceIds.has(spaceId))) {
2094
+ return true;
2095
+ }
2096
+ try {
2097
+ const indexed = await this.account.index.spaces.list();
2098
+ if (indexed.ok && indexed.data.length === 0) {
2099
+ return true;
2100
+ }
2101
+ } catch {
2102
+ }
2103
+ try {
2104
+ const spaces = await this.account.spaces.list();
2105
+ return spaces.ok && spaces.data.length === 0;
2106
+ } catch {
2107
+ return false;
2108
+ }
2109
+ }
2110
+ async runAccountBootstrap(steps) {
2111
+ if (!this.auth || !this._address) {
2112
+ throw new Error("Account bootstrap requires an active wallet session");
2113
+ }
2114
+ const host = this.hosts[0] ?? this.config.host;
2115
+ if (!host) {
2116
+ throw new Error("Account bootstrap requires a TinyCloud host");
2117
+ }
2118
+ const auth = this.auth;
2119
+ const sessions = /* @__PURE__ */ new Map();
2120
+ const rawAbilitiesBySpace = /* @__PURE__ */ new Map();
2121
+ const primarySession = auth.tinyCloudSession;
2122
+ const defaultSpaceId = this.ownedSpaceId("default");
2123
+ const canReusePrimaryBootstrapSession = primarySession?.spaceId === defaultSpaceId && auth.capabilityRequest === BOOTSTRAP_SESSION_REQUESTS.default;
2124
+ for (const step of steps) {
2125
+ if (step.kind !== "session") continue;
2126
+ if (step.space === "default" && canReusePrimaryBootstrapSession && primarySession) {
2127
+ sessions.set(step.space, primarySession);
2128
+ continue;
2129
+ }
2130
+ const rawAbilities = step.rawAbilities;
2131
+ if (rawAbilities) {
2132
+ rawAbilitiesBySpace.set(step.space, rawAbilities);
2133
+ }
2134
+ const session = await auth.createBootstrapSession({
2135
+ spaceId: step.spaceId,
2136
+ capabilityRequest: step.request ?? BOOTSTRAP_SESSION_REQUESTS[step.space],
2137
+ rawAbilities
2138
+ });
2139
+ sessions.set(step.space, session);
2140
+ }
2141
+ for (const step of steps) {
2142
+ if (step.kind !== "host") continue;
2143
+ const hosted = await auth.hostOwnedSpace(step.spaceId);
2144
+ if (!hosted) {
2145
+ throw new Error(`Failed to host bootstrap space: ${step.spaceId}`);
2146
+ }
2147
+ }
2148
+ await new Promise((resolve) => setTimeout(resolve, 100));
2149
+ for (const step of steps) {
2150
+ if (step.kind !== "activate") continue;
2151
+ const session = sessions.get(step.space);
2152
+ if (!session) {
2153
+ throw new Error(`Missing bootstrap session for ${step.space}`);
2154
+ }
2155
+ const activated = await activateSessionWithHost2(host, session.delegationHeader);
2156
+ if (!activated.success || activated.skipped?.includes(step.spaceId)) {
2157
+ throw new Error(
2158
+ `Failed to activate bootstrap session for ${step.spaceId}: ${activated.error ?? "space was skipped"}`
2159
+ );
2160
+ }
2161
+ this.registerBootstrapRuntimeGrant(
2162
+ session,
2163
+ BOOTSTRAP_SESSION_REQUESTS[step.space],
2164
+ rawAbilitiesBySpace.get(step.space)
2165
+ );
2166
+ }
2167
+ for (const step of steps) {
2168
+ if (step.kind === "account-index-schema") {
2169
+ const ensured = await this.account.index.ensure();
2170
+ if (!ensured.ok) {
2171
+ throw new Error(`Failed to create account index schema: ${ensured.error.message}`);
2172
+ }
2173
+ }
2174
+ if (step.kind === "seed-spaces") {
2175
+ for (const space of step.spaces) {
2176
+ const registered = await this.account.spaces.register({
2177
+ spaceId: space.spaceId,
2178
+ name: space.name,
2179
+ ownerDid: this.did,
2180
+ type: "owned",
2181
+ permissions: ["*"],
2182
+ status: "active"
2183
+ });
2184
+ if (!registered.ok) {
2185
+ throw new Error(
2186
+ `Failed to seed account space ${space.spaceId}: ${registered.error.message}`
2187
+ );
2188
+ }
2189
+ }
2190
+ }
2191
+ if (step.kind === "seed-applications") {
2192
+ const registered = await this.account.applications.register(
2193
+ step.manifests.length > 0 ? [...step.manifests] : TINYCLOUD_SECRETS_BOOTSTRAP_MANIFEST
2194
+ );
2195
+ if (!registered.ok) {
2196
+ throw new Error(`Failed to seed bootstrap applications: ${registered.error.message}`);
2197
+ }
2198
+ }
2199
+ if (step.kind === "encryption-network-create") {
2200
+ await this.ensureEncryptionNetwork(step.networkId);
2201
+ }
2202
+ if (step.kind === "secret-records-schema") {
2203
+ const db = this.sqlForSpace(step.spaceId).db(step.database);
2204
+ const migrated = await db.migrations.apply({
2205
+ namespace: "tinycloud.secrets.records",
2206
+ migrations: [
2207
+ {
2208
+ id: "001_initial",
2209
+ sql: [...SECRET_RECORDS_SCHEMA]
2210
+ }
2211
+ ]
2212
+ });
2213
+ if (!migrated.ok) {
2214
+ throw new Error(
2215
+ `Failed to create secret_records schema: ${migrated.error.message}`
2216
+ );
2217
+ }
2218
+ }
2219
+ }
2220
+ }
2221
+ registerBootstrapRuntimeGrant(session, request, rawAbilities) {
2222
+ const operations = [];
2223
+ for (const resource of request.resources) {
2224
+ const service = resource.service.startsWith("tinycloud.") ? this.shortServiceName(resource.service) : resource.service;
2225
+ const spaceId = resource.space.startsWith("tinycloud:") ? resource.space : this.ownedSpaceId(resource.space);
2226
+ for (const action of resource.actions) {
2227
+ operations.push({
2228
+ spaceId,
2229
+ service,
2230
+ path: resource.path,
2231
+ action
2232
+ });
2233
+ }
2234
+ }
2235
+ for (const [resource, actions2] of Object.entries(rawAbilities ?? {})) {
2236
+ for (const action of actions2) {
2237
+ operations.push({
2238
+ resource,
2239
+ service: "encryption",
2240
+ path: resource,
2241
+ action
2242
+ });
2243
+ }
2244
+ }
2245
+ const expiresAt = extractSiweExpiration(session.siwe) ?? this.getSessionExpiry();
2246
+ const actions = [...new Set(operations.map((operation) => operation.action))];
2247
+ this.runtimePermissionGrants.push({
2248
+ session: {
2249
+ delegationHeader: session.delegationHeader,
2250
+ delegationCid: session.delegationCid,
2251
+ spaceId: session.spaceId,
2252
+ verificationMethod: session.verificationMethod,
2253
+ jwk: session.jwk
2254
+ },
2255
+ delegation: {
2256
+ cid: session.delegationCid,
2257
+ delegationHeader: session.delegationHeader,
2258
+ delegateDID: session.verificationMethod,
2259
+ delegatorDID: this.did,
2260
+ spaceId: session.spaceId,
2261
+ path: "",
2262
+ actions,
2263
+ expiry: expiresAt,
2264
+ allowSubDelegation: true,
2265
+ ownerAddress: session.address,
2266
+ chainId: session.chainId,
2267
+ host: this.config.host
2268
+ },
2269
+ operations,
2270
+ expiresAt
2271
+ });
2272
+ }
1941
2273
  async writeManifestRegistryRecords() {
1942
2274
  const request = this.capabilityRequest;
1943
2275
  if (!request || request.registryRecords.length === 0) {
@@ -1947,7 +2279,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1947
2279
  throw new Error("Manifest registry write requires wallet mode");
1948
2280
  }
1949
2281
  const accountSpaceId = this.ownedSpaceId(ACCOUNT_REGISTRY_SPACE);
1950
- await this.ensureOwnedSpaceHosted(accountSpaceId);
2282
+ await this.ensureOwnedSpaceHostedById(accountSpaceId);
1951
2283
  const result = await this.account.applications.register(request.manifests);
1952
2284
  if (!result.ok) {
1953
2285
  throw new Error(
@@ -1957,6 +2289,7 @@ var _TinyCloudNode = class _TinyCloudNode {
1957
2289
  }
1958
2290
  scheduleAccountRegistrySync() {
1959
2291
  void this.withAccountRegistryRetry(async () => {
2292
+ void this.account.index.ensure();
1960
2293
  await this.writeManifestRegistryRecords();
1961
2294
  const spaces = await this.account.spaces.syncAccessible();
1962
2295
  if (!spaces.ok) {
@@ -2008,7 +2341,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2008
2341
  await this.ensureEncryptionNetwork(networkId);
2009
2342
  }
2010
2343
  }
2011
- async ensureOwnedSpaceHosted(spaceId) {
2344
+ async ensureOwnedSpaceHostedById(spaceId) {
2012
2345
  if (!this.auth) {
2013
2346
  throw new Error("Owned space hosting requires wallet mode");
2014
2347
  }
@@ -2051,7 +2384,7 @@ var _TinyCloudNode = class _TinyCloudNode {
2051
2384
  * caller is the root authority of their own owned spaces, so no additional
2052
2385
  * delegation is required.
2053
2386
  *
2054
- * Unlike {@link ensureOwnedSpaceHosted}, this always submits the host
2387
+ * Unlike {@link ensureOwnedSpaceHostedById}, this always submits the host
2055
2388
  * delegation rather than inferring hosting from session activation: a space
2056
2389
  * the current session has never referenced is reported neither as
2057
2390
  * `activated` nor `skipped`, so activation-based detection would wrongly
@@ -2096,6 +2429,91 @@ var _TinyCloudNode = class _TinyCloudNode {
2096
2429
  });
2097
2430
  return spaceId;
2098
2431
  }
2432
+ /**
2433
+ * Ensure one of this user's owned spaces (e.g. `"secrets"`) is hosted on the
2434
+ * server.
2435
+ *
2436
+ * At sign-in, a full-authority session auto-hosts the owner's `secrets`
2437
+ * space, but a session created with a manifest / capabilityRequest does not.
2438
+ * Such a session can therefore hold valid `tinycloud.kv/*` capabilities for
2439
+ * the owned `secrets` space yet still fail its first scoped
2440
+ * `secrets.put(...)` with `404 Space not found`, because the space was never
2441
+ * registered on the node.
2442
+ *
2443
+ * Calling this resolves `name` to the owner's owned-space URI
2444
+ * (`tinycloud:pkh:eip155:<chain>:<addr>:<name>`). It first consults the
2445
+ * account-space spaces registry (`account/spaces/{space_id}`, the canonical
2446
+ * KV source of truth, fronted by a best-effort SQLite index): if the space is
2447
+ * already registered/hosted it returns the URI WITHOUT submitting a host
2448
+ * delegation, avoiding a redundant host-SIWE signature prompt for owners who
2449
+ * already use the space. Only when the space is absent — or the registry
2450
+ * check fails for any reason (e.g. a cold SQLite index reporting
2451
+ * `no such table: spaces`) — does it fall through to {@link hostOwnedSpace}.
2452
+ *
2453
+ * The registry check is purely an optimization: any failure falls back to
2454
+ * hosting, and the host SIWE is idempotent server-side, so re-hosting an
2455
+ * existing space remains a safe no-op. Must be called after {@link signIn}.
2456
+ *
2457
+ * @param name - The owned space name (e.g. `"secrets"`).
2458
+ * @returns The hosted owned-space URI.
2459
+ */
2460
+ async ensureOwnedSpaceHosted(name) {
2461
+ if (!this.auth || !this.auth.tinyCloudSession) {
2462
+ throw new Error("Not signed in. Call signIn() first.");
2463
+ }
2464
+ const spaceId = this.ownedSpaceId(name);
2465
+ if (await this.isOwnedSpaceRegistered(spaceId)) {
2466
+ return spaceId;
2467
+ }
2468
+ const hosted = await this.hostOwnedSpace(name);
2469
+ try {
2470
+ await this.account.spaces.register({
2471
+ spaceId,
2472
+ name,
2473
+ ownerDid: this.did,
2474
+ type: "owned",
2475
+ permissions: ["*"],
2476
+ status: "active"
2477
+ });
2478
+ } catch {
2479
+ }
2480
+ return hosted;
2481
+ }
2482
+ /**
2483
+ * Check whether an owned space is already registered/hosted by consulting the
2484
+ * account spaces registry.
2485
+ *
2486
+ * Source of truth is the canonical KV registry record
2487
+ * `account/spaces/{space_id}`, read here via `account.spaces.get(spaceId)`.
2488
+ * The KV path is used (rather than `syncAccessible()`) because it works under
2489
+ * a manifest/recap session with NO extra prompt: the composed manifest recap
2490
+ * already grants `tinycloud.kv get/list` on the account space `spaces/`
2491
+ * prefix, whereas `syncAccessible()` depends on `tinycloud.space/list`, which
2492
+ * a recap session does not hold. Before reading, it consults the fast SQLite
2493
+ * index (`account.index.spaces.list()`) as a best-effort short-circuit; on a
2494
+ * cold index (`no such table: spaces`) or any other index failure it falls
2495
+ * back to the canonical KV read.
2496
+ *
2497
+ * This is a best-effort optimization. ANY failure of the check path (missing
2498
+ * table, KV error, missing record, thrown exception) resolves to `false` so
2499
+ * the caller falls through to hosting — per the directive, "if it fails in any
2500
+ * way then create the space".
2501
+ */
2502
+ async isOwnedSpaceRegistered(spaceId) {
2503
+ try {
2504
+ const indexed = await this.account.index.spaces.list();
2505
+ if (indexed.ok && indexed.data.some((space) => space.spaceId === spaceId)) {
2506
+ return true;
2507
+ }
2508
+ } catch {
2509
+ }
2510
+ try {
2511
+ const record = await this.account.spaces.get(spaceId);
2512
+ return record.ok;
2513
+ } catch {
2514
+ return false;
2515
+ }
2516
+ }
2099
2517
  /**
2100
2518
  * Restore a previously established session from stored delegation data.
2101
2519
  *
@@ -2125,6 +2543,14 @@ var _TinyCloudNode = class _TinyCloudNode {
2125
2543
  if (sessionData.chainId) {
2126
2544
  this._chainId = sessionData.chainId;
2127
2545
  }
2546
+ const resolvedHost = await this.resolveRestoredHost(
2547
+ sessionData.tinycloudHosts,
2548
+ restoredAddress,
2549
+ sessionData.chainId
2550
+ );
2551
+ if (resolvedHost) {
2552
+ this.config.host = resolvedHost;
2553
+ }
2128
2554
  this._serviceContext = new ServiceContext2({
2129
2555
  invoke: this.invokeWithRuntimePermissions,
2130
2556
  invokeAny: this.invokeAnyWithRuntimePermissions,
@@ -2170,12 +2596,45 @@ var _TinyCloudNode = class _TinyCloudNode {
2170
2596
  signature: sessionData.signature ?? ""
2171
2597
  };
2172
2598
  if (this.auth) {
2173
- this.auth.setRestoredTinyCloudSession(tcSession);
2599
+ this.auth.setRestoredTinyCloudSession(
2600
+ tcSession,
2601
+ this.config.host ? [this.config.host] : void 0
2602
+ );
2174
2603
  } else {
2175
2604
  this._restoredTcSession = tcSession;
2176
2605
  }
2177
2606
  }
2178
2607
  }
2608
+ /**
2609
+ * Resolve the host a restored session should target.
2610
+ *
2611
+ * Mirrors fresh sign-in host resolution but for the restore path:
2612
+ * an explicit/pinned host always wins, then the hosts the session was
2613
+ * persisted with, then a lazy registry/fallback resolution for sessions
2614
+ * that predate the persisted `tinycloudHosts` field. Returns `undefined`
2615
+ * only when there's nothing to resolve from (no explicit host, no
2616
+ * persisted hosts, and no address/chainId) — in which case the existing
2617
+ * `config.host` (default) is left in place.
2618
+ *
2619
+ * Resolution failures are surfaced, not swallowed: a genuinely broken
2620
+ * registry lookup throws rather than silently falling back to a wrong host.
2621
+ */
2622
+ async resolveRestoredHost(persistedHosts, address, chainId) {
2623
+ if (this.explicitHost) {
2624
+ return this.explicitHost;
2625
+ }
2626
+ if (persistedHosts && persistedHosts.length > 0) {
2627
+ return persistedHosts[0];
2628
+ }
2629
+ if (address === void 0 || chainId === void 0) {
2630
+ return void 0;
2631
+ }
2632
+ const resolved = await resolveTinyCloudHosts2(pkhDid2(address, chainId), {
2633
+ registryUrl: this.config.tinycloudRegistryUrl,
2634
+ fallbackHosts: this.config.tinycloudFallbackHosts
2635
+ });
2636
+ return resolved.hosts[0];
2637
+ }
2179
2638
  /**
2180
2639
  * Resolve the currently-active TinyCloudSession, preferring the auth
2181
2640
  * layer's value (wallet mode) and falling back to the node-level
@@ -2220,9 +2679,11 @@ var _TinyCloudNode = class _TinyCloudNode {
2220
2679
  );
2221
2680
  }
2222
2681
  this.signer = _TinyCloudNode.nodeDefaults.createSigner(privateKey);
2682
+ const authConfig = { ...this.config, prefix };
2683
+ const useBootstrapSignInRequest = this.shouldUseBootstrapSignInRequest(authConfig);
2223
2684
  this.auth = new NodeUserAuthorization({
2224
2685
  signer: this.signer,
2225
- signStrategy: { type: "auto-sign" },
2686
+ signStrategy: this.config.signStrategy ?? { type: "auto-sign" },
2226
2687
  wasmBindings: this.wasmBindings,
2227
2688
  sessionStorage: options?.sessionStorage ?? this.config.sessionStorage ?? new MemorySessionStorage(),
2228
2689
  domain: this.siweDomain,
@@ -2231,14 +2692,14 @@ var _TinyCloudNode = class _TinyCloudNode {
2231
2692
  tinycloudHosts: this.explicitHost ? [this.explicitHost] : void 0,
2232
2693
  tinycloudRegistryUrl: this.config.tinycloudRegistryUrl,
2233
2694
  tinycloudFallbackHosts: this.config.tinycloudFallbackHosts,
2234
- autoCreateSpace: this.config.autoCreateSpace,
2695
+ autoCreateSpace: useBootstrapSignInRequest ? false : this.config.autoCreateSpace,
2235
2696
  enablePublicSpace: this.config.enablePublicSpace ?? true,
2236
- spaceCreationHandler: this.config.spaceCreationHandler,
2697
+ spaceCreationHandler: useBootstrapSignInRequest ? void 0 : this.config.spaceCreationHandler,
2237
2698
  nonce: this.config.nonce,
2238
2699
  siweConfig: this.config.siweConfig,
2239
- manifest: this.config.manifest,
2240
- capabilityRequest: this.config.capabilityRequest,
2241
- includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
2700
+ manifest: useBootstrapSignInRequest ? void 0 : this.config.manifest,
2701
+ capabilityRequest: useBootstrapSignInRequest ? BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
2702
+ includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : this.config.includeAccountRegistryPermissions
2242
2703
  });
2243
2704
  this.tc = new TinyCloud(this.auth, {
2244
2705
  invokeAny: this.invokeAnyWithRuntimePermissions,
@@ -2265,9 +2726,11 @@ var _TinyCloudNode = class _TinyCloudNode {
2265
2726
  }
2266
2727
  const prefix = options?.prefix ?? "default";
2267
2728
  this.signer = signer;
2729
+ const authConfig = { ...this.config, prefix };
2730
+ const useBootstrapSignInRequest = this.shouldUseBootstrapSignInRequest(authConfig);
2268
2731
  this.auth = new NodeUserAuthorization({
2269
2732
  signer: this.signer,
2270
- signStrategy: { type: "auto-sign" },
2733
+ signStrategy: this.config.signStrategy ?? { type: "auto-sign" },
2271
2734
  wasmBindings: this.wasmBindings,
2272
2735
  sessionStorage: options?.sessionStorage ?? this.config.sessionStorage ?? new MemorySessionStorage(),
2273
2736
  domain: this.siweDomain,
@@ -2276,14 +2739,14 @@ var _TinyCloudNode = class _TinyCloudNode {
2276
2739
  tinycloudHosts: this.explicitHost ? [this.explicitHost] : void 0,
2277
2740
  tinycloudRegistryUrl: this.config.tinycloudRegistryUrl,
2278
2741
  tinycloudFallbackHosts: this.config.tinycloudFallbackHosts,
2279
- autoCreateSpace: this.config.autoCreateSpace,
2742
+ autoCreateSpace: useBootstrapSignInRequest ? false : this.config.autoCreateSpace,
2280
2743
  enablePublicSpace: this.config.enablePublicSpace ?? true,
2281
- spaceCreationHandler: this.config.spaceCreationHandler,
2744
+ spaceCreationHandler: useBootstrapSignInRequest ? void 0 : this.config.spaceCreationHandler,
2282
2745
  nonce: this.config.nonce,
2283
2746
  siweConfig: this.config.siweConfig,
2284
- manifest: this.config.manifest,
2285
- capabilityRequest: this.config.capabilityRequest,
2286
- includeAccountRegistryPermissions: this.config.includeAccountRegistryPermissions
2747
+ manifest: useBootstrapSignInRequest ? void 0 : this.config.manifest,
2748
+ capabilityRequest: useBootstrapSignInRequest ? BOOTSTRAP_SESSION_REQUESTS.default : this.config.capabilityRequest,
2749
+ includeAccountRegistryPermissions: useBootstrapSignInRequest ? false : this.config.includeAccountRegistryPermissions
2287
2750
  });
2288
2751
  this.tc = new TinyCloud(this.auth, {
2289
2752
  invokeAny: this.invokeAnyWithRuntimePermissions,
@@ -4493,6 +4956,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4493
4956
  delegation,
4494
4957
  targetHost,
4495
4958
  this.wasmBindings.invoke,
4959
+ this.wasmBindings.invokeAny,
4496
4960
  this.config.telemetry
4497
4961
  );
4498
4962
  }
@@ -4558,6 +5022,7 @@ var _TinyCloudNode = class _TinyCloudNode {
4558
5022
  delegation,
4559
5023
  targetHost,
4560
5024
  this.wasmBindings.invoke,
5025
+ this.wasmBindings.invokeAny,
4561
5026
  this.config.telemetry
4562
5027
  );
4563
5028
  }
@@ -4790,6 +5255,7 @@ export {
4790
5255
  SpaceErrorCodes,
4791
5256
  SpaceService2 as SpaceService,
4792
5257
  TinyCloud2 as TinyCloud,
5258
+ TinyCloudDebugLogger,
4793
5259
  TinyCloudNode,
4794
5260
  UnsupportedFeatureError2 as UnsupportedFeatureError,
4795
5261
  VAULT_PERMISSION_SERVICE,
@@ -4805,8 +5271,10 @@ export {
4805
5271
  canonicalizeNetworkId,
4806
5272
  canonicalizeSecretScope,
4807
5273
  checkNodeInfo2 as checkNodeInfo,
5274
+ clearTinyCloudDebugLogs,
4808
5275
  composeManifestRequest2 as composeManifestRequest,
4809
5276
  createCapabilityKeyRegistry,
5277
+ createOpenKeyCallbackSigningStrategy,
4810
5278
  createSharingService,
4811
5279
  createSpaceService,
4812
5280
  createVaultCrypto2 as createVaultCrypto,
@@ -4816,9 +5284,13 @@ export {
4816
5284
  deserializeDelegation,
4817
5285
  didCacheKey,
4818
5286
  didEquals,
5287
+ disableTinyCloudDebug,
5288
+ enableTinyCloudDebug,
4819
5289
  expandActionShortNames,
4820
5290
  expandPermissionEntries2 as expandPermissionEntries,
4821
5291
  expandPermissionEntry,
5292
+ getTinyCloudDebugLogs,
5293
+ installTinyCloudDebugGlobals,
4822
5294
  isCapabilitySubset2 as isCapabilitySubset,
4823
5295
  isEvmAddress,
4824
5296
  loadManifest,
@@ -4836,6 +5308,7 @@ export {
4836
5308
  resolveSecretPath2 as resolveSecretPath,
4837
5309
  resourceCapabilitiesToSpaceAbilitiesMap2 as resourceCapabilitiesToSpaceAbilitiesMap,
4838
5310
  serializeDelegation,
5311
+ tinyCloudDebugLogger,
4839
5312
  validateManifest
4840
5313
  };
4841
5314
  //# sourceMappingURL=core.js.map