@tinycloud/node-sdk 2.11.0 → 3.0.0-beta.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/index.cjs CHANGED
@@ -17092,6 +17092,7 @@ __export(index_exports, {
17092
17092
  VaultPublicSpaceKVActions: () => import_sdk_core16.VaultPublicSpaceKVActions,
17093
17093
  VersionCheckError: () => import_sdk_core22.VersionCheckError,
17094
17094
  WasmKeyProvider: () => WasmKeyProvider,
17095
+ activateCompactRuntimeDelegation: () => activateCompactRuntimeDelegation,
17095
17096
  activateValidatedRuntimeDelegation: () => activateValidatedRuntimeDelegation,
17096
17097
  addressStorageKey: () => import_sdk_core11.addressStorageKey,
17097
17098
  buildCanonicalDecryptRequest: () => import_sdk_core17.buildCanonicalDecryptRequest,
@@ -19206,6 +19207,18 @@ var ROOT_DELEGATION_ACTIONS = [
19206
19207
  import_sdk_core7.DUCKDB.ALL
19207
19208
  ];
19208
19209
  var DEFAULT_SESSION_EXPIRATION_MS = import_sdk_core7.EXPIRY.SESSION_MS;
19210
+ function unifiedRootAttenuation(capabilities) {
19211
+ const attenuation = {};
19212
+ for (const capability of capabilities) {
19213
+ if (attenuation[capability.resource] !== void 0) throw new Error("unified owner root contains a duplicate resource");
19214
+ attenuation[capability.resource] = capability.kind === "encryption" ? { [capability.action]: [{}] } : Object.fromEntries(capability.actions.map((action) => [action, [{
19215
+ type: "xyz.tinycloud.resource/selector",
19216
+ kind: capability.selector,
19217
+ value: capability.resource
19218
+ }]]));
19219
+ }
19220
+ return attenuation;
19221
+ }
19209
19222
  function decodeAuthorizationBytes(authorization) {
19210
19223
  const encoded = authorization.replace(/^Bearer /i, "");
19211
19224
  const match = /^([A-Za-z0-9_-]+)(={1,2})?$/.exec(encoded);
@@ -19585,6 +19598,8 @@ var _TinyCloudNode = class _TinyCloudNode {
19585
19598
  * hosting is confirmed against the session that was active at the time.
19586
19599
  */
19587
19600
  this.confirmedHostedSpaceIds = /* @__PURE__ */ new Set();
19601
+ /** Serializes account-registry writes for the active node instance. */
19602
+ this.accountRegistryTail = Promise.resolve();
19588
19603
  /**
19589
19604
  * True when the last signIn() detected an interactive signer and skipped
19590
19605
  * client-side bootstrap. Apps can read this to know whether bootstrap was
@@ -19981,6 +19996,7 @@ var _TinyCloudNode = class _TinyCloudNode {
19981
19996
  "Cannot signIn() in session-only mode. Provide a privateKey in config to create your own space."
19982
19997
  );
19983
19998
  }
19999
+ await this.accountRegistryTail;
19984
20000
  await this.wasmBindings.ensureInitialized?.();
19985
20001
  this._address = (0, import_sdk_core7.canonicalizeAddress)(await this.signer.getAddress());
19986
20002
  this._chainId = await this.signer.getChainId();
@@ -20462,16 +20478,48 @@ var _TinyCloudNode = class _TinyCloudNode {
20462
20478
  }
20463
20479
  }
20464
20480
  scheduleAccountRegistrySync() {
20465
- void this.withAccountRegistryRetry(async () => {
20466
- void this.account.index.ensure();
20467
- await this.writeManifestRegistryRecords();
20468
- if (this.currentSessionCanListSpaces()) {
20469
- const spaces = await this.account.spaces.syncAccessible();
20470
- if (!spaces.ok) {
20471
- throw new Error(`Failed to sync account spaces: ${spaces.error.message}`);
20481
+ const session = this.currentTinyCloudSession();
20482
+ if (!session || this.pendingAccountRegistrySync?.session === session) {
20483
+ return;
20484
+ }
20485
+ const promise = this.enqueueAccountRegistryOperation(session, async () => {
20486
+ await this.withAccountRegistryRetry(async () => {
20487
+ if (this.currentTinyCloudSession() !== session) {
20488
+ return;
20489
+ }
20490
+ await this.account.index.ensure();
20491
+ if (this.currentTinyCloudSession() !== session) {
20492
+ return;
20493
+ }
20494
+ await this.writeManifestRegistryRecords();
20495
+ if (this.currentTinyCloudSession() !== session) {
20496
+ return;
20472
20497
  }
20498
+ if (this.currentSessionCanListSpaces()) {
20499
+ const spaces = await this.account.spaces.syncAccessible();
20500
+ if (!spaces.ok) {
20501
+ throw new Error(`Failed to sync account spaces: ${spaces.error.message}`);
20502
+ }
20503
+ }
20504
+ });
20505
+ });
20506
+ this.pendingAccountRegistrySync = { session, promise };
20507
+ const clearPendingSync = () => {
20508
+ if (this.pendingAccountRegistrySync?.promise === promise) {
20509
+ this.pendingAccountRegistrySync = void 0;
20510
+ }
20511
+ };
20512
+ void promise.then(clearPendingSync, clearPendingSync);
20513
+ }
20514
+ enqueueAccountRegistryOperation(session, task) {
20515
+ const operation = this.accountRegistryTail.then(async () => {
20516
+ if (this.currentTinyCloudSession() === session) {
20517
+ await task();
20473
20518
  }
20474
20519
  });
20520
+ this.accountRegistryTail = operation.catch(() => {
20521
+ });
20522
+ return operation;
20475
20523
  }
20476
20524
  /**
20477
20525
  * Whether the current primary session may invoke `tinycloud.space/list`.
@@ -20624,7 +20672,8 @@ var _TinyCloudNode = class _TinyCloudNode {
20624
20672
  * @returns The hosted space URI.
20625
20673
  */
20626
20674
  async hostOwnedSpace(name) {
20627
- if (!this.auth || !this.auth.tinyCloudSession) {
20675
+ const session = this.auth?.tinyCloudSession;
20676
+ if (!this.auth || !session) {
20628
20677
  throw new Error("Not signed in. Call signIn() first.");
20629
20678
  }
20630
20679
  const spaceId = this.ownedSpaceId(name);
@@ -20640,22 +20689,26 @@ var _TinyCloudNode = class _TinyCloudNode {
20640
20689
  }
20641
20690
  const activation = await (0, import_sdk_core7.activateSessionWithHost)(
20642
20691
  host,
20643
- this.auth.tinyCloudSession.delegationHeader
20692
+ session.delegationHeader
20644
20693
  );
20645
20694
  if (!activation.success || activation.skipped?.includes(spaceId)) {
20646
20695
  throw new Error(
20647
20696
  `Failed to activate session for owned space ${spaceId}: ${activation.error ?? "space was skipped"}`
20648
20697
  );
20649
20698
  }
20650
- void this.account.spaces.register({
20651
- spaceId,
20652
- name,
20653
- ownerDid: this.did,
20654
- type: "owned",
20655
- permissions: ["*"],
20656
- status: "active"
20657
- }).catch(() => {
20658
- });
20699
+ try {
20700
+ await this.enqueueAccountRegistryOperation(session, async () => {
20701
+ await this.account.spaces.register({
20702
+ spaceId,
20703
+ name,
20704
+ ownerDid: this.did,
20705
+ type: "owned",
20706
+ permissions: ["*"],
20707
+ status: "active"
20708
+ });
20709
+ });
20710
+ } catch {
20711
+ }
20659
20712
  return spaceId;
20660
20713
  }
20661
20714
  /**
@@ -20690,23 +20743,12 @@ var _TinyCloudNode = class _TinyCloudNode {
20690
20743
  if (!this.auth || !this.auth.tinyCloudSession) {
20691
20744
  throw new Error("Not signed in. Call signIn() first.");
20692
20745
  }
20746
+ await this.accountRegistryTail;
20693
20747
  const spaceId = this.ownedSpaceId(name);
20694
20748
  if (await this.isOwnedSpaceRegistered(spaceId)) {
20695
20749
  return spaceId;
20696
20750
  }
20697
- const hosted = await this.hostOwnedSpace(name);
20698
- try {
20699
- await this.account.spaces.register({
20700
- spaceId,
20701
- name,
20702
- ownerDid: this.did,
20703
- type: "owned",
20704
- permissions: ["*"],
20705
- status: "active"
20706
- });
20707
- } catch {
20708
- }
20709
- return hosted;
20751
+ return this.hostOwnedSpace(name);
20710
20752
  }
20711
20753
  /** Resolve and authenticate the owner encoded by an owned-space URI. */
20712
20754
  credentialSpaceOwnerDid(spaceId) {
@@ -21259,6 +21301,46 @@ var _TinyCloudNode = class _TinyCloudNode {
21259
21301
  if (session === void 0) throw new Error("Not signed in. Call signIn() first.");
21260
21302
  return signBytesWithJwk(bytes, session.jwk);
21261
21303
  }
21304
+ /** Author a proofless Policy/v3 owner root with the authenticated holder key. */
21305
+ async createUnifiedOwnerRoot(input) {
21306
+ const ownerDid = this.credentialHolderDid;
21307
+ if (input.ownerDid !== ownerDid) throw new Error("unified owner root signer does not match owner DID");
21308
+ const facts = {
21309
+ role: input.role,
21310
+ mode: input.role === "policy-authority" ? "policy-source" : "conditional-mint",
21311
+ ownerDid,
21312
+ policyId: input.policyId,
21313
+ policyDigestHex: input.policyDigestHex,
21314
+ policyCid: input.policyCid,
21315
+ contentSourceDigestHex: input.contentSourceDigestHex,
21316
+ capabilityCeilingHashHex: input.capabilityCeilingHashHex,
21317
+ nativeProjectionHashHex: input.nativeProjectionHashHex,
21318
+ nodeAudience: input.nodeAudience,
21319
+ ...input.role === "policy-enforcement" ? { enforcerDid: input.audienceDid } : {}
21320
+ };
21321
+ const root = await (0, import_sdk_core7.signCompactUcanRootAuthorization)({
21322
+ issuerDid: ownerDid,
21323
+ audienceDid: input.audienceDid,
21324
+ attenuation: unifiedRootAttenuation(input.capabilities),
21325
+ facts: [facts],
21326
+ notBefore: Math.floor(input.notBefore.getTime() / 1e3),
21327
+ expiresAt: Math.floor(input.expiresAt.getTime() / 1e3),
21328
+ nonce: base64UrlEncode(crypto.getRandomValues(new Uint8Array(16))),
21329
+ sign: (bytes) => this.signSessionBytes(bytes)
21330
+ });
21331
+ const kv = input.capabilities.find((capability) => capability.kind === "kv");
21332
+ const resource = kv?.resource ?? "";
21333
+ const marker = resource.indexOf("/kv/");
21334
+ return {
21335
+ cid: root.cid,
21336
+ delegationHeader: { Authorization: root.authorization },
21337
+ delegateDID: input.audienceDid,
21338
+ spaceId: marker === -1 ? "" : resource.slice("tinycloud://".length, marker),
21339
+ path: marker === -1 ? resource : resource.slice(marker + 4),
21340
+ actions: kv?.actions ?? [],
21341
+ expiry: input.expiresAt
21342
+ };
21343
+ }
21262
21344
  /** Apply only an already-enabled signing policy to the exact credential request. */
21263
21345
  async autoSignCredentialBytes(bytes) {
21264
21346
  const strategy = this.config.signStrategy;
@@ -24497,7 +24579,7 @@ function compactUcanPayload(authorization) {
24497
24579
  }
24498
24580
  try {
24499
24581
  const payload = JSON.parse(
24500
- Buffer.from(parts[1], "base64url").toString("utf8")
24582
+ new TextDecoder("utf-8", { fatal: true }).decode((0, import_sdk_core9.decodeBase64Url)(parts[1]))
24501
24583
  );
24502
24584
  if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
24503
24585
  throw new Error("payload is not an object");
@@ -24589,7 +24671,9 @@ function signedAuthorityFromCompactUcan(authorization) {
24589
24671
  ));
24590
24672
  continue;
24591
24673
  }
24592
- const match = /^(tinycloud:[^/]+)\/(kv|sql|duckdb|hooks)\/(.*)$/.exec(resource);
24674
+ const nativeMatch = /^(tinycloud:[^/]+)\/(kv|sql|duckdb|hooks)\/(.*)$/.exec(resource);
24675
+ const policyMatch = /^tinycloud:\/\/([^/]+)\/(kv|sql|duckdb|hooks)\/(.*)$/.exec(resource);
24676
+ const match = nativeMatch ?? policyMatch;
24593
24677
  if (!match) {
24594
24678
  throw new Error(
24595
24679
  `Validated runtime delegation has an unsupported signed resource '${resource}'.`
@@ -24761,6 +24845,40 @@ async function activateValidatedRuntimeDelegation(node, delegation, options) {
24761
24845
  host: normalizedHost(installed.host ?? host)
24762
24846
  };
24763
24847
  }
24848
+ async function activateCompactRuntimeDelegation(node, input) {
24849
+ const authorization = input.authorization;
24850
+ const cid = node.computeDelegationCid(authorization);
24851
+ if (cid !== input.cid) {
24852
+ throw new Error("Compact runtime delegation CID does not match authorization bytes.");
24853
+ }
24854
+ const signed2 = signedAuthorityFromCompactUcan(authorization);
24855
+ const primary = signed2.resources[0];
24856
+ const signedSpace = signed2.permissions.find(
24857
+ (permission) => permission.service !== "tinycloud.encryption"
24858
+ )?.space;
24859
+ const delegation = {
24860
+ cid,
24861
+ delegationHeader: { Authorization: authorization },
24862
+ ownerAddress: input.ownerAddress,
24863
+ chainId: input.chainId,
24864
+ delegatorDID: signed2.issuer,
24865
+ authHeader: authorization,
24866
+ spaceId: signedSpace ?? primary.space,
24867
+ path: primary.path,
24868
+ actions: [...primary.actions],
24869
+ ...primary.caveats === void 0 ? {} : { caveats: primary.caveats },
24870
+ resources: signed2.resources.map((resource) => ({
24871
+ ...resource,
24872
+ actions: [...resource.actions]
24873
+ })),
24874
+ expiry: new Date(signed2.expiry),
24875
+ delegateDID: signed2.audience,
24876
+ host: normalizedHost(input.host)
24877
+ };
24878
+ return activateValidatedRuntimeDelegation(node, delegation, {
24879
+ host: input.host
24880
+ });
24881
+ }
24764
24882
  async function grantAuthRequest(authority, request, options) {
24765
24883
  if (request.kind !== "tinycloud.auth.request") {
24766
24884
  throw new Error(
@@ -24883,6 +25001,7 @@ var import_sdk_core23 = require("@tinycloud/sdk-core");
24883
25001
  VaultPublicSpaceKVActions,
24884
25002
  VersionCheckError,
24885
25003
  WasmKeyProvider,
25004
+ activateCompactRuntimeDelegation,
24886
25005
  activateValidatedRuntimeDelegation,
24887
25006
  addressStorageKey,
24888
25007
  buildCanonicalDecryptRequest,