@tinycloud/sdk-core 2.1.0-beta.0 → 2.1.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
@@ -3733,11 +3733,404 @@ async function checkNodeInfo(host, sdkProtocol, fetchFn = globalThis.fetch.bind(
3733
3733
  quotaUrl: data.quota_url
3734
3734
  };
3735
3735
  }
3736
+
3737
+ // src/manifest.ts
3738
+ import ms from "ms";
3739
+ var ManifestValidationError = class extends Error {
3740
+ constructor(message) {
3741
+ super(`Manifest validation failed: ${message}`);
3742
+ this.name = "ManifestValidationError";
3743
+ }
3744
+ };
3745
+ var DEFAULT_EXPIRY = "30d";
3746
+ var DEFAULT_DEFAULTS = true;
3747
+ var SERVICE_SHORT_TO_LONG = Object.freeze({
3748
+ kv: "tinycloud.kv",
3749
+ sql: "tinycloud.sql",
3750
+ duckdb: "tinycloud.duckdb",
3751
+ capabilities: "tinycloud.capabilities",
3752
+ hooks: "tinycloud.hooks"
3753
+ });
3754
+ var SERVICE_LONG_TO_SHORT = Object.freeze(
3755
+ Object.fromEntries(
3756
+ Object.entries(SERVICE_SHORT_TO_LONG).map(([s, l]) => [l, s])
3757
+ )
3758
+ );
3759
+ var DEFAULT_STANDARD_ENTRIES = [
3760
+ {
3761
+ service: "tinycloud.kv",
3762
+ space: "default",
3763
+ path: "/",
3764
+ actions: ["get", "put", "del", "list", "metadata"]
3765
+ },
3766
+ {
3767
+ service: "tinycloud.sql",
3768
+ space: "default",
3769
+ path: "/",
3770
+ actions: ["read", "write"]
3771
+ },
3772
+ {
3773
+ service: "tinycloud.capabilities",
3774
+ space: "default",
3775
+ path: "/",
3776
+ actions: ["read"]
3777
+ }
3778
+ ];
3779
+ var DEFAULT_ADMIN_ENTRIES = [
3780
+ {
3781
+ service: "tinycloud.kv",
3782
+ space: "default",
3783
+ path: "/",
3784
+ actions: ["get", "put", "del", "list", "metadata"]
3785
+ },
3786
+ {
3787
+ service: "tinycloud.sql",
3788
+ space: "default",
3789
+ path: "/",
3790
+ actions: ["read", "write", "ddl"]
3791
+ },
3792
+ {
3793
+ service: "tinycloud.capabilities",
3794
+ space: "default",
3795
+ path: "/",
3796
+ actions: ["read", "admin"]
3797
+ }
3798
+ ];
3799
+ var DEFAULT_ALL_ENTRIES = [
3800
+ {
3801
+ service: "tinycloud.kv",
3802
+ space: "default",
3803
+ path: "/",
3804
+ actions: ["get", "put", "del", "list", "metadata"]
3805
+ },
3806
+ {
3807
+ service: "tinycloud.sql",
3808
+ space: "default",
3809
+ path: "/",
3810
+ actions: ["read", "write", "ddl"]
3811
+ },
3812
+ {
3813
+ service: "tinycloud.duckdb",
3814
+ space: "default",
3815
+ path: "/",
3816
+ actions: ["read", "write"]
3817
+ },
3818
+ {
3819
+ service: "tinycloud.capabilities",
3820
+ space: "default",
3821
+ path: "/",
3822
+ actions: ["read", "admin"]
3823
+ }
3824
+ ];
3825
+ function parseExpiry(duration) {
3826
+ if (typeof duration !== "string" || duration.length === 0) {
3827
+ throw new ManifestValidationError(
3828
+ `expiry must be a non-empty duration string (got ${JSON.stringify(duration)})`
3829
+ );
3830
+ }
3831
+ const parsed = ms(
3832
+ duration
3833
+ );
3834
+ if (typeof parsed !== "number" || !Number.isFinite(parsed) || parsed <= 0) {
3835
+ throw new ManifestValidationError(
3836
+ `invalid expiry duration: ${JSON.stringify(duration)}`
3837
+ );
3838
+ }
3839
+ return parsed;
3840
+ }
3841
+ function expandActionShortNames(service, actions) {
3842
+ return actions.map((a) => {
3843
+ if (a.includes("/")) {
3844
+ return a;
3845
+ }
3846
+ return `${service}/${a}`;
3847
+ });
3848
+ }
3849
+ function applyPrefix(prefix, path, skipPrefix) {
3850
+ if (skipPrefix) {
3851
+ return path;
3852
+ }
3853
+ if (prefix === "") {
3854
+ return path;
3855
+ }
3856
+ if (path.startsWith("/")) {
3857
+ return `${prefix}${path}`;
3858
+ }
3859
+ return `${prefix}/${path}`;
3860
+ }
3861
+ async function loadManifest(url) {
3862
+ const fetchFn = globalThis.fetch;
3863
+ if (typeof fetchFn !== "function") {
3864
+ throw new ManifestValidationError(
3865
+ "loadManifest requires a global fetch; pass the manifest object directly on runtimes without fetch"
3866
+ );
3867
+ }
3868
+ const res = await fetchFn(url);
3869
+ if (!res.ok) {
3870
+ throw new ManifestValidationError(
3871
+ `failed to fetch manifest from ${url}: HTTP ${res.status}`
3872
+ );
3873
+ }
3874
+ const json = await res.json();
3875
+ return validateManifest(json);
3876
+ }
3877
+ function validateManifest(input) {
3878
+ if (input === null || typeof input !== "object") {
3879
+ throw new ManifestValidationError("manifest must be an object");
3880
+ }
3881
+ const m = input;
3882
+ if (typeof m.id !== "string" || m.id.length === 0) {
3883
+ throw new ManifestValidationError("manifest.id is required and must be a non-empty string");
3884
+ }
3885
+ if (typeof m.name !== "string" || m.name.length === 0) {
3886
+ throw new ManifestValidationError("manifest.name is required and must be a non-empty string");
3887
+ }
3888
+ if (m.expiry !== void 0) {
3889
+ parseExpiry(m.expiry);
3890
+ }
3891
+ if (m.permissions !== void 0) {
3892
+ if (!Array.isArray(m.permissions)) {
3893
+ throw new ManifestValidationError("manifest.permissions must be an array");
3894
+ }
3895
+ m.permissions.forEach(
3896
+ (p, i) => validatePermissionEntry(p, `permissions[${i}]`)
3897
+ );
3898
+ }
3899
+ if (m.delegations !== void 0) {
3900
+ if (!Array.isArray(m.delegations)) {
3901
+ throw new ManifestValidationError("manifest.delegations must be an array");
3902
+ }
3903
+ m.delegations.forEach((d, i) => {
3904
+ if (typeof d?.to !== "string" || d.to.length === 0) {
3905
+ throw new ManifestValidationError(
3906
+ `delegations[${i}].to is required and must be a non-empty DID string`
3907
+ );
3908
+ }
3909
+ if (d.expiry !== void 0) {
3910
+ parseExpiry(d.expiry);
3911
+ }
3912
+ if (!Array.isArray(d.permissions)) {
3913
+ throw new ManifestValidationError(
3914
+ `delegations[${i}].permissions must be an array`
3915
+ );
3916
+ }
3917
+ d.permissions.forEach(
3918
+ (p, j) => validatePermissionEntry(p, `delegations[${i}].permissions[${j}]`)
3919
+ );
3920
+ });
3921
+ }
3922
+ return m;
3923
+ }
3924
+ function validatePermissionEntry(p, path) {
3925
+ if (p === null || typeof p !== "object") {
3926
+ throw new ManifestValidationError(`${path} must be an object`);
3927
+ }
3928
+ const entry = p;
3929
+ if (typeof entry.service !== "string" || entry.service.length === 0) {
3930
+ throw new ManifestValidationError(`${path}.service is required`);
3931
+ }
3932
+ if (typeof entry.space !== "string" || entry.space.length === 0) {
3933
+ throw new ManifestValidationError(`${path}.space is required`);
3934
+ }
3935
+ if (typeof entry.path !== "string") {
3936
+ throw new ManifestValidationError(
3937
+ `${path}.path is required (use "" or "/" for root)`
3938
+ );
3939
+ }
3940
+ if (!Array.isArray(entry.actions) || entry.actions.length === 0) {
3941
+ throw new ManifestValidationError(
3942
+ `${path}.actions must be a non-empty array`
3943
+ );
3944
+ }
3945
+ if (entry.expiry !== void 0) {
3946
+ parseExpiry(entry.expiry);
3947
+ }
3948
+ }
3949
+ function normalizeDefaults(value) {
3950
+ if (value === void 0) {
3951
+ return DEFAULT_DEFAULTS;
3952
+ }
3953
+ if (typeof value === "boolean") {
3954
+ return value;
3955
+ }
3956
+ if (typeof value !== "string") {
3957
+ return true;
3958
+ }
3959
+ const normalized = value.trim().toLowerCase();
3960
+ if (normalized === "admin" || normalized === "all") {
3961
+ return normalized;
3962
+ }
3963
+ return true;
3964
+ }
3965
+ function defaultEntriesForTier(tier) {
3966
+ if (tier === false) {
3967
+ return [];
3968
+ }
3969
+ const source = tier === "admin" ? DEFAULT_ADMIN_ENTRIES : tier === "all" ? DEFAULT_ALL_ENTRIES : DEFAULT_STANDARD_ENTRIES;
3970
+ return source.map((e) => ({
3971
+ service: e.service,
3972
+ space: e.space,
3973
+ path: e.path,
3974
+ actions: [...e.actions]
3975
+ }));
3976
+ }
3977
+ function resolveManifest(input) {
3978
+ const manifest = validateManifest(input);
3979
+ const prefix = manifest.prefix !== void 0 ? manifest.prefix : manifest.id;
3980
+ const expiryMs = parseExpiry(manifest.expiry ?? DEFAULT_EXPIRY);
3981
+ const includePublicSpace = manifest.includePublicSpace ?? true;
3982
+ const tier = normalizeDefaults(manifest.defaults);
3983
+ const defaultEntries = defaultEntriesForTier(tier);
3984
+ const explicitEntries = manifest.permissions ?? [];
3985
+ const allEntries = [...defaultEntries, ...explicitEntries];
3986
+ const resources = allEntries.map(
3987
+ (entry) => resolveEntry(entry, prefix, expiryMs)
3988
+ );
3989
+ const additionalDelegates = (manifest.delegations ?? []).map((d) => ({
3990
+ did: d.to,
3991
+ name: d.name,
3992
+ expiryMs: parseExpiry(d.expiry ?? manifest.expiry ?? DEFAULT_EXPIRY),
3993
+ permissions: d.permissions.map(
3994
+ (entry) => resolveEntry(
3995
+ entry,
3996
+ prefix,
3997
+ parseExpiry(d.expiry ?? manifest.expiry ?? DEFAULT_EXPIRY)
3998
+ )
3999
+ )
4000
+ }));
4001
+ return {
4002
+ id: manifest.id,
4003
+ resources,
4004
+ expiryMs,
4005
+ includePublicSpace,
4006
+ additionalDelegates
4007
+ };
4008
+ }
4009
+ function resolveEntry(entry, prefix, _inheritedExpiryMs) {
4010
+ const resolvedPath = applyPrefix(
4011
+ prefix,
4012
+ entry.path,
4013
+ entry.skipPrefix === true
4014
+ );
4015
+ const resolvedActions = expandActionShortNames(entry.service, entry.actions);
4016
+ const entryExpiryMs = entry.expiry !== void 0 ? parseExpiry(entry.expiry) : void 0;
4017
+ return {
4018
+ service: entry.service,
4019
+ space: entry.space,
4020
+ path: resolvedPath,
4021
+ actions: resolvedActions,
4022
+ // Only populate `expiryMs` when the entry had its own expiry override.
4023
+ // When absent, callers use the parent (delegation or manifest) expiry
4024
+ // which is carried on ResolvedDelegate.expiryMs / ResolvedCapabilities.expiryMs.
4025
+ ...entryExpiryMs !== void 0 ? { expiryMs: entryExpiryMs } : {}
4026
+ };
4027
+ }
4028
+
4029
+ // src/capabilities.ts
4030
+ var PermissionNotInManifestError = class extends Error {
4031
+ constructor(missing, granted) {
4032
+ super(
4033
+ `Requested capabilities exceed current session. Missing ${missing.length} entries.`
4034
+ );
4035
+ this.name = "PermissionNotInManifestError";
4036
+ this.missing = missing;
4037
+ this.granted = granted;
4038
+ }
4039
+ };
4040
+ var SessionExpiredError = class extends Error {
4041
+ constructor(expiredAt) {
4042
+ super(`Session expired at ${expiredAt.toISOString()}`);
4043
+ this.name = "SessionExpiredError";
4044
+ this.expiredAt = expiredAt;
4045
+ }
4046
+ };
4047
+ function isCapabilitySubset(requested, granted) {
4048
+ const missing = [];
4049
+ for (const req of requested) {
4050
+ const match = granted.find((g) => canonicalizeEntryMatches(req, g));
4051
+ if (match === void 0) {
4052
+ missing.push(cloneEntry(req));
4053
+ continue;
4054
+ }
4055
+ }
4056
+ return { subset: missing.length === 0, missing };
4057
+ }
4058
+ function canonicalizeEntryMatches(requested, granted) {
4059
+ if (requested.service !== granted.service) {
4060
+ return false;
4061
+ }
4062
+ if (requested.space !== granted.space) {
4063
+ return false;
4064
+ }
4065
+ if (!pathContains(granted.path, requested.path)) {
4066
+ return false;
4067
+ }
4068
+ const reqActions = new Set(
4069
+ expandActionShortNames(requested.service, requested.actions)
4070
+ );
4071
+ const grantedActions = new Set(
4072
+ expandActionShortNames(granted.service, granted.actions)
4073
+ );
4074
+ for (const a of reqActions) {
4075
+ if (!grantedActions.has(a)) {
4076
+ return false;
4077
+ }
4078
+ }
4079
+ return true;
4080
+ }
4081
+ function pathContains(grantedPath, requestedPath) {
4082
+ if (grantedPath === "" || grantedPath === "/") {
4083
+ return true;
4084
+ }
4085
+ if (grantedPath.endsWith("/")) {
4086
+ return requestedPath.startsWith(grantedPath);
4087
+ }
4088
+ return requestedPath === grantedPath;
4089
+ }
4090
+ function cloneEntry(entry) {
4091
+ return {
4092
+ service: entry.service,
4093
+ space: entry.space,
4094
+ path: entry.path,
4095
+ actions: [...entry.actions],
4096
+ ...entry.skipPrefix !== void 0 ? { skipPrefix: entry.skipPrefix } : {},
4097
+ ...entry.expiry !== void 0 ? { expiry: entry.expiry } : {}
4098
+ };
4099
+ }
4100
+ function parseRecapCapabilities(parseWasm, siwe) {
4101
+ const raw = parseWasm(siwe);
4102
+ if (!Array.isArray(raw)) {
4103
+ throw new Error(
4104
+ "parseRecapFromSiwe returned a non-array value; wasm binding may be out of sync"
4105
+ );
4106
+ }
4107
+ const normalized = raw.map((entry) => {
4108
+ const longService = SERVICE_SHORT_TO_LONG[entry.service] ?? // Unknown short names pass through. If the recap already contained a
4109
+ // long-form service (e.g. a future tinycloud-node version emits long
4110
+ // form directly), don't double-prefix.
4111
+ (entry.service.startsWith("tinycloud.") ? entry.service : `tinycloud.${entry.service}`);
4112
+ return {
4113
+ service: longService,
4114
+ space: entry.space,
4115
+ path: entry.path,
4116
+ actions: [...entry.actions]
4117
+ };
4118
+ });
4119
+ normalized.sort((a, b) => {
4120
+ if (a.space !== b.space) return a.space < b.space ? -1 : 1;
4121
+ if (a.service !== b.service) return a.service < b.service ? -1 : 1;
4122
+ if (a.path !== b.path) return a.path < b.path ? -1 : 1;
4123
+ return 0;
4124
+ });
4125
+ return normalized;
4126
+ }
3736
4127
  export {
3737
4128
  AutoApproveSpaceCreationHandler,
3738
4129
  CapabilityKeyRegistry,
3739
4130
  CapabilityKeyRegistryErrorCodes,
3740
4131
  ClientSessionSchema,
4132
+ DEFAULT_DEFAULTS,
4133
+ DEFAULT_EXPIRY,
3741
4134
  DataVaultService,
3742
4135
  DatabaseHandle,
3743
4136
  DelegationErrorCodes,
@@ -3749,11 +4142,16 @@ export {
3749
4142
  ErrorCodes2 as ErrorCodes,
3750
4143
  HooksService2 as HooksService,
3751
4144
  KVService2 as KVService,
4145
+ ManifestValidationError,
4146
+ PermissionNotInManifestError,
3752
4147
  PrefixedKVService,
3753
4148
  ProtocolMismatchError,
4149
+ SERVICE_LONG_TO_SHORT,
4150
+ SERVICE_SHORT_TO_LONG,
3754
4151
  SQLAction,
3755
4152
  SQLService2 as SQLService,
3756
4153
  ServiceContext2 as ServiceContext,
4154
+ SessionExpiredError,
3757
4155
  SharingService,
3758
4156
  SilentNotificationHandler,
3759
4157
  SiweConfigSchema,
@@ -3767,6 +4165,7 @@ export {
3767
4165
  VaultPublicSpaceKVActions,
3768
4166
  VersionCheckError,
3769
4167
  activateSessionWithHost,
4168
+ applyPrefix,
3770
4169
  buildSpaceUri,
3771
4170
  checkNodeInfo,
3772
4171
  createCapabilityKeyRegistry,
@@ -3777,13 +4176,21 @@ export {
3777
4176
  defaultSignStrategy,
3778
4177
  defaultSpaceCreationHandler,
3779
4178
  err4 as err,
4179
+ expandActionShortNames,
3780
4180
  fetchPeerId,
4181
+ isCapabilitySubset,
4182
+ loadManifest,
3781
4183
  makePublicSpaceId,
4184
+ normalizeDefaults,
3782
4185
  ok4 as ok,
4186
+ parseExpiry,
4187
+ parseRecapCapabilities,
3783
4188
  parseSpaceUri,
4189
+ resolveManifest,
3784
4190
  serviceError4 as serviceError,
3785
4191
  submitHostDelegation,
3786
4192
  validateClientSession,
4193
+ validateManifest,
3787
4194
  validatePersistedSessionData
3788
4195
  };
3789
4196
  //# sourceMappingURL=index.js.map