@tinycloud/node-sdk 3.2.0-beta.3 → 3.2.0-beta.5

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.
@@ -329,6 +329,7 @@ declare class NodeUserAuthorization implements IUserAuthorization {
329
329
  private _address?;
330
330
  private _chainId?;
331
331
  private _nodeFeatures;
332
+ private _nodeFeaturesKnown;
332
333
  /**
333
334
  * `nodeId` from the `GET /info` performed during sign-in/restore, tagged with
334
335
  * the host it came from. Callers that need the node DID (encryption-network
@@ -383,7 +384,7 @@ declare class NodeUserAuthorization implements IUserAuthorization {
383
384
  */
384
385
  setRestoredTinyCloudSession(session: TinyCloudSession, hosts?: string[]): void;
385
386
  /** @internal Atomically adopt the state staged by TinyCloudNode.restoreSession. */
386
- installRestoredSession(sessionManager: ISessionManager, session: TinyCloudSession | undefined, hosts?: string[]): void;
387
+ installRestoredSession(sessionManager: ISessionManager, session: TinyCloudSession | undefined, hosts?: string[], nodeFeatures?: string[]): void;
387
388
  /**
388
389
  * Ensure `tinycloudHosts` are resolved before a host-needing call.
389
390
  *
@@ -403,6 +404,8 @@ declare class NodeUserAuthorization implements IUserAuthorization {
403
404
  private requireTinyCloudHosts;
404
405
  private get primaryTinyCloudHost();
405
406
  get nodeFeatures(): string[];
407
+ get nodeFeaturesKnown(): boolean;
408
+ refreshNodeInfo(): Promise<void>;
406
409
  /**
407
410
  * The node DID reported by `GET /info` for `host`, if the sign-in (or
408
411
  * restore) that populated it targeted that same host. Returns `undefined`
@@ -1474,6 +1477,7 @@ declare class TinyCloudNode {
1474
1477
  * {@link currentTinyCloudSession} as a fallback for `auth.tinyCloudSession`.
1475
1478
  */
1476
1479
  private _restoredTcSession?;
1480
+ private _restoredNodeFeatures?;
1477
1481
  private _activeServiceSession?;
1478
1482
  /**
1479
1483
  * True when the last signIn() detected an interactive signer and skipped
@@ -1504,6 +1508,7 @@ declare class TinyCloudNode {
1504
1508
  warnings?: BootstrapWarning[];
1505
1509
  };
1506
1510
  private get nodeFeatures();
1511
+ private get replicationNodeFeatures();
1507
1512
  /** SIWE domain — uses config override or defaults to app.tinycloud.xyz */
1508
1513
  private get siweDomain();
1509
1514
  private readonly invokeWithRuntimePermissions;
@@ -329,6 +329,7 @@ declare class NodeUserAuthorization implements IUserAuthorization {
329
329
  private _address?;
330
330
  private _chainId?;
331
331
  private _nodeFeatures;
332
+ private _nodeFeaturesKnown;
332
333
  /**
333
334
  * `nodeId` from the `GET /info` performed during sign-in/restore, tagged with
334
335
  * the host it came from. Callers that need the node DID (encryption-network
@@ -383,7 +384,7 @@ declare class NodeUserAuthorization implements IUserAuthorization {
383
384
  */
384
385
  setRestoredTinyCloudSession(session: TinyCloudSession, hosts?: string[]): void;
385
386
  /** @internal Atomically adopt the state staged by TinyCloudNode.restoreSession. */
386
- installRestoredSession(sessionManager: ISessionManager, session: TinyCloudSession | undefined, hosts?: string[]): void;
387
+ installRestoredSession(sessionManager: ISessionManager, session: TinyCloudSession | undefined, hosts?: string[], nodeFeatures?: string[]): void;
387
388
  /**
388
389
  * Ensure `tinycloudHosts` are resolved before a host-needing call.
389
390
  *
@@ -403,6 +404,8 @@ declare class NodeUserAuthorization implements IUserAuthorization {
403
404
  private requireTinyCloudHosts;
404
405
  private get primaryTinyCloudHost();
405
406
  get nodeFeatures(): string[];
407
+ get nodeFeaturesKnown(): boolean;
408
+ refreshNodeInfo(): Promise<void>;
406
409
  /**
407
410
  * The node DID reported by `GET /info` for `host`, if the sign-in (or
408
411
  * restore) that populated it targeted that same host. Returns `undefined`
@@ -1474,6 +1477,7 @@ declare class TinyCloudNode {
1474
1477
  * {@link currentTinyCloudSession} as a fallback for `auth.tinyCloudSession`.
1475
1478
  */
1476
1479
  private _restoredTcSession?;
1480
+ private _restoredNodeFeatures?;
1477
1481
  private _activeServiceSession?;
1478
1482
  /**
1479
1483
  * True when the last signIn() detected an interactive signer and skipped
@@ -1504,6 +1508,7 @@ declare class TinyCloudNode {
1504
1508
  warnings?: BootstrapWarning[];
1505
1509
  };
1506
1510
  private get nodeFeatures();
1511
+ private get replicationNodeFeatures();
1507
1512
  /** SIWE domain — uses config override or defaults to app.tinycloud.xyz */
1508
1513
  private get siweDomain();
1509
1514
  private readonly invokeWithRuntimePermissions;
@@ -1,5 +1,5 @@
1
- import { PermissionEntry, ReplicationOptions as ReplicationOptions$1, TinyCloudSession } from '@tinycloud/sdk-core';
2
- import { ReplicationOptions, AuthorityRefusal, ReplicationAuthority } from '@tinycloud/sdk-services';
1
+ import { ReplicationOptions, PermissionEntry, TinyCloudSession } from '@tinycloud/sdk-core';
2
+ import { AuthorityRefusal, ReplicationAuthority } from '@tinycloud/sdk-services';
3
3
 
4
4
  /** Constructor-time validation; the space-dependent secrets check runs at sign-in and open. */
5
5
  declare function assertValidReplicationConfig(replication: (ReplicationOptions & {
@@ -10,7 +10,7 @@ declare function hasUnrestrictedGetCoverage(entries: readonly PermissionEntry[],
10
10
  declare function augmentSignInEntriesWithReplication(input: {
11
11
  entries: readonly PermissionEntry[];
12
12
  primarySpaceId: string;
13
- replication?: Pick<ReplicationOptions$1, "prefixes" | "allowSecrets">;
13
+ replication?: Pick<ReplicationOptions, "prefixes" | "allowSecrets">;
14
14
  }): PermissionEntry[];
15
15
 
16
16
  /**
@@ -29,9 +29,8 @@ declare function augmentSignInEntriesWithReplication(input: {
29
29
  */
30
30
 
31
31
  /**
32
- * Whether the signed `att` grants `ability` over the selected namespace, with
33
- * each grant unrestricted (no caveats). A bare selector requires both its
34
- * exact key and its slash descendants; a trailing-slash selector is one range.
32
+ * Whether the signed `att` grants `ability` over the folder prefix, with
33
+ * unrestricted authority unless explicitly asked to ignore caveats.
35
34
  */
36
35
  declare function ucanAttCovers(att: Record<string, Record<string, unknown>>, space: string, prefix: string, ability: string, options?: {
37
36
  ignoreCaveats?: boolean;
@@ -1,5 +1,5 @@
1
- import { PermissionEntry, ReplicationOptions as ReplicationOptions$1, TinyCloudSession } from '@tinycloud/sdk-core';
2
- import { ReplicationOptions, AuthorityRefusal, ReplicationAuthority } from '@tinycloud/sdk-services';
1
+ import { ReplicationOptions, PermissionEntry, TinyCloudSession } from '@tinycloud/sdk-core';
2
+ import { AuthorityRefusal, ReplicationAuthority } from '@tinycloud/sdk-services';
3
3
 
4
4
  /** Constructor-time validation; the space-dependent secrets check runs at sign-in and open. */
5
5
  declare function assertValidReplicationConfig(replication: (ReplicationOptions & {
@@ -10,7 +10,7 @@ declare function hasUnrestrictedGetCoverage(entries: readonly PermissionEntry[],
10
10
  declare function augmentSignInEntriesWithReplication(input: {
11
11
  entries: readonly PermissionEntry[];
12
12
  primarySpaceId: string;
13
- replication?: Pick<ReplicationOptions$1, "prefixes" | "allowSecrets">;
13
+ replication?: Pick<ReplicationOptions, "prefixes" | "allowSecrets">;
14
14
  }): PermissionEntry[];
15
15
 
16
16
  /**
@@ -29,9 +29,8 @@ declare function augmentSignInEntriesWithReplication(input: {
29
29
  */
30
30
 
31
31
  /**
32
- * Whether the signed `att` grants `ability` over the selected namespace, with
33
- * each grant unrestricted (no caveats). A bare selector requires both its
34
- * exact key and its slash descendants; a trailing-slash selector is one range.
32
+ * Whether the signed `att` grants `ability` over the folder prefix, with
33
+ * unrestricted authority unless explicitly asked to ignore caveats.
35
34
  */
36
35
  declare function ucanAttCovers(att: Record<string, Record<string, unknown>>, space: string, prefix: string, ability: string, options?: {
37
36
  ignoreCaveats?: boolean;
package/dist/core.cjs CHANGED
@@ -347,13 +347,13 @@ var import_sdk_core4 = require("@tinycloud/sdk-core");
347
347
  // src/replication/authority-sign-in.ts
348
348
  var import_sdk_core2 = require("@tinycloud/sdk-core");
349
349
  var import_sdk_services = require("@tinycloud/sdk-services");
350
+ var import_prefix = require("@tinycloud/sdk-services/kv/replication/prefix");
350
351
  var KV_GET = import_sdk_core2.KV.GET;
351
352
  var KV_SYNC = import_sdk_core2.KV.SYNC;
352
353
  var KV_SERVICE = "tinycloud.kv";
353
354
  function hasUnrestrictedGetCoverage(entries, spaceId, prefix) {
354
- const requiredPaths = (0, import_sdk_core2.grantPathsForSelector)(prefix);
355
355
  return (0, import_sdk_core2.isCapabilitySubset)(
356
- requiredPaths.map((path) => ({ service: KV_SERVICE, space: spaceId, path, actions: [KV_GET] })),
356
+ [{ service: KV_SERVICE, space: spaceId, path: prefix, actions: [KV_GET] }],
357
357
  [...entries]
358
358
  ).subset;
359
359
  }
@@ -361,21 +361,33 @@ function augmentSignInEntriesWithReplication(input) {
361
361
  const out = [];
362
362
  const replication = input.replication;
363
363
  if (replication === void 0) return out;
364
+ const eligiblePaths = [];
364
365
  for (const prefix of replication.prefixes) {
366
+ (0, import_prefix.assertFolderPrefix)(prefix);
367
+ const path = prefix === "/" ? "" : prefix;
365
368
  if ((0, import_sdk_services.requiresSecretsOptIn)(input.primarySpaceId, prefix) && replication.allowSecrets !== true) {
366
369
  continue;
367
370
  }
368
- if (!hasUnrestrictedGetCoverage(input.entries, input.primarySpaceId, prefix)) {
371
+ if (!hasUnrestrictedGetCoverage(input.entries, input.primarySpaceId, path)) {
369
372
  continue;
370
373
  }
371
- for (const path of (0, import_sdk_core2.grantPathsForSelector)(prefix)) {
372
- out.push({
373
- service: KV_SERVICE,
374
- space: input.primarySpaceId,
375
- path,
376
- actions: [KV_GET, KV_SYNC]
377
- });
378
- }
374
+ eligiblePaths.push(path);
375
+ }
376
+ if (eligiblePaths.length === replication.prefixes.length && eligiblePaths.length > 0 && hasUnrestrictedGetCoverage(input.entries, input.primarySpaceId, "")) {
377
+ return [{
378
+ service: KV_SERVICE,
379
+ space: input.primarySpaceId,
380
+ path: "",
381
+ actions: [KV_GET, KV_SYNC]
382
+ }];
383
+ }
384
+ for (const path of eligiblePaths) {
385
+ out.push({
386
+ service: KV_SERVICE,
387
+ space: input.primarySpaceId,
388
+ path,
389
+ actions: [KV_GET, KV_SYNC]
390
+ });
379
391
  }
380
392
  return out;
381
393
  }
@@ -444,6 +456,7 @@ var NodeUserAuthorization = class {
444
456
  this.activeSessionKeyId = "default";
445
457
  this.extensions = [];
446
458
  this._nodeFeatures = [];
459
+ this._nodeFeaturesKnown = false;
447
460
  this._lastActivationSkippedSpaceIds = [];
448
461
  this.wasm = config.wasmBindings;
449
462
  this.signer = config.signer;
@@ -567,8 +580,11 @@ var NodeUserAuthorization = class {
567
580
  }
568
581
  }
569
582
  /** @internal Atomically adopt the state staged by TinyCloudNode.restoreSession. */
570
- installRestoredSession(sessionManager, session, hosts) {
583
+ installRestoredSession(sessionManager, session, hosts, nodeFeatures) {
571
584
  this.sessionManager = sessionManager;
585
+ this._nodeFeatures = nodeFeatures === void 0 ? [] : [...nodeFeatures];
586
+ this._nodeFeaturesKnown = nodeFeatures !== void 0;
587
+ this._nodeInfoIdentity = void 0;
572
588
  this.tinycloudHosts = hosts && hosts.length > 0 ? [...hosts] : void 0;
573
589
  this._lastActivationSkippedSpaceIds = [];
574
590
  if (!session) {
@@ -643,6 +659,16 @@ var NodeUserAuthorization = class {
643
659
  get nodeFeatures() {
644
660
  return this._nodeFeatures;
645
661
  }
662
+ get nodeFeaturesKnown() {
663
+ return this._nodeFeaturesKnown;
664
+ }
665
+ async refreshNodeInfo() {
666
+ const infoHost = this.primaryTinyCloudHost;
667
+ const nodeInfo = await (0, import_sdk_core4.checkNodeInfo)(infoHost, this.wasm.protocolVersion());
668
+ this._nodeFeatures = nodeInfo.features;
669
+ this._nodeFeaturesKnown = true;
670
+ this._nodeInfoIdentity = nodeInfo.nodeId ? { host: infoHost, nodeId: nodeInfo.nodeId } : void 0;
671
+ }
646
672
  /**
647
673
  * The node DID reported by `GET /info` for `host`, if the sign-in (or
648
674
  * restore) that populated it targeted that same host. Returns `undefined`
@@ -1306,13 +1332,7 @@ var NodeUserAuthorization = class {
1306
1332
  this._tinyCloudSession = tinyCloudSession;
1307
1333
  this._address = address;
1308
1334
  this._chainId = chainId;
1309
- const infoHost = this.primaryTinyCloudHost;
1310
- const nodeInfo = await (0, import_sdk_core4.checkNodeInfo)(
1311
- infoHost,
1312
- this.wasm.protocolVersion()
1313
- );
1314
- this._nodeFeatures = nodeInfo.features;
1315
- this._nodeInfoIdentity = nodeInfo.nodeId ? { host: infoHost, nodeId: nodeInfo.nodeId } : void 0;
1335
+ await this.refreshNodeInfo();
1316
1336
  for (const ext of this.extensions) {
1317
1337
  if (ext.afterSignIn) {
1318
1338
  await ext.afterSignIn(clientSession);
@@ -1478,13 +1498,7 @@ var NodeUserAuthorization = class {
1478
1498
  this._tinyCloudSession = tinyCloudSession;
1479
1499
  this._address = address;
1480
1500
  this._chainId = chainId;
1481
- const infoHost = this.primaryTinyCloudHost;
1482
- const nodeInfo = await (0, import_sdk_core4.checkNodeInfo)(
1483
- infoHost,
1484
- this.wasm.protocolVersion()
1485
- );
1486
- this._nodeFeatures = nodeInfo.features;
1487
- this._nodeInfoIdentity = nodeInfo.nodeId ? { host: infoHost, nodeId: nodeInfo.nodeId } : void 0;
1501
+ await this.refreshNodeInfo();
1488
1502
  for (const ext of this.extensions) {
1489
1503
  if (ext.afterSignIn) {
1490
1504
  await ext.afterSignIn(clientSession);
@@ -2437,7 +2451,7 @@ function deserializeDelegation(data) {
2437
2451
  }
2438
2452
 
2439
2453
  // src/replication/config.ts
2440
- var import_sdk_services2 = require("@tinycloud/sdk-services");
2454
+ var import_prefix2 = require("@tinycloud/sdk-services/kv/replication/prefix");
2441
2455
  function assertValidReplicationConfig(replication) {
2442
2456
  if (replication === void 0 || replication.enabled !== true) return;
2443
2457
  if (replication.storage === void 0 || replication.storage === null) {
@@ -2447,19 +2461,21 @@ function assertValidReplicationConfig(replication) {
2447
2461
  throw new TypeError("replication.prefixes must be a non-empty array");
2448
2462
  }
2449
2463
  for (const prefix of replication.prefixes) {
2450
- if (typeof prefix !== "string" || prefix === "") {
2451
- throw new TypeError("replication.prefixes must not contain an empty prefix");
2464
+ try {
2465
+ (0, import_prefix2.assertFolderPrefix)(prefix);
2466
+ } catch (error) {
2467
+ throw new TypeError(error instanceof Error ? error.message : String(error));
2452
2468
  }
2453
2469
  }
2454
2470
  for (const [index, prefix] of replication.prefixes.entries()) {
2455
2471
  for (const other of replication.prefixes.slice(index + 1)) {
2456
- if ((0, import_sdk_services2.kvPrefixCovers)(prefix, other) || (0, import_sdk_services2.kvPrefixCovers)(other, prefix)) {
2472
+ if ((0, import_prefix2.kvPrefixCovers)(prefix, other) || (0, import_prefix2.kvPrefixCovers)(other, prefix)) {
2457
2473
  throw new TypeError(
2458
2474
  `replication.prefixes must not overlap: ${JSON.stringify(prefix)} and ${JSON.stringify(other)}`
2459
2475
  );
2460
2476
  }
2461
2477
  }
2462
- if (prefix.split("/", 1)[0] === "vault" && replication.allowSecrets !== true) {
2478
+ if ((prefix === "/" || prefix.split("/", 1)[0] === "vault") && replication.allowSecrets !== true) {
2463
2479
  throw new TypeError(
2464
2480
  `replication prefix ${JSON.stringify(prefix)} overlaps the vault namespace; pass allowSecrets to opt in`
2465
2481
  );
@@ -2469,7 +2485,7 @@ function assertValidReplicationConfig(replication) {
2469
2485
 
2470
2486
  // src/TinyCloudNode.ts
2471
2487
  var import_sdk_core10 = require("@tinycloud/sdk-core");
2472
- var import_sdk_services3 = require("@tinycloud/sdk-services");
2488
+ var import_sdk_services2 = require("@tinycloud/sdk-services");
2473
2489
 
2474
2490
  // src/account/AccountService.ts
2475
2491
  var import_sdk_core6 = require("@tinycloud/sdk-core");
@@ -3567,7 +3583,11 @@ var _TinyCloudNode = class _TinyCloudNode {
3567
3583
  return this._bootstrapStatus;
3568
3584
  }
3569
3585
  get nodeFeatures() {
3570
- return this.auth?.nodeFeatures ?? [];
3586
+ return this.auth?.nodeFeatures ?? this._restoredNodeFeatures ?? [];
3587
+ }
3588
+ get replicationNodeFeatures() {
3589
+ if (this.auth) return this.auth.nodeFeaturesKnown ? this.auth.nodeFeatures : void 0;
3590
+ return this._restoredNodeFeatures;
3571
3591
  }
3572
3592
  /** SIWE domain — uses config override or defaults to app.tinycloud.xyz */
3573
3593
  get siweDomain() {
@@ -4920,6 +4940,14 @@ var _TinyCloudNode = class _TinyCloudNode {
4920
4940
  sessionData.chainId
4921
4941
  );
4922
4942
  const stagedHost = resolvedHost ?? this.config.host;
4943
+ let restoredNodeFeatures;
4944
+ if (this.config.replication?.enabled) {
4945
+ try {
4946
+ restoredNodeFeatures = (await (0, import_sdk_core10.checkNodeInfo)(stagedHost, this.wasmBindings.protocolVersion())).features;
4947
+ } catch (error) {
4948
+ if (!(error instanceof import_sdk_core10.VersionCheckError)) throw error;
4949
+ }
4950
+ }
4923
4951
  const stagedAddress = restoredAddress;
4924
4952
  const stagedChainId = sessionData.chainId ?? 1;
4925
4953
  let replicationPrincipal;
@@ -5022,8 +5050,9 @@ var _TinyCloudNode = class _TinyCloudNode {
5022
5050
  this._recapOperationsCache = stagedPrimary.cache;
5023
5051
  this.runtimePermissionGrants = stagedPrimary.grants;
5024
5052
  this.confirmedHostedSpaceIds.clear();
5053
+ this._restoredNodeFeatures = restoredNodeFeatures;
5025
5054
  if (this.auth) {
5026
- this.auth.installRestoredSession(stagedManager, stagedTcSession, [stagedHost]);
5055
+ this.auth.installRestoredSession(stagedManager, stagedTcSession, [stagedHost], restoredNodeFeatures);
5027
5056
  this._restoredTcSession = void 0;
5028
5057
  } else {
5029
5058
  this._restoredTcSession = stagedTcSession;
@@ -5853,7 +5882,7 @@ var _TinyCloudNode = class _TinyCloudNode {
5853
5882
  );
5854
5883
  graph.assertActive();
5855
5884
  if (!response.ok) {
5856
- const permissionHint = response.status === 401 || response.status === 403 ? (0, import_sdk_services3.parsePermissionHint)({
5885
+ const permissionHint = response.status === 401 || response.status === 403 ? (0, import_sdk_services2.parsePermissionHint)({
5857
5886
  service: "tinycloud.encryption",
5858
5887
  path: networkId,
5859
5888
  actions: ["tinycloud.encryption/decrypt"]
@@ -6621,7 +6650,7 @@ var _TinyCloudNode = class _TinyCloudNode {
6621
6650
  );
6622
6651
  if (result.status !== "ok") {
6623
6652
  if (result.status === "permission_required") {
6624
- const hint = (0, import_sdk_services3.parsePermissionHint)(result.hint);
6653
+ const hint = (0, import_sdk_services2.parsePermissionHint)(result.hint);
6625
6654
  if (hint === void 0) return { status: "read_failed" };
6626
6655
  return {
6627
6656
  status: "permission_required",
@@ -7597,6 +7626,7 @@ var _TinyCloudNode = class _TinyCloudNode {
7597
7626
  session,
7598
7627
  principal: identityPrincipal,
7599
7628
  authority,
7629
+ nodeFeatures: this.replicationNodeFeatures,
7600
7630
  primaryKV: [{ space: session.spaceId, kv }]
7601
7631
  });
7602
7632
  }