@tinycloud/sdk-core 2.2.0-beta.1 → 2.2.0-beta.10

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.js CHANGED
@@ -154,6 +154,7 @@ var Space = class {
154
154
  this._id = config.id;
155
155
  this._name = config.name;
156
156
  this._kv = config.createKV(config.id);
157
+ this._vault = config.createVault(config.id);
157
158
  this._delegations = config.createDelegations(config.id);
158
159
  this._sharing = config.createSharing(config.id);
159
160
  this._getInfo = config.getInfo;
@@ -176,6 +177,12 @@ var Space = class {
176
177
  get kv() {
177
178
  return this._kv;
178
179
  }
180
+ /**
181
+ * Data Vault operations scoped to this space.
182
+ */
183
+ get vault() {
184
+ return this._vault;
185
+ }
179
186
  /**
180
187
  * Delegation operations scoped to this space.
181
188
  */
@@ -559,6 +566,8 @@ var SpaceConfigSchema = z4.object({
559
566
  name: z4.string(),
560
567
  /** Factory function to create a space-scoped KV service */
561
568
  createKV: z4.function(),
569
+ /** Factory function to create a space-scoped Data Vault service */
570
+ createVault: z4.function(),
562
571
  /** Factory function to create space-scoped delegations */
563
572
  createDelegations: z4.function(),
564
573
  /** Factory function to create space-scoped sharing */
@@ -579,6 +588,8 @@ var SpaceServiceConfigSchema = z4.object({
579
588
  capabilityRegistry: z4.unknown().optional(),
580
589
  /** Factory function to create a space-scoped KV service */
581
590
  createKVService: z4.function().optional(),
591
+ /** Factory function to create a space-scoped Data Vault service */
592
+ createVaultService: z4.function().optional(),
582
593
  /** User's PKH DID (derived from address or provided explicitly) */
583
594
  userDid: z4.string().optional(),
584
595
  /** Optional SharingService for v2 sharing links (client-side) */
@@ -820,6 +831,7 @@ var SpaceService = class {
820
831
  this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
821
832
  this.capabilityRegistry = config.capabilityRegistry;
822
833
  this.createKVServiceFn = config.createKVService;
834
+ this.createVaultServiceFn = config.createVaultService;
823
835
  this._userDid = config.userDid;
824
836
  this.sharingService = config.sharingService;
825
837
  this.createDelegationFn = config.createDelegation;
@@ -834,6 +846,7 @@ var SpaceService = class {
834
846
  if (config.fetch) this.fetchFn = config.fetch;
835
847
  if (config.capabilityRegistry) this.capabilityRegistry = config.capabilityRegistry;
836
848
  if (config.createKVService) this.createKVServiceFn = config.createKVService;
849
+ if (config.createVaultService) this.createVaultServiceFn = config.createVaultService;
837
850
  if (config.userDid !== void 0) this._userDid = config.userDid;
838
851
  if (config.sharingService) this.sharingService = config.sharingService;
839
852
  if (config.createDelegation) this.createDelegationFn = config.createDelegation;
@@ -1116,6 +1129,7 @@ var SpaceService = class {
1116
1129
  id: spaceId,
1117
1130
  name,
1118
1131
  createKV: this.createSpaceScopedKV.bind(this),
1132
+ createVault: this.createSpaceScopedVault.bind(this),
1119
1133
  createDelegations: this.createSpaceScopedDelegations.bind(this),
1120
1134
  createSharing: this.createSpaceScopedSharing.bind(this),
1121
1135
  getInfo: this.getSpaceInfo.bind(this)
@@ -1245,6 +1259,21 @@ var SpaceService = class {
1245
1259
  }
1246
1260
  });
1247
1261
  }
1262
+ /**
1263
+ * Create a space-scoped Data Vault service.
1264
+ */
1265
+ createSpaceScopedVault(spaceId) {
1266
+ if (this.createVaultServiceFn) {
1267
+ return this.createVaultServiceFn(spaceId);
1268
+ }
1269
+ return new Proxy({}, {
1270
+ get: () => {
1271
+ throw new Error(
1272
+ "Vault service factory not configured. Provide createVaultService in SpaceServiceConfig."
1273
+ );
1274
+ }
1275
+ });
1276
+ }
1248
1277
  /**
1249
1278
  * Create space-scoped delegation operations.
1250
1279
  */
@@ -1989,7 +2018,11 @@ import {
1989
2018
  DataVaultService,
1990
2019
  VaultHeaders,
1991
2020
  VaultPublicSpaceKVActions,
1992
- createVaultCrypto
2021
+ createVaultCrypto,
2022
+ SecretsService,
2023
+ SECRET_NAME_RE as SECRET_NAME_RE2,
2024
+ canonicalizeSecretScope,
2025
+ resolveSecretPath as resolveSecretPath2
1993
2026
  } from "@tinycloud/sdk-services";
1994
2027
 
1995
2028
  // src/space.ts
@@ -2658,6 +2691,7 @@ function validateEncodedShareData(data) {
2658
2691
 
2659
2692
  // src/manifest.ts
2660
2693
  import ms from "ms";
2694
+ import { resolveSecretPath, SECRET_NAME_RE } from "@tinycloud/sdk-services";
2661
2695
  var ManifestValidationError = class extends Error {
2662
2696
  constructor(message) {
2663
2697
  super(`Manifest validation failed: ${message}`);
@@ -2670,6 +2704,8 @@ var DEFAULT_MANIFEST_VERSION = 1;
2670
2704
  var DEFAULT_MANIFEST_SPACE = "applications";
2671
2705
  var ACCOUNT_REGISTRY_SPACE = "account";
2672
2706
  var ACCOUNT_REGISTRY_PATH = "applications/";
2707
+ var SECRETS_SPACE = "secrets";
2708
+ var VAULT_PERMISSION_SERVICE = "tinycloud.vault";
2673
2709
  var SERVICE_SHORT_TO_LONG = Object.freeze({
2674
2710
  kv: "tinycloud.kv",
2675
2711
  sql: "tinycloud.sql",
@@ -2694,12 +2730,6 @@ var DEFAULT_STANDARD_ENTRIES = [
2694
2730
  space: DEFAULT_MANIFEST_SPACE,
2695
2731
  path: "/",
2696
2732
  actions: ["read", "write"]
2697
- },
2698
- {
2699
- service: "tinycloud.capabilities",
2700
- space: DEFAULT_MANIFEST_SPACE,
2701
- path: "/",
2702
- actions: ["read"]
2703
2733
  }
2704
2734
  ];
2705
2735
  var DEFAULT_ADMIN_ENTRIES = [
@@ -2714,12 +2744,6 @@ var DEFAULT_ADMIN_ENTRIES = [
2714
2744
  space: DEFAULT_MANIFEST_SPACE,
2715
2745
  path: "/",
2716
2746
  actions: ["read", "write", "ddl"]
2717
- },
2718
- {
2719
- service: "tinycloud.capabilities",
2720
- space: DEFAULT_MANIFEST_SPACE,
2721
- path: "/",
2722
- actions: ["read", "admin"]
2723
2747
  }
2724
2748
  ];
2725
2749
  var DEFAULT_ALL_ENTRIES = [
@@ -2740,12 +2764,6 @@ var DEFAULT_ALL_ENTRIES = [
2740
2764
  space: DEFAULT_MANIFEST_SPACE,
2741
2765
  path: "/",
2742
2766
  actions: ["read", "write"]
2743
- },
2744
- {
2745
- service: "tinycloud.capabilities",
2746
- space: DEFAULT_MANIFEST_SPACE,
2747
- path: "/",
2748
- actions: ["read", "admin"]
2749
2767
  }
2750
2768
  ];
2751
2769
  function parseExpiry(duration) {
@@ -2770,6 +2788,20 @@ function expandActionShortNames(service, actions) {
2770
2788
  return `${service}/${a}`;
2771
2789
  });
2772
2790
  }
2791
+ function expandPermissionEntry(entry) {
2792
+ if (entry.service !== VAULT_PERMISSION_SERVICE) {
2793
+ return [
2794
+ {
2795
+ ...entry,
2796
+ actions: expandActionShortNames(entry.service, entry.actions)
2797
+ }
2798
+ ];
2799
+ }
2800
+ return expandVaultPermissionEntry(entry);
2801
+ }
2802
+ function expandPermissionEntries(entries) {
2803
+ return entries.flatMap(expandPermissionEntry);
2804
+ }
2773
2805
  function applyPrefix(prefix, path, skipPrefix) {
2774
2806
  if (skipPrefix) {
2775
2807
  return path;
@@ -2841,8 +2873,49 @@ function validateManifest(input) {
2841
2873
  (p, i) => validatePermissionEntry(p, `permissions[${i}]`)
2842
2874
  );
2843
2875
  }
2876
+ if (m.secrets !== void 0) {
2877
+ validateManifestSecrets(m.secrets);
2878
+ }
2844
2879
  return m;
2845
2880
  }
2881
+ function validateManifestSecrets(secrets) {
2882
+ if (secrets === null || typeof secrets !== "object" || Array.isArray(secrets)) {
2883
+ throw new ManifestValidationError("manifest.secrets must be an object");
2884
+ }
2885
+ for (const [name, spec] of Object.entries(secrets)) {
2886
+ if (!SECRET_NAME_RE.test(name)) {
2887
+ throw new ManifestValidationError(
2888
+ `manifest.secrets.${name} must match ${SECRET_NAME_RE.source}`
2889
+ );
2890
+ }
2891
+ try {
2892
+ resolveSecretPath(
2893
+ secretNameFromSpec(name, spec),
2894
+ { scope: secretScopeFromSpec(spec) }
2895
+ );
2896
+ } catch (error) {
2897
+ throw new ManifestValidationError(
2898
+ `manifest.secrets.${name}: ${error instanceof Error ? error.message : String(error)}`
2899
+ );
2900
+ }
2901
+ const actions = secretActionsFromSpec(name, spec);
2902
+ if (actions.length === 0) {
2903
+ throw new ManifestValidationError(
2904
+ `manifest.secrets.${name} actions must be non-empty`
2905
+ );
2906
+ }
2907
+ for (const action of actions) {
2908
+ if (typeof action !== "string" || action.length === 0) {
2909
+ throw new ManifestValidationError(
2910
+ `manifest.secrets.${name} actions must be non-empty strings`
2911
+ );
2912
+ }
2913
+ }
2914
+ if (spec !== null && typeof spec === "object" && !Array.isArray(spec) && spec.expiry !== void 0) {
2915
+ parseExpiry(spec.expiry);
2916
+ }
2917
+ }
2918
+ }
2846
2919
  function validatePermissionEntry(p, path) {
2847
2920
  if (p === null || typeof p !== "object") {
2848
2921
  throw new ManifestValidationError(`${path} must be an object`);
@@ -2866,6 +2939,16 @@ function validatePermissionEntry(p, path) {
2866
2939
  `${path}.actions must be a non-empty array`
2867
2940
  );
2868
2941
  }
2942
+ for (const action of entry.actions) {
2943
+ if (typeof action !== "string" || action.length === 0) {
2944
+ throw new ManifestValidationError(
2945
+ `${path}.actions must contain non-empty strings`
2946
+ );
2947
+ }
2948
+ if (entry.service === VAULT_PERMISSION_SERVICE) {
2949
+ vaultActionExpansion(action);
2950
+ }
2951
+ }
2869
2952
  if (entry.expiry !== void 0) {
2870
2953
  parseExpiry(entry.expiry);
2871
2954
  }
@@ -2895,7 +2978,8 @@ function defaultEntriesForTier(tier) {
2895
2978
  service: e.service,
2896
2979
  space: e.space,
2897
2980
  path: e.path,
2898
- actions: [...e.actions]
2981
+ actions: [...e.actions],
2982
+ ...e.skipPrefix !== void 0 ? { skipPrefix: e.skipPrefix } : {}
2899
2983
  }));
2900
2984
  }
2901
2985
  function resolveManifest(input) {
@@ -2907,9 +2991,14 @@ function resolveManifest(input) {
2907
2991
  const tier = normalizeDefaults(manifest.defaults);
2908
2992
  const defaultEntries = defaultEntriesForTier(tier);
2909
2993
  const explicitEntries = manifest.permissions ?? [];
2910
- const allEntries = [...defaultEntries, ...explicitEntries];
2911
- const resources = allEntries.map(
2912
- (entry) => resolveEntry(entry, prefix, expiryMs, space)
2994
+ const secretEntries = secretEntriesForManifest(manifest.secrets);
2995
+ const allEntries = [
2996
+ ...defaultEntries,
2997
+ ...explicitEntries,
2998
+ ...secretEntries
2999
+ ];
3000
+ const resources = withCapabilitiesReadForSpaces(
3001
+ allEntries.flatMap((entry) => resolveEntry(entry, prefix, expiryMs, space))
2913
3002
  );
2914
3003
  const additionalDelegates = manifest.did === void 0 ? [] : [
2915
3004
  {
@@ -2929,25 +3018,191 @@ function resolveManifest(input) {
2929
3018
  additionalDelegates
2930
3019
  };
2931
3020
  }
3021
+ function normalizeSecretActions(actions) {
3022
+ const out = [];
3023
+ const seen = /* @__PURE__ */ new Set();
3024
+ const add = (action) => {
3025
+ if (!seen.has(action)) {
3026
+ out.push(action);
3027
+ seen.add(action);
3028
+ }
3029
+ };
3030
+ for (const action of actions) {
3031
+ if (action === "read") {
3032
+ add("get");
3033
+ continue;
3034
+ }
3035
+ if (action === "write") {
3036
+ add("put");
3037
+ continue;
3038
+ }
3039
+ if (action === "delete") {
3040
+ add("del");
3041
+ continue;
3042
+ }
3043
+ if (action === "get" || action === "put" || action === "del" || action === "list" || action === "metadata") {
3044
+ add(action);
3045
+ continue;
3046
+ }
3047
+ if (action === "tinycloud.kv/get" || action === "tinycloud.kv/put" || action === "tinycloud.kv/del" || action === "tinycloud.kv/list" || action === "tinycloud.kv/metadata") {
3048
+ add(action);
3049
+ continue;
3050
+ }
3051
+ throw new ManifestValidationError(
3052
+ `unknown secret action ${JSON.stringify(action)}; expected read, write, delete, list, or metadata`
3053
+ );
3054
+ }
3055
+ return out;
3056
+ }
3057
+ function secretNameFromSpec(fallbackName, spec) {
3058
+ if (spec !== null && typeof spec === "object" && !Array.isArray(spec)) {
3059
+ return spec.name ?? fallbackName;
3060
+ }
3061
+ return fallbackName;
3062
+ }
3063
+ function secretScopeFromSpec(spec) {
3064
+ if (spec !== null && typeof spec === "object" && !Array.isArray(spec)) {
3065
+ return spec.scope;
3066
+ }
3067
+ return void 0;
3068
+ }
3069
+ function secretActionsFromSpec(name, spec) {
3070
+ if (spec === true) {
3071
+ return ["read"];
3072
+ }
3073
+ if (typeof spec === "string") {
3074
+ return [spec];
3075
+ }
3076
+ if (Array.isArray(spec)) {
3077
+ return spec;
3078
+ }
3079
+ if (spec === null || typeof spec !== "object") {
3080
+ throw new ManifestValidationError(
3081
+ `manifest.secrets.${name} must be true, a string action, an actions array, or an object`
3082
+ );
3083
+ }
3084
+ if (spec.actions === void 0) {
3085
+ return ["read"];
3086
+ }
3087
+ if (typeof spec.actions === "string") {
3088
+ return [spec.actions];
3089
+ }
3090
+ if (Array.isArray(spec.actions)) {
3091
+ return spec.actions;
3092
+ }
3093
+ throw new ManifestValidationError(
3094
+ `manifest.secrets.${name}.actions must be a string or array`
3095
+ );
3096
+ }
3097
+ function secretEntriesForManifest(secrets) {
3098
+ if (secrets === void 0) {
3099
+ return [];
3100
+ }
3101
+ const entries = [];
3102
+ for (const [name, spec] of Object.entries(secrets)) {
3103
+ const actions = secretActionsFromSpec(name, spec);
3104
+ const secretPath = resolveSecretPath(
3105
+ secretNameFromSpec(name, spec),
3106
+ { scope: secretScopeFromSpec(spec) }
3107
+ );
3108
+ const extra = spec !== true && typeof spec === "object" && !Array.isArray(spec) ? spec : {};
3109
+ entries.push({
3110
+ service: VAULT_PERMISSION_SERVICE,
3111
+ space: SECRETS_SPACE,
3112
+ path: secretPath.vaultKey,
3113
+ actions: normalizeSecretActions(actions),
3114
+ skipPrefix: true,
3115
+ ...extra.expiry !== void 0 ? { expiry: extra.expiry } : {},
3116
+ ...extra.description !== void 0 ? { description: extra.description } : {}
3117
+ });
3118
+ }
3119
+ return entries;
3120
+ }
2932
3121
  function resolveEntry(entry, prefix, _inheritedExpiryMs, inheritedSpace) {
2933
3122
  const resolvedPath = applyPrefix(
2934
3123
  prefix,
2935
3124
  entry.path,
2936
3125
  entry.skipPrefix === true
2937
3126
  );
2938
- const resolvedActions = expandActionShortNames(entry.service, entry.actions);
2939
3127
  const entryExpiryMs = entry.expiry !== void 0 ? parseExpiry(entry.expiry) : void 0;
2940
- return {
2941
- service: entry.service,
3128
+ return expandPermissionEntry({
3129
+ ...entry,
2942
3130
  space: entry.space ?? inheritedSpace,
2943
3131
  path: resolvedPath,
2944
- actions: resolvedActions,
3132
+ skipPrefix: true
3133
+ }).map((expanded) => ({
3134
+ service: expanded.service,
3135
+ space: expanded.space ?? inheritedSpace,
3136
+ path: expanded.path,
3137
+ actions: expanded.actions,
2945
3138
  // Only populate `expiryMs` when the entry had its own expiry override.
2946
3139
  // When absent, callers use the parent (delegation or manifest) expiry
2947
3140
  // which is carried on ResolvedDelegate.expiryMs / ResolvedCapabilities.expiryMs.
2948
3141
  ...entryExpiryMs !== void 0 ? { expiryMs: entryExpiryMs } : {},
2949
3142
  ...entry.description !== void 0 ? { description: entry.description } : {}
2950
- };
3143
+ }));
3144
+ }
3145
+ function expandVaultPermissionEntry(entry) {
3146
+ const byBase = /* @__PURE__ */ new Map();
3147
+ for (const action of entry.actions) {
3148
+ const expansion = vaultActionExpansion(action);
3149
+ for (const base of expansion.bases) {
3150
+ const actions = byBase.get(base) ?? [];
3151
+ if (!actions.includes(expansion.action)) {
3152
+ actions.push(expansion.action);
3153
+ }
3154
+ byBase.set(base, actions);
3155
+ }
3156
+ }
3157
+ return [...byBase.entries()].map(([base, actions]) => ({
3158
+ ...entry,
3159
+ service: "tinycloud.kv",
3160
+ path: vaultKVPath(base, entry.path),
3161
+ actions,
3162
+ skipPrefix: true
3163
+ }));
3164
+ }
3165
+ function vaultActionExpansion(action) {
3166
+ const normalized = normalizeVaultAction(action);
3167
+ if (normalized === "read" || normalized === "get") {
3168
+ return { bases: ["keys", "vault"], action: "tinycloud.kv/get" };
3169
+ }
3170
+ if (normalized === "write" || normalized === "put") {
3171
+ return { bases: ["keys", "vault"], action: "tinycloud.kv/put" };
3172
+ }
3173
+ if (normalized === "delete" || normalized === "del") {
3174
+ return { bases: ["keys", "vault"], action: "tinycloud.kv/del" };
3175
+ }
3176
+ if (normalized === "list") {
3177
+ return { bases: ["vault"], action: "tinycloud.kv/list" };
3178
+ }
3179
+ if (normalized === "head") {
3180
+ return { bases: ["vault"], action: "tinycloud.kv/get" };
3181
+ }
3182
+ if (normalized === "metadata") {
3183
+ return { bases: ["vault"], action: "tinycloud.kv/metadata" };
3184
+ }
3185
+ throw new ManifestValidationError(
3186
+ `unknown vault action ${JSON.stringify(action)}; expected read, write, delete, get, put, del, list, head, or metadata`
3187
+ );
3188
+ }
3189
+ function normalizeVaultAction(action) {
3190
+ if (action.startsWith(`${VAULT_PERMISSION_SERVICE}/`)) {
3191
+ return action.slice(`${VAULT_PERMISSION_SERVICE}/`.length);
3192
+ }
3193
+ if (action.startsWith("tinycloud.kv/")) {
3194
+ return action.slice("tinycloud.kv/".length);
3195
+ }
3196
+ if (action.includes("/")) {
3197
+ throw new ManifestValidationError(
3198
+ `unknown vault action ${JSON.stringify(action)}; expected a tinycloud.vault or tinycloud.kv action`
3199
+ );
3200
+ }
3201
+ return action;
3202
+ }
3203
+ function vaultKVPath(base, path) {
3204
+ const normalized = path.startsWith("/") ? path.slice(1) : path;
3205
+ return `${base}/${normalized}`;
2951
3206
  }
2952
3207
  function cloneResourceCapability(entry) {
2953
3208
  return {
@@ -2992,6 +3247,24 @@ function dedupeResources(resources) {
2992
3247
  }
2993
3248
  return [...byKey.values()];
2994
3249
  }
3250
+ function capabilitiesReadPermission(space) {
3251
+ return {
3252
+ service: "tinycloud.capabilities",
3253
+ space,
3254
+ path: "",
3255
+ actions: ["tinycloud.capabilities/read"]
3256
+ };
3257
+ }
3258
+ function withCapabilitiesReadForSpaces(resources) {
3259
+ if (resources.length === 0) {
3260
+ return [];
3261
+ }
3262
+ const spaces = new Set(resources.map((resource) => resource.space));
3263
+ return dedupeResources([
3264
+ ...resources,
3265
+ ...[...spaces].map(capabilitiesReadPermission)
3266
+ ]);
3267
+ }
2995
3268
  function accountRegistryPermission() {
2996
3269
  return {
2997
3270
  service: "tinycloud.kv",
@@ -3019,6 +3292,7 @@ function composeManifestRequest(inputs, options = {}) {
3019
3292
  if (includeAccountRegistryPermissions) {
3020
3293
  resources.push(accountRegistryPermission());
3021
3294
  }
3295
+ const resourcesWithImplicitCapabilities = withCapabilitiesReadForSpaces(resources);
3022
3296
  const manifestsByAppId = /* @__PURE__ */ new Map();
3023
3297
  for (const manifest of manifests) {
3024
3298
  const current = manifestsByAppId.get(manifest.app_id);
@@ -3038,7 +3312,7 @@ function composeManifestRequest(inputs, options = {}) {
3038
3312
  })) : [];
3039
3313
  return {
3040
3314
  manifests,
3041
- resources: dedupeResources(resources),
3315
+ resources: resourcesWithImplicitCapabilities,
3042
3316
  delegationTargets,
3043
3317
  registryRecords,
3044
3318
  expiryMs: Math.max(...resolved.map((entry) => entry.expiryMs)),
@@ -4250,6 +4524,394 @@ async function checkNodeInfo(host, sdkProtocol, fetchFn = globalThis.fetch.bind(
4250
4524
  };
4251
4525
  }
4252
4526
 
4527
+ // src/location.ts
4528
+ import { multiaddr } from "@multiformats/multiaddr";
4529
+ import { multiaddrToUri } from "@multiformats/multiaddr-to-uri";
4530
+ import { uriToMultiaddr } from "@multiformats/uri-to-multiaddr";
4531
+ import { ed25519 } from "@noble/curves/ed25519";
4532
+ import { bases } from "multiformats/basics";
4533
+ import { verifyMessage } from "viem";
4534
+ var DEFAULT_TINYCLOUD_LOCATION_REGISTRY_URL = "https://registry.tinycloud.xyz";
4535
+ var DEFAULT_TINYCLOUD_FALLBACK_HOST = "https://node.tinycloud.xyz";
4536
+ var LocationRecordValidationError = class extends Error {
4537
+ constructor(message) {
4538
+ super(`Location record validation failed: ${message}`);
4539
+ this.name = "LocationRecordValidationError";
4540
+ }
4541
+ };
4542
+ var CloudLocationResolutionError = class extends Error {
4543
+ constructor(subject, attempts) {
4544
+ super(`Unable to resolve TinyCloud location for ${subject}`);
4545
+ this.name = "CloudLocationResolutionError";
4546
+ this.attempts = attempts;
4547
+ }
4548
+ };
4549
+ function locationPayloadForRecord(record) {
4550
+ return {
4551
+ version: record.version,
4552
+ subject: record.subject,
4553
+ multiaddrs: [...record.multiaddrs],
4554
+ updated_at: record.updated_at,
4555
+ sequence: record.sequence
4556
+ };
4557
+ }
4558
+ function canonicalLocationPayload(payload) {
4559
+ return JSON.stringify({
4560
+ version: payload.version,
4561
+ subject: payload.subject,
4562
+ multiaddrs: payload.multiaddrs,
4563
+ updated_at: payload.updated_at,
4564
+ sequence: payload.sequence
4565
+ });
4566
+ }
4567
+ async function signLocationRecord(payload, signer) {
4568
+ validateLocationRecordPayload(payload);
4569
+ const message = canonicalLocationPayload(payload);
4570
+ const signature = signer.type === "did:pkh" ? await signer.signMessage(message) : base64UrlEncode2(
4571
+ await signer.signBytes(new TextEncoder().encode(message))
4572
+ );
4573
+ return { ...payload, signature };
4574
+ }
4575
+ function validateLocationRecordPayload(input) {
4576
+ if (input === null || typeof input !== "object") {
4577
+ throw new LocationRecordValidationError("payload must be an object");
4578
+ }
4579
+ const payload = input;
4580
+ if (payload.version !== 1) {
4581
+ throw new LocationRecordValidationError("version must be 1");
4582
+ }
4583
+ validateSubject(payload.subject);
4584
+ validateMultiaddrs(payload.multiaddrs);
4585
+ if (typeof payload.updated_at !== "string" || Number.isNaN(Date.parse(payload.updated_at))) {
4586
+ throw new LocationRecordValidationError(
4587
+ "updated_at must be an ISO timestamp"
4588
+ );
4589
+ }
4590
+ if (typeof payload.sequence !== "number" || !Number.isSafeInteger(payload.sequence) || payload.sequence < 0) {
4591
+ throw new LocationRecordValidationError(
4592
+ "sequence must be a non-negative safe integer"
4593
+ );
4594
+ }
4595
+ return {
4596
+ version: 1,
4597
+ subject: payload.subject,
4598
+ multiaddrs: [...payload.multiaddrs],
4599
+ updated_at: payload.updated_at,
4600
+ sequence: payload.sequence
4601
+ };
4602
+ }
4603
+ function validateLocationRecord(input) {
4604
+ const payload = validateLocationRecordPayload(input);
4605
+ const signature = input.signature;
4606
+ if (typeof signature !== "string" || signature.length === 0) {
4607
+ throw new LocationRecordValidationError(
4608
+ "signature must be a non-empty string"
4609
+ );
4610
+ }
4611
+ return { ...payload, signature };
4612
+ }
4613
+ async function verifyLocationRecord(input) {
4614
+ const record = validateLocationRecord(input);
4615
+ const payload = canonicalLocationPayload(locationPayloadForRecord(record));
4616
+ if (record.subject.startsWith("did:pkh:")) {
4617
+ return verifyPkhSignature(record.subject, payload, record.signature);
4618
+ }
4619
+ if (record.subject.startsWith("did:key:")) {
4620
+ return verifyDidKeySignature(record.subject, payload, record.signature);
4621
+ }
4622
+ return false;
4623
+ }
4624
+ async function fetchLocationRecord(registryUrl, subject, fetchFn = globalThis.fetch) {
4625
+ const url = `${registryUrl.replace(/\/$/, "")}/v1/locations/${encodeURIComponent(subject)}`;
4626
+ const response = await fetchFn(url);
4627
+ if (response.status === 404) {
4628
+ return null;
4629
+ }
4630
+ if (!response.ok) {
4631
+ throw new Error(`location registry returned HTTP ${response.status}`);
4632
+ }
4633
+ const body = await response.json();
4634
+ if (body.record === void 0) {
4635
+ throw new LocationRecordValidationError("registry response missing record");
4636
+ }
4637
+ return validateLocationRecord(body.record);
4638
+ }
4639
+ async function resolveCloudLocation(subject, options = {}) {
4640
+ validateSubject(subject);
4641
+ const verifyRecords = options.verifyRecords ?? true;
4642
+ const attempts = await Promise.all([
4643
+ resolveExplicit(subject, options.explicitMultiaddrs),
4644
+ resolveBlockchain(subject, options.blockchain, verifyRecords),
4645
+ resolveCentralized(subject, options, verifyRecords),
4646
+ resolveFallback(subject, options.fallbackMultiaddrs)
4647
+ ]);
4648
+ const winner = attempts.find((attempt) => attempt.candidate)?.candidate;
4649
+ if (!winner) {
4650
+ throw new CloudLocationResolutionError(subject, attempts);
4651
+ }
4652
+ return {
4653
+ subject,
4654
+ source: winner.source,
4655
+ multiaddrs: [...winner.multiaddrs],
4656
+ ...winner.record ? { record: winner.record } : {},
4657
+ attempts,
4658
+ resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
4659
+ };
4660
+ }
4661
+ async function resolveTinyCloudHosts(subject, options = {}) {
4662
+ const location = await resolveCloudLocation(subject, {
4663
+ explicitMultiaddrs: hostsToMultiaddrs(options.explicitHosts),
4664
+ blockchain: options.blockchain,
4665
+ centralizedRegistryUrl: options.registryUrl === null ? void 0 : options.registryUrl ?? DEFAULT_TINYCLOUD_LOCATION_REGISTRY_URL,
4666
+ fallbackMultiaddrs: hostsToMultiaddrs(
4667
+ options.fallbackHosts === null ? void 0 : options.fallbackHosts ?? [DEFAULT_TINYCLOUD_FALLBACK_HOST]
4668
+ ),
4669
+ fetch: options.fetch,
4670
+ verifyRecords: options.verifyRecords
4671
+ });
4672
+ return {
4673
+ hosts: location.multiaddrs.map((addr) => multiaddrToHttpUrl(addr)),
4674
+ location
4675
+ };
4676
+ }
4677
+ function multiaddrToHttpUrl(input) {
4678
+ const uri = multiaddrToUri(multiaddr(input));
4679
+ if (!uri.startsWith("http://") && !uri.startsWith("https://")) {
4680
+ throw new LocationRecordValidationError(
4681
+ `multiaddr does not resolve to http/https: ${input}`
4682
+ );
4683
+ }
4684
+ return uri;
4685
+ }
4686
+ function httpUrlToMultiaddr(input) {
4687
+ const url = new URL(input);
4688
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
4689
+ throw new LocationRecordValidationError("URL must use http or https");
4690
+ }
4691
+ return uriToMultiaddr(url.toString()).toString();
4692
+ }
4693
+ function hostsToMultiaddrs(hosts) {
4694
+ if (hosts === void 0 || hosts.length === 0) {
4695
+ return void 0;
4696
+ }
4697
+ return hosts.map(
4698
+ (host) => host.startsWith("/") ? host : httpUrlToMultiaddr(host)
4699
+ );
4700
+ }
4701
+ async function resolveExplicit(subject, multiaddrs) {
4702
+ return resolveAttempt("explicit", async () => {
4703
+ if (multiaddrs === void 0 || multiaddrs.length === 0) {
4704
+ return null;
4705
+ }
4706
+ return toCandidate(subject, "explicit", multiaddrs, false);
4707
+ });
4708
+ }
4709
+ async function resolveBlockchain(subject, resolver, verifyRecords) {
4710
+ return resolveAttempt("blockchain", async () => {
4711
+ if (!resolver) {
4712
+ return null;
4713
+ }
4714
+ return toCandidate(
4715
+ subject,
4716
+ "blockchain",
4717
+ await resolver(subject),
4718
+ verifyRecords
4719
+ );
4720
+ });
4721
+ }
4722
+ async function resolveCentralized(subject, options, verifyRecords) {
4723
+ return resolveAttempt("centralized", async () => {
4724
+ if (!options.centralizedRegistryUrl) {
4725
+ return null;
4726
+ }
4727
+ const record = await fetchLocationRecord(
4728
+ options.centralizedRegistryUrl,
4729
+ subject,
4730
+ options.fetch
4731
+ );
4732
+ return toCandidate(subject, "centralized", record, verifyRecords);
4733
+ });
4734
+ }
4735
+ async function resolveFallback(subject, multiaddrs) {
4736
+ return resolveAttempt("fallback", async () => {
4737
+ if (multiaddrs === void 0 || multiaddrs.length === 0) {
4738
+ return null;
4739
+ }
4740
+ return toCandidate(subject, "fallback", multiaddrs, false);
4741
+ });
4742
+ }
4743
+ async function resolveAttempt(source, resolve) {
4744
+ try {
4745
+ const candidate = await resolve();
4746
+ return candidate ? { source, candidate } : { source };
4747
+ } catch (error) {
4748
+ return {
4749
+ source,
4750
+ error: error instanceof Error ? error : new Error(String(error))
4751
+ };
4752
+ }
4753
+ }
4754
+ async function toCandidate(subject, source, input, verifyRecord) {
4755
+ if (input === null || input === void 0) {
4756
+ return null;
4757
+ }
4758
+ if (Array.isArray(input)) {
4759
+ validateMultiaddrs(input);
4760
+ return { source, multiaddrs: [...input] };
4761
+ }
4762
+ const maybeRecord = input;
4763
+ if (maybeRecord.version === 1 && maybeRecord.signature !== void 0) {
4764
+ const record = validateLocationRecord(input);
4765
+ if (record.subject !== subject) {
4766
+ throw new LocationRecordValidationError(
4767
+ "location record subject does not match requested subject"
4768
+ );
4769
+ }
4770
+ if (verifyRecord && !await verifyLocationRecord(record)) {
4771
+ throw new LocationRecordValidationError(
4772
+ "location record signature is invalid"
4773
+ );
4774
+ }
4775
+ return { source, multiaddrs: [...record.multiaddrs], record };
4776
+ }
4777
+ const candidateInput = input;
4778
+ if (!Array.isArray(candidateInput.multiaddrs)) {
4779
+ throw new LocationRecordValidationError(
4780
+ "candidate multiaddrs must be an array"
4781
+ );
4782
+ }
4783
+ validateMultiaddrs(candidateInput.multiaddrs);
4784
+ if (candidateInput.record !== void 0) {
4785
+ const record = validateLocationRecord(candidateInput.record);
4786
+ if (record.subject !== subject) {
4787
+ throw new LocationRecordValidationError(
4788
+ "location record subject does not match requested subject"
4789
+ );
4790
+ }
4791
+ if (verifyRecord && !await verifyLocationRecord(record)) {
4792
+ throw new LocationRecordValidationError(
4793
+ "location record signature is invalid"
4794
+ );
4795
+ }
4796
+ return { source, multiaddrs: [...candidateInput.multiaddrs], record };
4797
+ }
4798
+ return { source, multiaddrs: [...candidateInput.multiaddrs] };
4799
+ }
4800
+ function validateSubject(subject) {
4801
+ if (typeof subject !== "string" || subject.length === 0) {
4802
+ throw new LocationRecordValidationError(
4803
+ "subject must be a non-empty string"
4804
+ );
4805
+ }
4806
+ if (!subject.startsWith("did:pkh:") && !subject.startsWith("did:key:")) {
4807
+ throw new LocationRecordValidationError(
4808
+ "subject must be did:pkh or did:key"
4809
+ );
4810
+ }
4811
+ }
4812
+ function validateMultiaddrs(input) {
4813
+ if (!Array.isArray(input)) {
4814
+ throw new LocationRecordValidationError("multiaddrs must be an array");
4815
+ }
4816
+ for (const addr of input) {
4817
+ if (typeof addr !== "string" || addr.length === 0) {
4818
+ throw new LocationRecordValidationError(
4819
+ "multiaddr entries must be non-empty strings"
4820
+ );
4821
+ }
4822
+ try {
4823
+ multiaddr(addr);
4824
+ } catch {
4825
+ throw new LocationRecordValidationError(`invalid multiaddr: ${addr}`);
4826
+ }
4827
+ }
4828
+ }
4829
+ async function verifyPkhSignature(did, payload, signature) {
4830
+ const address = did.split(":").at(-1);
4831
+ if (!address || !/^0x[a-fA-F0-9]{40}$/.test(address)) {
4832
+ throw new LocationRecordValidationError(
4833
+ "did:pkh subject must end with an EVM address"
4834
+ );
4835
+ }
4836
+ if (!/^0x[0-9a-fA-F]+$/.test(signature)) {
4837
+ throw new LocationRecordValidationError("did:pkh signature must be hex");
4838
+ }
4839
+ return verifyMessage({
4840
+ address,
4841
+ message: payload,
4842
+ signature
4843
+ });
4844
+ }
4845
+ function verifyDidKeySignature(did, payload, signature) {
4846
+ const publicKey = ed25519PublicKeyFromDidKey(did);
4847
+ const signatureBytes = decodeBase64Url(signature);
4848
+ if (signatureBytes.length !== 64) {
4849
+ throw new LocationRecordValidationError(
4850
+ "did:key signature must be a base64url Ed25519 signature"
4851
+ );
4852
+ }
4853
+ return ed25519.verify(
4854
+ signatureBytes,
4855
+ new TextEncoder().encode(payload),
4856
+ publicKey
4857
+ );
4858
+ }
4859
+ function ed25519PublicKeyFromDidKey(did) {
4860
+ const identifier = did.slice("did:key:".length);
4861
+ if (!identifier.startsWith("z")) {
4862
+ throw new LocationRecordValidationError(
4863
+ "did:key must use base58btc multibase"
4864
+ );
4865
+ }
4866
+ const bytes = bases.base58btc.decode(identifier);
4867
+ if (bytes.length !== 34 || bytes[0] !== 237 || bytes[1] !== 1) {
4868
+ throw new LocationRecordValidationError(
4869
+ "did:key must be an Ed25519 public key"
4870
+ );
4871
+ }
4872
+ return bytes.slice(2);
4873
+ }
4874
+ function base64UrlEncode2(bytes) {
4875
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
4876
+ let output = "";
4877
+ for (let i = 0; i < bytes.length; i += 3) {
4878
+ const a = bytes[i];
4879
+ const b = bytes[i + 1];
4880
+ const c = bytes[i + 2];
4881
+ const triplet = a << 16 | (b ?? 0) << 8 | (c ?? 0);
4882
+ output += alphabet[triplet >> 18 & 63];
4883
+ output += alphabet[triplet >> 12 & 63];
4884
+ if (i + 1 < bytes.length) {
4885
+ output += alphabet[triplet >> 6 & 63];
4886
+ }
4887
+ if (i + 2 < bytes.length) {
4888
+ output += alphabet[triplet & 63];
4889
+ }
4890
+ }
4891
+ return output;
4892
+ }
4893
+ function decodeBase64Url(value) {
4894
+ const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
4895
+ const bytes = [];
4896
+ let buffer = 0;
4897
+ let bits = 0;
4898
+ for (const char of value) {
4899
+ const index = alphabet.indexOf(char);
4900
+ if (index < 0) {
4901
+ throw new LocationRecordValidationError(
4902
+ "did:key signature must be base64url"
4903
+ );
4904
+ }
4905
+ buffer = buffer << 6 | index;
4906
+ bits += 6;
4907
+ if (bits >= 8) {
4908
+ bits -= 8;
4909
+ bytes.push(buffer >> bits & 255);
4910
+ }
4911
+ }
4912
+ return Uint8Array.from(bytes);
4913
+ }
4914
+
4253
4915
  // src/capabilities.ts
4254
4916
  var PermissionNotInManifestError = class extends Error {
4255
4917
  constructor(missing, granted) {
@@ -4370,10 +5032,13 @@ export {
4370
5032
  CapabilityKeyRegistry,
4371
5033
  CapabilityKeyRegistryErrorCodes,
4372
5034
  ClientSessionSchema,
5035
+ CloudLocationResolutionError,
4373
5036
  DEFAULT_DEFAULTS,
4374
5037
  DEFAULT_EXPIRY,
4375
5038
  DEFAULT_MANIFEST_SPACE,
4376
5039
  DEFAULT_MANIFEST_VERSION,
5040
+ DEFAULT_TINYCLOUD_FALLBACK_HOST,
5041
+ DEFAULT_TINYCLOUD_LOCATION_REGISTRY_URL,
4377
5042
  DataVaultService,
4378
5043
  DatabaseHandle,
4379
5044
  DelegationErrorCodes,
@@ -4385,14 +5050,17 @@ export {
4385
5050
  ErrorCodes2 as ErrorCodes,
4386
5051
  HooksService2 as HooksService,
4387
5052
  KVService2 as KVService,
5053
+ LocationRecordValidationError,
4388
5054
  ManifestValidationError,
4389
5055
  PermissionNotInManifestError,
4390
5056
  PrefixedKVService,
4391
5057
  ProtocolMismatchError,
5058
+ SECRET_NAME_RE2 as SECRET_NAME_RE,
4392
5059
  SERVICE_LONG_TO_SHORT,
4393
5060
  SERVICE_SHORT_TO_LONG,
4394
5061
  SQLAction,
4395
5062
  SQLService2 as SQLService,
5063
+ SecretsService,
4396
5064
  ServiceContext2 as ServiceContext,
4397
5065
  SessionExpiredError,
4398
5066
  SharingService,
@@ -4404,12 +5072,15 @@ export {
4404
5072
  SpaceService,
4405
5073
  TinyCloud,
4406
5074
  UnsupportedFeatureError,
5075
+ VAULT_PERMISSION_SERVICE,
4407
5076
  VaultHeaders,
4408
5077
  VaultPublicSpaceKVActions,
4409
5078
  VersionCheckError,
4410
5079
  activateSessionWithHost,
4411
5080
  applyPrefix,
4412
5081
  buildSpaceUri,
5082
+ canonicalLocationPayload,
5083
+ canonicalizeSecretScope,
4413
5084
  checkNodeInfo,
4414
5085
  composeManifestRequest,
4415
5086
  createCapabilityKeyRegistry,
@@ -4421,23 +5092,36 @@ export {
4421
5092
  defaultSpaceCreationHandler,
4422
5093
  err4 as err,
4423
5094
  expandActionShortNames,
5095
+ expandPermissionEntries,
5096
+ expandPermissionEntry,
5097
+ fetchLocationRecord,
4424
5098
  fetchPeerId,
5099
+ httpUrlToMultiaddr,
4425
5100
  isCapabilitySubset,
4426
5101
  loadManifest,
5102
+ locationPayloadForRecord,
4427
5103
  makePublicSpaceId,
4428
5104
  manifestAbilitiesUnion,
5105
+ multiaddrToHttpUrl,
4429
5106
  normalizeDefaults,
4430
5107
  ok4 as ok,
4431
5108
  parseExpiry,
4432
5109
  parseRecapCapabilities,
4433
5110
  parseSpaceUri,
5111
+ resolveCloudLocation,
4434
5112
  resolveManifest,
5113
+ resolveSecretPath2 as resolveSecretPath,
5114
+ resolveTinyCloudHosts,
4435
5115
  resourceCapabilitiesToAbilitiesMap,
4436
5116
  resourceCapabilitiesToSpaceAbilitiesMap,
4437
5117
  serviceError4 as serviceError,
5118
+ signLocationRecord,
4438
5119
  submitHostDelegation,
4439
5120
  validateClientSession,
5121
+ validateLocationRecord,
5122
+ validateLocationRecordPayload,
4440
5123
  validateManifest,
4441
- validatePersistedSessionData
5124
+ validatePersistedSessionData,
5125
+ verifyLocationRecord
4442
5126
  };
4443
5127
  //# sourceMappingURL=index.js.map