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

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
@@ -2666,6 +2666,10 @@ var ManifestValidationError = class extends Error {
2666
2666
  };
2667
2667
  var DEFAULT_EXPIRY = "30d";
2668
2668
  var DEFAULT_DEFAULTS = true;
2669
+ var DEFAULT_MANIFEST_VERSION = 1;
2670
+ var DEFAULT_MANIFEST_SPACE = "applications";
2671
+ var ACCOUNT_REGISTRY_SPACE = "account";
2672
+ var ACCOUNT_REGISTRY_PATH = "applications/";
2669
2673
  var SERVICE_SHORT_TO_LONG = Object.freeze({
2670
2674
  kv: "tinycloud.kv",
2671
2675
  sql: "tinycloud.sql",
@@ -2681,19 +2685,19 @@ var SERVICE_LONG_TO_SHORT = Object.freeze(
2681
2685
  var DEFAULT_STANDARD_ENTRIES = [
2682
2686
  {
2683
2687
  service: "tinycloud.kv",
2684
- space: "default",
2688
+ space: DEFAULT_MANIFEST_SPACE,
2685
2689
  path: "/",
2686
2690
  actions: ["get", "put", "del", "list", "metadata"]
2687
2691
  },
2688
2692
  {
2689
2693
  service: "tinycloud.sql",
2690
- space: "default",
2694
+ space: DEFAULT_MANIFEST_SPACE,
2691
2695
  path: "/",
2692
2696
  actions: ["read", "write"]
2693
2697
  },
2694
2698
  {
2695
2699
  service: "tinycloud.capabilities",
2696
- space: "default",
2700
+ space: DEFAULT_MANIFEST_SPACE,
2697
2701
  path: "/",
2698
2702
  actions: ["read"]
2699
2703
  }
@@ -2701,19 +2705,19 @@ var DEFAULT_STANDARD_ENTRIES = [
2701
2705
  var DEFAULT_ADMIN_ENTRIES = [
2702
2706
  {
2703
2707
  service: "tinycloud.kv",
2704
- space: "default",
2708
+ space: DEFAULT_MANIFEST_SPACE,
2705
2709
  path: "/",
2706
2710
  actions: ["get", "put", "del", "list", "metadata"]
2707
2711
  },
2708
2712
  {
2709
2713
  service: "tinycloud.sql",
2710
- space: "default",
2714
+ space: DEFAULT_MANIFEST_SPACE,
2711
2715
  path: "/",
2712
2716
  actions: ["read", "write", "ddl"]
2713
2717
  },
2714
2718
  {
2715
2719
  service: "tinycloud.capabilities",
2716
- space: "default",
2720
+ space: DEFAULT_MANIFEST_SPACE,
2717
2721
  path: "/",
2718
2722
  actions: ["read", "admin"]
2719
2723
  }
@@ -2721,25 +2725,25 @@ var DEFAULT_ADMIN_ENTRIES = [
2721
2725
  var DEFAULT_ALL_ENTRIES = [
2722
2726
  {
2723
2727
  service: "tinycloud.kv",
2724
- space: "default",
2728
+ space: DEFAULT_MANIFEST_SPACE,
2725
2729
  path: "/",
2726
2730
  actions: ["get", "put", "del", "list", "metadata"]
2727
2731
  },
2728
2732
  {
2729
2733
  service: "tinycloud.sql",
2730
- space: "default",
2734
+ space: DEFAULT_MANIFEST_SPACE,
2731
2735
  path: "/",
2732
2736
  actions: ["read", "write", "ddl"]
2733
2737
  },
2734
2738
  {
2735
2739
  service: "tinycloud.duckdb",
2736
- space: "default",
2740
+ space: DEFAULT_MANIFEST_SPACE,
2737
2741
  path: "/",
2738
2742
  actions: ["read", "write"]
2739
2743
  },
2740
2744
  {
2741
2745
  service: "tinycloud.capabilities",
2742
- space: "default",
2746
+ space: DEFAULT_MANIFEST_SPACE,
2743
2747
  path: "/",
2744
2748
  actions: ["read", "admin"]
2745
2749
  }
@@ -2750,9 +2754,7 @@ function parseExpiry(duration) {
2750
2754
  `expiry must be a non-empty duration string (got ${JSON.stringify(duration)})`
2751
2755
  );
2752
2756
  }
2753
- const parsed = ms(
2754
- duration
2755
- );
2757
+ const parsed = ms(duration);
2756
2758
  if (typeof parsed !== "number" || !Number.isFinite(parsed) || parsed <= 0) {
2757
2759
  throw new ManifestValidationError(
2758
2760
  `invalid expiry duration: ${JSON.stringify(duration)}`
@@ -2801,46 +2803,44 @@ function validateManifest(input) {
2801
2803
  throw new ManifestValidationError("manifest must be an object");
2802
2804
  }
2803
2805
  const m = input;
2804
- if (typeof m.id !== "string" || m.id.length === 0) {
2805
- throw new ManifestValidationError("manifest.id is required and must be a non-empty string");
2806
+ if (m.manifest_version !== void 0 && m.manifest_version !== DEFAULT_MANIFEST_VERSION) {
2807
+ throw new ManifestValidationError(
2808
+ `manifest.manifest_version must be ${DEFAULT_MANIFEST_VERSION}`
2809
+ );
2810
+ }
2811
+ if (typeof m.app_id !== "string" || m.app_id.length === 0) {
2812
+ throw new ManifestValidationError(
2813
+ "manifest.app_id is required and must be a non-empty string"
2814
+ );
2806
2815
  }
2807
2816
  if (typeof m.name !== "string" || m.name.length === 0) {
2808
- throw new ManifestValidationError("manifest.name is required and must be a non-empty string");
2817
+ throw new ManifestValidationError(
2818
+ "manifest.name is required and must be a non-empty string"
2819
+ );
2820
+ }
2821
+ if (m.did !== void 0 && (typeof m.did !== "string" || m.did.length === 0)) {
2822
+ throw new ManifestValidationError(
2823
+ "manifest.did must be a non-empty DID string"
2824
+ );
2825
+ }
2826
+ if (m.space !== void 0 && (typeof m.space !== "string" || m.space.length === 0)) {
2827
+ throw new ManifestValidationError(
2828
+ "manifest.space must be a non-empty string"
2829
+ );
2809
2830
  }
2810
2831
  if (m.expiry !== void 0) {
2811
2832
  parseExpiry(m.expiry);
2812
2833
  }
2813
2834
  if (m.permissions !== void 0) {
2814
2835
  if (!Array.isArray(m.permissions)) {
2815
- throw new ManifestValidationError("manifest.permissions must be an array");
2836
+ throw new ManifestValidationError(
2837
+ "manifest.permissions must be an array"
2838
+ );
2816
2839
  }
2817
2840
  m.permissions.forEach(
2818
2841
  (p, i) => validatePermissionEntry(p, `permissions[${i}]`)
2819
2842
  );
2820
2843
  }
2821
- if (m.delegations !== void 0) {
2822
- if (!Array.isArray(m.delegations)) {
2823
- throw new ManifestValidationError("manifest.delegations must be an array");
2824
- }
2825
- m.delegations.forEach((d, i) => {
2826
- if (typeof d?.to !== "string" || d.to.length === 0) {
2827
- throw new ManifestValidationError(
2828
- `delegations[${i}].to is required and must be a non-empty DID string`
2829
- );
2830
- }
2831
- if (d.expiry !== void 0) {
2832
- parseExpiry(d.expiry);
2833
- }
2834
- if (!Array.isArray(d.permissions)) {
2835
- throw new ManifestValidationError(
2836
- `delegations[${i}].permissions must be an array`
2837
- );
2838
- }
2839
- d.permissions.forEach(
2840
- (p, j) => validatePermissionEntry(p, `delegations[${i}].permissions[${j}]`)
2841
- );
2842
- });
2843
- }
2844
2844
  return m;
2845
2845
  }
2846
2846
  function validatePermissionEntry(p, path) {
@@ -2851,8 +2851,10 @@ function validatePermissionEntry(p, path) {
2851
2851
  if (typeof entry.service !== "string" || entry.service.length === 0) {
2852
2852
  throw new ManifestValidationError(`${path}.service is required`);
2853
2853
  }
2854
- if (typeof entry.space !== "string" || entry.space.length === 0) {
2855
- throw new ManifestValidationError(`${path}.space is required`);
2854
+ if (entry.space !== void 0 && (typeof entry.space !== "string" || entry.space.length === 0)) {
2855
+ throw new ManifestValidationError(
2856
+ `${path}.space must be a non-empty string`
2857
+ );
2856
2858
  }
2857
2859
  if (typeof entry.path !== "string") {
2858
2860
  throw new ManifestValidationError(
@@ -2898,7 +2900,8 @@ function defaultEntriesForTier(tier) {
2898
2900
  }
2899
2901
  function resolveManifest(input) {
2900
2902
  const manifest = validateManifest(input);
2901
- const prefix = manifest.prefix !== void 0 ? manifest.prefix : manifest.id;
2903
+ const prefix = manifest.prefix !== void 0 ? manifest.prefix : manifest.app_id;
2904
+ const space = manifest.space ?? DEFAULT_MANIFEST_SPACE;
2902
2905
  const expiryMs = parseExpiry(manifest.expiry ?? DEFAULT_EXPIRY);
2903
2906
  const includePublicSpace = manifest.includePublicSpace ?? true;
2904
2907
  const tier = normalizeDefaults(manifest.defaults);
@@ -2906,29 +2909,27 @@ function resolveManifest(input) {
2906
2909
  const explicitEntries = manifest.permissions ?? [];
2907
2910
  const allEntries = [...defaultEntries, ...explicitEntries];
2908
2911
  const resources = allEntries.map(
2909
- (entry) => resolveEntry(entry, prefix, expiryMs)
2912
+ (entry) => resolveEntry(entry, prefix, expiryMs, space)
2910
2913
  );
2911
- const additionalDelegates = (manifest.delegations ?? []).map((d) => ({
2912
- did: d.to,
2913
- name: d.name,
2914
- expiryMs: parseExpiry(d.expiry ?? manifest.expiry ?? DEFAULT_EXPIRY),
2915
- permissions: d.permissions.map(
2916
- (entry) => resolveEntry(
2917
- entry,
2918
- prefix,
2919
- parseExpiry(d.expiry ?? manifest.expiry ?? DEFAULT_EXPIRY)
2920
- )
2921
- )
2922
- }));
2914
+ const additionalDelegates = manifest.did === void 0 ? [] : [
2915
+ {
2916
+ did: manifest.did,
2917
+ name: manifest.name,
2918
+ expiryMs,
2919
+ permissions: resources.map(cloneResourceCapability)
2920
+ }
2921
+ ];
2923
2922
  return {
2924
- id: manifest.id,
2923
+ app_id: manifest.app_id,
2924
+ ...manifest.did !== void 0 ? { did: manifest.did } : {},
2925
+ space,
2925
2926
  resources,
2926
2927
  expiryMs,
2927
2928
  includePublicSpace,
2928
2929
  additionalDelegates
2929
2930
  };
2930
2931
  }
2931
- function resolveEntry(entry, prefix, _inheritedExpiryMs) {
2932
+ function resolveEntry(entry, prefix, _inheritedExpiryMs, inheritedSpace) {
2932
2933
  const resolvedPath = applyPrefix(
2933
2934
  prefix,
2934
2935
  entry.path,
@@ -2938,13 +2939,110 @@ function resolveEntry(entry, prefix, _inheritedExpiryMs) {
2938
2939
  const entryExpiryMs = entry.expiry !== void 0 ? parseExpiry(entry.expiry) : void 0;
2939
2940
  return {
2940
2941
  service: entry.service,
2941
- space: entry.space,
2942
+ space: entry.space ?? inheritedSpace,
2942
2943
  path: resolvedPath,
2943
2944
  actions: resolvedActions,
2944
2945
  // Only populate `expiryMs` when the entry had its own expiry override.
2945
2946
  // When absent, callers use the parent (delegation or manifest) expiry
2946
2947
  // which is carried on ResolvedDelegate.expiryMs / ResolvedCapabilities.expiryMs.
2947
- ...entryExpiryMs !== void 0 ? { expiryMs: entryExpiryMs } : {}
2948
+ ...entryExpiryMs !== void 0 ? { expiryMs: entryExpiryMs } : {},
2949
+ ...entry.description !== void 0 ? { description: entry.description } : {}
2950
+ };
2951
+ }
2952
+ function cloneResourceCapability(entry) {
2953
+ return {
2954
+ service: entry.service,
2955
+ space: entry.space,
2956
+ path: entry.path,
2957
+ actions: [...entry.actions],
2958
+ ...entry.expiryMs !== void 0 ? { expiryMs: entry.expiryMs } : {},
2959
+ ...entry.description !== void 0 ? { description: entry.description } : {}
2960
+ };
2961
+ }
2962
+ function clonePermissionEntry(entry) {
2963
+ return {
2964
+ service: entry.service,
2965
+ ...entry.space !== void 0 ? { space: entry.space } : {},
2966
+ path: entry.path,
2967
+ actions: [...entry.actions],
2968
+ ...entry.skipPrefix !== void 0 ? { skipPrefix: entry.skipPrefix } : {},
2969
+ ...entry.expiry !== void 0 ? { expiry: entry.expiry } : {},
2970
+ ...entry.description !== void 0 ? { description: entry.description } : {}
2971
+ };
2972
+ }
2973
+ function dedupeResources(resources) {
2974
+ const byKey = /* @__PURE__ */ new Map();
2975
+ for (const resource of resources) {
2976
+ const key = `${resource.service}\0${resource.space}\0${resource.path}\0${resource.expiryMs ?? ""}`;
2977
+ const existing = byKey.get(key);
2978
+ if (existing === void 0) {
2979
+ byKey.set(key, cloneResourceCapability(resource));
2980
+ continue;
2981
+ }
2982
+ const seen = new Set(existing.actions);
2983
+ for (const action of resource.actions) {
2984
+ if (!seen.has(action)) {
2985
+ existing.actions.push(action);
2986
+ seen.add(action);
2987
+ }
2988
+ }
2989
+ if (existing.description === void 0 && resource.description !== void 0) {
2990
+ existing.description = resource.description;
2991
+ }
2992
+ }
2993
+ return [...byKey.values()];
2994
+ }
2995
+ function accountRegistryPermission() {
2996
+ return {
2997
+ service: "tinycloud.kv",
2998
+ space: ACCOUNT_REGISTRY_SPACE,
2999
+ path: ACCOUNT_REGISTRY_PATH,
3000
+ actions: ["tinycloud.kv/get", "tinycloud.kv/put", "tinycloud.kv/list"]
3001
+ };
3002
+ }
3003
+ function composeManifestRequest(inputs, options = {}) {
3004
+ if (!Array.isArray(inputs) || inputs.length === 0) {
3005
+ throw new ManifestValidationError(
3006
+ "composeManifestRequest requires at least one manifest"
3007
+ );
3008
+ }
3009
+ const includeAccountRegistryPermissions = options.includeAccountRegistryPermissions ?? true;
3010
+ const manifests = inputs.map(validateManifest);
3011
+ const resolved = manifests.map(resolveManifest);
3012
+ const resources = resolved.flatMap((entry) => entry.resources);
3013
+ const delegationTargets = resolved.flatMap(
3014
+ (entry) => entry.additionalDelegates.map((delegate) => ({
3015
+ ...delegate,
3016
+ permissions: dedupeResources(delegate.permissions)
3017
+ }))
3018
+ );
3019
+ if (includeAccountRegistryPermissions) {
3020
+ resources.push(accountRegistryPermission());
3021
+ }
3022
+ const manifestsByAppId = /* @__PURE__ */ new Map();
3023
+ for (const manifest of manifests) {
3024
+ const current = manifestsByAppId.get(manifest.app_id);
3025
+ if (current === void 0) {
3026
+ manifestsByAppId.set(manifest.app_id, [manifest]);
3027
+ } else {
3028
+ current.push(manifest);
3029
+ }
3030
+ }
3031
+ const registryRecords = includeAccountRegistryPermissions ? [...manifestsByAppId.entries()].map(([app_id, appManifests]) => ({
3032
+ key: `${ACCOUNT_REGISTRY_PATH}${app_id}`,
3033
+ app_id,
3034
+ manifests: appManifests.map((manifest) => ({
3035
+ ...manifest,
3036
+ permissions: manifest.permissions?.map(clonePermissionEntry)
3037
+ }))
3038
+ })) : [];
3039
+ return {
3040
+ manifests,
3041
+ resources: dedupeResources(resources),
3042
+ delegationTargets,
3043
+ registryRecords,
3044
+ expiryMs: Math.max(...resolved.map((entry) => entry.expiryMs)),
3045
+ includePublicSpace: resolved.some((entry) => entry.includePublicSpace)
2948
3046
  };
2949
3047
  }
2950
3048
  function resourceCapabilitiesToAbilitiesMap(resources) {
@@ -2975,6 +3073,22 @@ function resourceCapabilitiesToAbilitiesMap(resources) {
2975
3073
  }
2976
3074
  return out;
2977
3075
  }
3076
+ function resourceCapabilitiesToSpaceAbilitiesMap(resources) {
3077
+ const grouped = /* @__PURE__ */ new Map();
3078
+ for (const resource of resources) {
3079
+ const entries = grouped.get(resource.space);
3080
+ if (entries === void 0) {
3081
+ grouped.set(resource.space, [resource]);
3082
+ } else {
3083
+ entries.push(resource);
3084
+ }
3085
+ }
3086
+ const out = {};
3087
+ for (const [space, entries] of grouped.entries()) {
3088
+ out[space] = resourceCapabilitiesToAbilitiesMap(entries);
3089
+ }
3090
+ return out;
3091
+ }
2978
3092
  function manifestAbilitiesUnion(resolved) {
2979
3093
  const all = [...resolved.resources];
2980
3094
  for (const delegate of resolved.additionalDelegates) {
@@ -4179,7 +4293,7 @@ function canonicalizeEntryMatches(requested, granted) {
4179
4293
  if (requested.service !== granted.service) {
4180
4294
  return false;
4181
4295
  }
4182
- if (normalizeSpace(requested.space) !== normalizeSpace(granted.space)) {
4296
+ if (normalizeSpace(requested.space ?? DEFAULT_MANIFEST_SPACE) !== normalizeSpace(granted.space ?? DEFAULT_MANIFEST_SPACE)) {
4183
4297
  return false;
4184
4298
  }
4185
4299
  if (!pathContains(granted.path, requested.path)) {
@@ -4210,7 +4324,7 @@ function pathContains(grantedPath, requestedPath) {
4210
4324
  function cloneEntry(entry) {
4211
4325
  return {
4212
4326
  service: entry.service,
4213
- space: entry.space,
4327
+ ...entry.space !== void 0 ? { space: entry.space } : {},
4214
4328
  path: entry.path,
4215
4329
  actions: [...entry.actions],
4216
4330
  ...entry.skipPrefix !== void 0 ? { skipPrefix: entry.skipPrefix } : {},
@@ -4240,7 +4354,9 @@ function parseRecapCapabilities(parseWasm, siwe) {
4240
4354
  };
4241
4355
  });
4242
4356
  normalized.sort((a, b) => {
4243
- if (a.space !== b.space) return a.space < b.space ? -1 : 1;
4357
+ const aSpace = a.space ?? DEFAULT_MANIFEST_SPACE;
4358
+ const bSpace = b.space ?? DEFAULT_MANIFEST_SPACE;
4359
+ if (aSpace !== bSpace) return aSpace < bSpace ? -1 : 1;
4244
4360
  if (a.service !== b.service) return a.service < b.service ? -1 : 1;
4245
4361
  if (a.path !== b.path) return a.path < b.path ? -1 : 1;
4246
4362
  return 0;
@@ -4248,12 +4364,16 @@ function parseRecapCapabilities(parseWasm, siwe) {
4248
4364
  return normalized;
4249
4365
  }
4250
4366
  export {
4367
+ ACCOUNT_REGISTRY_PATH,
4368
+ ACCOUNT_REGISTRY_SPACE,
4251
4369
  AutoApproveSpaceCreationHandler,
4252
4370
  CapabilityKeyRegistry,
4253
4371
  CapabilityKeyRegistryErrorCodes,
4254
4372
  ClientSessionSchema,
4255
4373
  DEFAULT_DEFAULTS,
4256
4374
  DEFAULT_EXPIRY,
4375
+ DEFAULT_MANIFEST_SPACE,
4376
+ DEFAULT_MANIFEST_VERSION,
4257
4377
  DataVaultService,
4258
4378
  DatabaseHandle,
4259
4379
  DelegationErrorCodes,
@@ -4291,6 +4411,7 @@ export {
4291
4411
  applyPrefix,
4292
4412
  buildSpaceUri,
4293
4413
  checkNodeInfo,
4414
+ composeManifestRequest,
4294
4415
  createCapabilityKeyRegistry,
4295
4416
  createSharingService,
4296
4417
  createSpaceService,
@@ -4312,6 +4433,7 @@ export {
4312
4433
  parseSpaceUri,
4313
4434
  resolveManifest,
4314
4435
  resourceCapabilitiesToAbilitiesMap,
4436
+ resourceCapabilitiesToSpaceAbilitiesMap,
4315
4437
  serviceError4 as serviceError,
4316
4438
  submitHostDelegation,
4317
4439
  validateClientSession,