@tinycloud/sdk-services 3.11.0 → 3.12.0-beta.4
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/{BaseService-Ma2MwJAd.d.ts → BaseService-BRDG69KR.d.ts} +1 -1
- package/dist/{BaseService-O0iyuFLk.d.cts → BaseService-CdBkfPGE.d.cts} +1 -1
- package/dist/{IHooksService-4QNODVy6.d.cts → IHooksService-C9p3stsB.d.cts} +1 -1
- package/dist/{IHooksService-CvTpJbQD.d.ts → IHooksService-D6eyZat1.d.ts} +1 -1
- package/dist/{ISQLService-Aly1CBsW.d.ts → ISQLService-B_4KcGA-.d.ts} +1 -1
- package/dist/{ISQLService-BOgWzHLT.d.cts → ISQLService-C6nNpcls.d.cts} +1 -1
- package/dist/context.cjs +4 -0
- package/dist/context.cjs.map +1 -1
- package/dist/context.d.cts +1 -1
- package/dist/context.d.ts +1 -1
- package/dist/context.js +4 -0
- package/dist/context.js.map +1 -1
- package/dist/encryption/index.cjs +60 -3
- package/dist/encryption/index.cjs.map +1 -1
- package/dist/encryption/index.d.cts +12 -22
- package/dist/encryption/index.d.ts +12 -22
- package/dist/encryption/index.js +60 -3
- package/dist/encryption/index.js.map +1 -1
- package/dist/encryption/named-keys.cjs +2 -0
- package/dist/encryption/named-keys.cjs.map +1 -1
- package/dist/encryption/named-keys.d.cts +2 -2
- package/dist/encryption/named-keys.d.ts +2 -2
- package/dist/encryption/named-keys.js +2 -0
- package/dist/encryption/named-keys.js.map +1 -1
- package/dist/encryption/resource.cjs.map +1 -1
- package/dist/encryption/resource.js.map +1 -1
- package/dist/feed/index.cjs +4 -0
- package/dist/feed/index.cjs.map +1 -1
- package/dist/feed/index.d.cts +3 -3
- package/dist/feed/index.d.ts +3 -3
- package/dist/feed/index.js +4 -0
- package/dist/feed/index.js.map +1 -1
- package/dist/index.cjs +270 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -12
- package/dist/index.d.ts +14 -12
- package/dist/index.js +263 -7
- package/dist/index.js.map +1 -1
- package/dist/internal/decrypt-transport-response-error.d.cts +1 -1
- package/dist/internal/decrypt-transport-response-error.d.ts +1 -1
- package/dist/kv/index.cjs +34 -0
- package/dist/kv/index.cjs.map +1 -1
- package/dist/kv/index.d.cts +4 -4
- package/dist/kv/index.d.ts +4 -4
- package/dist/kv/index.js +34 -0
- package/dist/kv/index.js.map +1 -1
- package/dist/kv/replication/index.cjs +34 -0
- package/dist/kv/replication/index.cjs.map +1 -1
- package/dist/kv/replication/index.d.cts +3 -3
- package/dist/kv/replication/index.d.ts +3 -3
- package/dist/kv/replication/index.js +34 -0
- package/dist/kv/replication/index.js.map +1 -1
- package/dist/{scope-DEgc9f-W.d.cts → scope-DtcbyRKY.d.cts} +1 -1
- package/dist/{scope-BZr0QwxX.d.ts → scope-J20PQP3u.d.ts} +1 -1
- package/dist/sql/index.cjs +36 -0
- package/dist/sql/index.cjs.map +1 -1
- package/dist/sql/index.d.cts +4 -4
- package/dist/sql/index.d.ts +4 -4
- package/dist/sql/index.js +36 -0
- package/dist/sql/index.js.map +1 -1
- package/dist/telemetry/requestLog.cjs +4 -0
- package/dist/telemetry/requestLog.cjs.map +1 -1
- package/dist/telemetry/requestLog.d.cts +1 -1
- package/dist/telemetry/requestLog.d.ts +1 -1
- package/dist/telemetry/requestLog.js +4 -0
- package/dist/telemetry/requestLog.js.map +1 -1
- package/dist/{types-CSd0RYir.d.ts → types-DjAP8dve.d.cts} +4 -0
- package/dist/{types-CSd0RYir.d.cts → types-DjAP8dve.d.ts} +4 -0
- package/dist/{types-D4XbIfFJ.d.cts → types-XE4tyBLH.d.ts} +60 -3
- package/dist/{types-Bf91gup6.d.ts → types-cWtQmCCw.d.cts} +60 -3
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -51,7 +51,11 @@ __export(src_exports, {
|
|
|
51
51
|
MAX_SECRET_SCOPE_LENGTH: () => MAX_SECRET_SCOPE_LENGTH,
|
|
52
52
|
NETWORK_NAME_PATTERN: () => NETWORK_NAME_PATTERN,
|
|
53
53
|
NetworkIdError: () => NetworkIdError,
|
|
54
|
+
NodeIdentityMismatchError: () => NodeIdentityMismatchError,
|
|
55
|
+
NodeNotRespondingError: () => NodeNotRespondingError,
|
|
56
|
+
PartialNodeWriteError: () => PartialNodeWriteError,
|
|
54
57
|
PrefixedKVService: () => PrefixedKVService,
|
|
58
|
+
ResourceNotRoutedError: () => ResourceNotRoutedError,
|
|
55
59
|
RetryPolicySchema: () => RetryPolicySchema,
|
|
56
60
|
SECRET_NAME_RE: () => SECRET_NAME_RE,
|
|
57
61
|
SECRET_SCOPE_RE: () => SECRET_SCOPE_RE,
|
|
@@ -99,6 +103,7 @@ __export(src_exports, {
|
|
|
99
103
|
checkDecryptInvocationInput: () => checkDecryptInvocationInput,
|
|
100
104
|
clearTinyCloudDebugLogs: () => clearTinyCloudDebugLogs,
|
|
101
105
|
createKVResponseSchema: () => createKVResponseSchema,
|
|
106
|
+
createResourceRoutingFetch: () => createResourceRoutingFetch,
|
|
102
107
|
createResultSchema: () => createResultSchema,
|
|
103
108
|
createVaultCrypto: () => createVaultCrypto,
|
|
104
109
|
decryptEnvelopeWithKey: () => decryptEnvelopeWithKey,
|
|
@@ -141,6 +146,7 @@ __export(src_exports, {
|
|
|
141
146
|
requiresSecretsOptIn: () => requiresSecretsOptIn,
|
|
142
147
|
resolveSecretListPrefix: () => resolveSecretListPrefix,
|
|
143
148
|
resolveSecretPath: () => resolveSecretPath,
|
|
149
|
+
routingFailure: () => routingFailure,
|
|
144
150
|
serviceError: () => serviceError,
|
|
145
151
|
storageLimitReachedError: () => storageLimitReachedError,
|
|
146
152
|
storageQuotaExceededError: () => storageQuotaExceededError,
|
|
@@ -161,6 +167,7 @@ __export(src_exports, {
|
|
|
161
167
|
validateTelemetrySpanEvent: () => validateTelemetrySpanEvent,
|
|
162
168
|
validatedCapabilityOf: () => validatedCapabilityOf,
|
|
163
169
|
verifyDecryptResponse: () => verifyDecryptResponse,
|
|
170
|
+
verifyResourceDestination: () => verifyResourceDestination,
|
|
164
171
|
wrapError: () => wrapError
|
|
165
172
|
});
|
|
166
173
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -177,6 +184,10 @@ var ErrorCodes = {
|
|
|
177
184
|
/** HTTP 401: a delegation the cited one rests on was revoked. */
|
|
178
185
|
AUTH_DELEGATION_ANCESTOR_REVOKED: "AUTH_DELEGATION_ANCESTOR_REVOKED",
|
|
179
186
|
NETWORK_ERROR: "NETWORK_ERROR",
|
|
187
|
+
NODE_NOT_RESPONDING: "NODE_NOT_RESPONDING",
|
|
188
|
+
NODE_IDENTITY_MISMATCH: "NODE_IDENTITY_MISMATCH",
|
|
189
|
+
PARTIAL_NODE_WRITE: "PARTIAL_NODE_WRITE",
|
|
190
|
+
RESOURCE_NOT_ROUTED: "RESOURCE_NOT_ROUTED",
|
|
180
191
|
TIMEOUT: "TIMEOUT",
|
|
181
192
|
ABORTED: "ABORTED",
|
|
182
193
|
INVALID_INPUT: "INVALID_INPUT",
|
|
@@ -951,6 +962,196 @@ function networkDiscoveryKey(name) {
|
|
|
951
962
|
var ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;
|
|
952
963
|
var NETWORK_NAME_PATTERN = NETWORK_NAME_RE;
|
|
953
964
|
|
|
965
|
+
// src/resourceRouting.ts
|
|
966
|
+
async function verifyResourceDestination(target, fetchFn) {
|
|
967
|
+
if (!target.nodeDid) return;
|
|
968
|
+
let info;
|
|
969
|
+
let keys;
|
|
970
|
+
try {
|
|
971
|
+
const signal = AbortSignal.timeout(5e3);
|
|
972
|
+
const responses = await Promise.all([
|
|
973
|
+
fetchFn(`${target.url}/info`, { signal, redirect: "error" }),
|
|
974
|
+
fetchFn(`${target.url}/.well-known/tinycloud/node-keys`, { signal, redirect: "error" })
|
|
975
|
+
]);
|
|
976
|
+
if (responses.some((response) => response.status >= 500)) throw new NodeNotRespondingError([target.url]);
|
|
977
|
+
if (responses.some((response) => !response.ok)) throw new NodeIdentityMismatchError(target.url, target.nodeDid);
|
|
978
|
+
try {
|
|
979
|
+
[info, keys] = await Promise.all(responses.map((response) => response.json()));
|
|
980
|
+
} catch {
|
|
981
|
+
throw new NodeIdentityMismatchError(target.url, target.nodeDid);
|
|
982
|
+
}
|
|
983
|
+
} catch (error) {
|
|
984
|
+
if (error instanceof NodeIdentityMismatchError || error instanceof NodeNotRespondingError) throw error;
|
|
985
|
+
throw new NodeNotRespondingError([target.url], error);
|
|
986
|
+
}
|
|
987
|
+
const infoDid = typeof info === "object" && info !== null && "nodeId" in info && typeof info.nodeId === "string" ? info.nodeId : void 0;
|
|
988
|
+
const keysDid = typeof keys === "object" && keys !== null && "nodeDid" in keys && typeof keys.nodeDid === "string" ? keys.nodeDid : void 0;
|
|
989
|
+
if (infoDid !== target.nodeDid || keysDid !== target.nodeDid) {
|
|
990
|
+
throw new NodeIdentityMismatchError(target.url, target.nodeDid, infoDid !== target.nodeDid ? infoDid : keysDid);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
var NodeNotRespondingError = class extends Error {
|
|
994
|
+
constructor(hosts, cause) {
|
|
995
|
+
super("The place your data is stored isn't responding right now. Please try again.");
|
|
996
|
+
this.hosts = hosts;
|
|
997
|
+
this.cause = cause;
|
|
998
|
+
this.code = "NODE_NOT_RESPONDING";
|
|
999
|
+
this.name = "NodeNotRespondingError";
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
var NodeIdentityMismatchError = class extends Error {
|
|
1003
|
+
constructor(url, expectedDid, actualDid) {
|
|
1004
|
+
super(`TinyCloud node identity does not match the registry at ${url}`);
|
|
1005
|
+
this.url = url;
|
|
1006
|
+
this.expectedDid = expectedDid;
|
|
1007
|
+
this.actualDid = actualDid;
|
|
1008
|
+
this.code = "NODE_IDENTITY_MISMATCH";
|
|
1009
|
+
this.name = "NodeIdentityMismatchError";
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
var ResourceNotRoutedError = class extends Error {
|
|
1013
|
+
constructor(resource) {
|
|
1014
|
+
super(`No registered destination can serve ${resource}`);
|
|
1015
|
+
this.resource = resource;
|
|
1016
|
+
this.code = "RESOURCE_NOT_ROUTED";
|
|
1017
|
+
this.name = "ResourceNotRoutedError";
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
var PartialNodeWriteError = class extends Error {
|
|
1021
|
+
constructor(succeeded, failed) {
|
|
1022
|
+
super("The write did not complete on every registered writable node.");
|
|
1023
|
+
this.succeeded = succeeded;
|
|
1024
|
+
this.failed = failed;
|
|
1025
|
+
this.code = "PARTIAL_NODE_WRITE";
|
|
1026
|
+
this.name = "PartialNodeWriteError";
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
var ROUTING_FAILURE_CODES = {
|
|
1030
|
+
NODE_NOT_RESPONDING: true,
|
|
1031
|
+
PARTIAL_NODE_WRITE: true,
|
|
1032
|
+
NODE_IDENTITY_MISMATCH: true,
|
|
1033
|
+
RESOURCE_NOT_ROUTED: true,
|
|
1034
|
+
DECRYPTION_DEPENDENCY_UNAVAILABLE: true,
|
|
1035
|
+
registry_unavailable: true,
|
|
1036
|
+
node_not_found: true,
|
|
1037
|
+
node_registry_entry_exists: true
|
|
1038
|
+
};
|
|
1039
|
+
function isRoutingFailureCode(code) {
|
|
1040
|
+
return Object.prototype.hasOwnProperty.call(ROUTING_FAILURE_CODES, code);
|
|
1041
|
+
}
|
|
1042
|
+
function routingFailure(error) {
|
|
1043
|
+
if (typeof error === "object" && error !== null && "code" in error && "message" in error && typeof error.code === "string" && typeof error.message === "string" && isRoutingFailureCode(error.code)) {
|
|
1044
|
+
const candidate = "meta" in error ? error.meta : void 0;
|
|
1045
|
+
let meta = typeof candidate === "object" && candidate !== null && !Array.isArray(candidate) ? candidate : void 0;
|
|
1046
|
+
if (error.code === "NODE_NOT_RESPONDING" && "hosts" in error && Array.isArray(error.hosts)) {
|
|
1047
|
+
meta = { ...meta, hosts: error.hosts };
|
|
1048
|
+
} else if (error.code === "PARTIAL_NODE_WRITE" && "succeeded" in error && Array.isArray(error.succeeded) && "failed" in error && Array.isArray(error.failed)) {
|
|
1049
|
+
meta = { ...meta, succeeded: error.succeeded, failed: error.failed };
|
|
1050
|
+
}
|
|
1051
|
+
return { code: error.code, message: error.message, ...error instanceof Error ? { cause: error } : {}, ...meta ? { meta } : {} };
|
|
1052
|
+
}
|
|
1053
|
+
return void 0;
|
|
1054
|
+
}
|
|
1055
|
+
function authorizationOf(headers) {
|
|
1056
|
+
if (headers === void 0) return void 0;
|
|
1057
|
+
return (Array.isArray(headers) ? headers : Object.entries(headers)).find(([name]) => name.toLowerCase() === "authorization")?.[1];
|
|
1058
|
+
}
|
|
1059
|
+
function invocationResources(authorization) {
|
|
1060
|
+
const encoded = authorization.split(".")[1];
|
|
1061
|
+
if (!encoded) throw new ResourceNotRoutedError("invalid invocation");
|
|
1062
|
+
const payload = JSON.parse(atob(encoded.replace(/-/g, "+").replace(/_/g, "/")));
|
|
1063
|
+
if (typeof payload !== "object" || payload === null || !("att" in payload) || typeof payload.att !== "object" || payload.att === null || Array.isArray(payload.att)) {
|
|
1064
|
+
throw new ResourceNotRoutedError("invalid invocation attenuation");
|
|
1065
|
+
}
|
|
1066
|
+
const resources = [];
|
|
1067
|
+
for (const [uri, abilities] of Object.entries(payload.att)) {
|
|
1068
|
+
if (typeof abilities !== "object" || abilities === null || Array.isArray(abilities)) throw new ResourceNotRoutedError(uri);
|
|
1069
|
+
for (const action of Object.keys(abilities)) {
|
|
1070
|
+
const resource = uri.startsWith("tinycloud:") ? uri.split("/")[0] : uri;
|
|
1071
|
+
if (!resource.startsWith("tinycloud:") && !resource.startsWith("urn:tinycloud:encryption:")) throw new ResourceNotRoutedError(uri);
|
|
1072
|
+
const read2 = /^(tinycloud\.(kv\/(get|list|metadata|head|sync)|sql\/(read|export)|duckdb\/(read|describe|export)|capabilities\/read|space\/(list|info)|hooks\/list))$/.test(action);
|
|
1073
|
+
const access = resource.startsWith("urn:tinycloud:encryption:") ? "encryption" : action === "tinycloud.space/host" ? "host" : read2 ? "read" : "write";
|
|
1074
|
+
if (!resources.some((entry) => entry.resource === resource && entry.access === access)) resources.push({ resource, access });
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
if (resources.length === 0) throw new ResourceNotRoutedError("empty invocation");
|
|
1078
|
+
return resources;
|
|
1079
|
+
}
|
|
1080
|
+
function createResourceRoutingFetch(fetchFn, routing) {
|
|
1081
|
+
return async (url, init) => {
|
|
1082
|
+
const parsed = new URL(url);
|
|
1083
|
+
if (parsed.pathname !== "/invoke") return fetchFn(url, init);
|
|
1084
|
+
const authorization = authorizationOf(init?.headers);
|
|
1085
|
+
if (!authorization) throw new ResourceNotRoutedError("unsigned invocation");
|
|
1086
|
+
const resources = invocationResources(authorization);
|
|
1087
|
+
const routes = await Promise.all(resources.map(({ resource, access }) => routing.resolveResource(resource, access)));
|
|
1088
|
+
if (routes.some((route) => route.length === 0)) throw new ResourceNotRoutedError(resources[routes.findIndex((route) => route.length === 0)].resource);
|
|
1089
|
+
const write = resources.some(({ access }) => access === "write" || access === "host");
|
|
1090
|
+
let targets = routes[0].filter((target) => routes.every((route) => route.some((other) => other.url === target.url && other.nodeDid === target.nodeDid)));
|
|
1091
|
+
if (targets.length === 0 || write && routes.some((route) => route.length !== targets.length)) throw new ResourceNotRoutedError(resources.map(({ resource }) => resource).join(", "));
|
|
1092
|
+
targets = targets.filter((target, index) => targets.findIndex((other) => other.url === target.url) === index);
|
|
1093
|
+
const send = async (target) => {
|
|
1094
|
+
init?.signal?.throwIfAborted();
|
|
1095
|
+
if (routing.verifyDestination) await routing.verifyDestination(target);
|
|
1096
|
+
else await verifyResourceDestination(target, fetchFn);
|
|
1097
|
+
const signed = target.nodeDid ? await routing.bindAudience(authorization, target.nodeDid) : authorization;
|
|
1098
|
+
const headers = (Array.isArray(init?.headers) ? init.headers : Object.entries(init?.headers ?? {})).filter(([name]) => name.toLowerCase() !== "authorization");
|
|
1099
|
+
headers.push(["Authorization", signed]);
|
|
1100
|
+
const timeout = AbortSignal.timeout(5e3);
|
|
1101
|
+
const signal = init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout;
|
|
1102
|
+
try {
|
|
1103
|
+
return await fetchFn(`${target.url}${parsed.pathname}${parsed.search}`, { ...init, headers, signal, redirect: "error" });
|
|
1104
|
+
} catch (error) {
|
|
1105
|
+
if (init?.signal?.aborted) throw error;
|
|
1106
|
+
if (routingFailure(error)) throw error;
|
|
1107
|
+
throw new NodeNotRespondingError([target.url], error);
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
if (!write) {
|
|
1111
|
+
let response2;
|
|
1112
|
+
let failure;
|
|
1113
|
+
for (const target of targets) {
|
|
1114
|
+
try {
|
|
1115
|
+
response2 = await send(target);
|
|
1116
|
+
if (response2.ok || response2.status !== 404) return response2;
|
|
1117
|
+
} catch (error) {
|
|
1118
|
+
if (!(error instanceof NodeNotRespondingError)) throw error;
|
|
1119
|
+
failure = error;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
if (failure) throw new NodeNotRespondingError(targets.map(({ url: url2 }) => url2), failure);
|
|
1123
|
+
if (response2) return response2;
|
|
1124
|
+
throw new NodeNotRespondingError(targets.map(({ url: url2 }) => url2), failure);
|
|
1125
|
+
}
|
|
1126
|
+
const succeeded = [];
|
|
1127
|
+
const failed = [];
|
|
1128
|
+
let response;
|
|
1129
|
+
let failureResponse;
|
|
1130
|
+
for (const target of targets) {
|
|
1131
|
+
try {
|
|
1132
|
+
const result = await send(target);
|
|
1133
|
+
if (result.ok) {
|
|
1134
|
+
succeeded.push(target.url);
|
|
1135
|
+
response ?? (response = result);
|
|
1136
|
+
} else {
|
|
1137
|
+
failed.push({ url: target.url, status: result.status });
|
|
1138
|
+
failureResponse ?? (failureResponse = result);
|
|
1139
|
+
}
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
if (!routingFailure(error) && succeeded.length === 0) throw error;
|
|
1142
|
+
failed.push({ url: target.url, cause: error });
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
if (failed.length > 0) {
|
|
1146
|
+
if (succeeded.length > 0 || targets.length > 1) throw new PartialNodeWriteError(succeeded, failed);
|
|
1147
|
+
if (failureResponse) return failureResponse;
|
|
1148
|
+
if (failed[0]?.cause && !(failed[0].cause instanceof NodeNotRespondingError)) throw failed[0].cause;
|
|
1149
|
+
throw new NodeNotRespondingError(targets.map(({ url: url2 }) => url2), failed[0]?.cause);
|
|
1150
|
+
}
|
|
1151
|
+
return response;
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
|
|
954
1155
|
// src/errors.ts
|
|
955
1156
|
function parsePermissionHint(value) {
|
|
956
1157
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
@@ -1256,6 +1457,8 @@ function isStorageFullError(error) {
|
|
|
1256
1457
|
return error?.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error?.code === ErrorCodes.STORAGE_LIMIT_REACHED;
|
|
1257
1458
|
}
|
|
1258
1459
|
function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
|
|
1460
|
+
const routing = routingFailure(error);
|
|
1461
|
+
if (routing) return { ...routing, service };
|
|
1259
1462
|
if (error instanceof Error) {
|
|
1260
1463
|
if (isRequestTimeoutError(error)) {
|
|
1261
1464
|
return timeoutError(service, error.timeoutMs, error);
|
|
@@ -6197,6 +6400,8 @@ function defaultVaultMessage(input) {
|
|
|
6197
6400
|
case "STORAGE_QUOTA_EXCEEDED":
|
|
6198
6401
|
case "STORAGE_LIMIT_REACHED":
|
|
6199
6402
|
return input.message;
|
|
6403
|
+
default:
|
|
6404
|
+
return input.message ?? input.code;
|
|
6200
6405
|
}
|
|
6201
6406
|
}
|
|
6202
6407
|
function vaultError(input) {
|
|
@@ -6248,6 +6453,8 @@ function ifMatchWriteError(key, error) {
|
|
|
6248
6453
|
});
|
|
6249
6454
|
}
|
|
6250
6455
|
function kvWriteError(context, error) {
|
|
6456
|
+
const routing = routingFailure(error);
|
|
6457
|
+
if (routing) return vaultError(routing);
|
|
6251
6458
|
if (error.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error.code === ErrorCodes.STORAGE_LIMIT_REACHED) {
|
|
6252
6459
|
return vaultError({ code: error.code, message: error.message, meta: error.meta });
|
|
6253
6460
|
}
|
|
@@ -6554,6 +6761,8 @@ var DataVaultService = class extends BaseService {
|
|
|
6554
6761
|
{ aad, metadata }
|
|
6555
6762
|
);
|
|
6556
6763
|
if (!envelopeResult.ok) {
|
|
6764
|
+
const routing = routingFailure(envelopeResult.error);
|
|
6765
|
+
if (routing) return vaultError(routing);
|
|
6557
6766
|
return vaultError({
|
|
6558
6767
|
code: "STORAGE_ERROR",
|
|
6559
6768
|
cause: new Error(envelopeResult.error.message)
|
|
@@ -6580,6 +6789,8 @@ var DataVaultService = class extends BaseService {
|
|
|
6580
6789
|
}
|
|
6581
6790
|
return { ok: true, data: void 0 };
|
|
6582
6791
|
} catch (error) {
|
|
6792
|
+
const routing = routingFailure(error);
|
|
6793
|
+
if (routing) return vaultError(routing);
|
|
6583
6794
|
return vaultError({
|
|
6584
6795
|
code: "STORAGE_ERROR",
|
|
6585
6796
|
cause: toError(error)
|
|
@@ -6606,6 +6817,8 @@ var DataVaultService = class extends BaseService {
|
|
|
6606
6817
|
...options?.source === void 0 ? {} : { source: options.source }
|
|
6607
6818
|
});
|
|
6608
6819
|
if (!valueResult.ok) {
|
|
6820
|
+
const routing = routingFailure(valueResult.error);
|
|
6821
|
+
if (routing) return vaultError(routing);
|
|
6609
6822
|
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
6610
6823
|
}
|
|
6611
6824
|
const rawEnvelope = unwrapKVData(valueResult.data);
|
|
@@ -6626,6 +6839,8 @@ var DataVaultService = class extends BaseService {
|
|
|
6626
6839
|
const proof = await this.decryptCapabilityProof();
|
|
6627
6840
|
const plaintextResult = await config.service.decryptEnvelope(envelope, proof);
|
|
6628
6841
|
if (!plaintextResult.ok) {
|
|
6842
|
+
const routing = routingFailure(plaintextResult.error);
|
|
6843
|
+
if (routing) return vaultError(routing);
|
|
6629
6844
|
return vaultError({
|
|
6630
6845
|
code: "DECRYPTION_FAILED",
|
|
6631
6846
|
message: plaintextResult.error.message
|
|
@@ -6645,6 +6860,8 @@ var DataVaultService = class extends BaseService {
|
|
|
6645
6860
|
data: { value, metadata, keyId, ...etag === void 0 ? {} : { etag } }
|
|
6646
6861
|
};
|
|
6647
6862
|
} catch (error) {
|
|
6863
|
+
const routing = routingFailure(error);
|
|
6864
|
+
if (routing) return vaultError(routing);
|
|
6648
6865
|
return vaultError({
|
|
6649
6866
|
code: "STORAGE_ERROR",
|
|
6650
6867
|
cause: toError(error)
|
|
@@ -6657,6 +6874,8 @@ var DataVaultService = class extends BaseService {
|
|
|
6657
6874
|
* Unlike {@link getNetworkEncrypted}, this is intentionally message-free so
|
|
6658
6875
|
* an operation layer can distinguish an authorized absence from a failed
|
|
6659
6876
|
* read without receiving node, envelope, plaintext, or key material.
|
|
6877
|
+
* Typed routing failures reject unchanged rather than becoming an ordinary
|
|
6878
|
+
* read/decrypt failure; the operation boundary projects their safe code.
|
|
6660
6879
|
*/
|
|
6661
6880
|
async readNetworkEncrypted(key, options) {
|
|
6662
6881
|
const config = this.networkEncryption;
|
|
@@ -6666,10 +6885,12 @@ var DataVaultService = class extends BaseService {
|
|
|
6666
6885
|
let valueResult;
|
|
6667
6886
|
try {
|
|
6668
6887
|
valueResult = await this.tc.kv.get(`vault/${key}`, { raw: true, source: "network" });
|
|
6669
|
-
} catch {
|
|
6888
|
+
} catch (error) {
|
|
6889
|
+
if (routingFailure(error)) throw error;
|
|
6670
6890
|
return { status: "node_unreachable" };
|
|
6671
6891
|
}
|
|
6672
6892
|
if (!valueResult.ok) {
|
|
6893
|
+
if (routingFailure(valueResult.error)) throw valueResult.error;
|
|
6673
6894
|
const permissionHint = parsePermissionHint(valueResult.error.meta?.permissionHint);
|
|
6674
6895
|
if (valueResult.error.code === "AUTH_UNAUTHORIZED" && permissionHint !== void 0) {
|
|
6675
6896
|
return { status: "permission_required", hint: permissionHint };
|
|
@@ -6677,7 +6898,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6677
6898
|
if ((valueResult.error.code === "NOT_FOUND" || valueResult.error.code === "KV_NOT_FOUND") && !hasHttpResponse(valueResult.error)) {
|
|
6678
6899
|
return { status: "not_found" };
|
|
6679
6900
|
}
|
|
6680
|
-
if (valueResult.error.code === "NETWORK_ERROR" || valueResult.error.code === "TIMEOUT" || valueResult.error.code === "ABORTED") {
|
|
6901
|
+
if (valueResult.error.code === "NODE_NOT_RESPONDING" || valueResult.error.code === "NETWORK_ERROR" || valueResult.error.code === "TIMEOUT" || valueResult.error.code === "ABORTED") {
|
|
6681
6902
|
return hasHttpResponse(valueResult.error) ? { status: "read_failed" } : { status: "node_unreachable" };
|
|
6682
6903
|
}
|
|
6683
6904
|
return { status: "read_failed" };
|
|
@@ -6699,10 +6920,12 @@ var DataVaultService = class extends BaseService {
|
|
|
6699
6920
|
try {
|
|
6700
6921
|
const proof = await this.decryptCapabilityProof();
|
|
6701
6922
|
plaintextResult = await config.service.decryptEnvelope(envelope, proof);
|
|
6702
|
-
} catch {
|
|
6923
|
+
} catch (error) {
|
|
6924
|
+
if (routingFailure(error)) throw error;
|
|
6703
6925
|
return { status: "decrypt_failed" };
|
|
6704
6926
|
}
|
|
6705
6927
|
if (!plaintextResult.ok) {
|
|
6928
|
+
if (routingFailure(plaintextResult.error)) throw plaintextResult.error;
|
|
6706
6929
|
const permissionHint = parsePermissionHint(
|
|
6707
6930
|
plaintextResult.error.permissionHint
|
|
6708
6931
|
);
|
|
@@ -6712,7 +6935,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6712
6935
|
if (plaintextResult.error.code === "INVALID_ENVELOPE") {
|
|
6713
6936
|
return { status: "corrupt_envelope" };
|
|
6714
6937
|
}
|
|
6715
|
-
if (plaintextResult.error.code === "TRANSPORT_ERROR") {
|
|
6938
|
+
if (plaintextResult.error.code === "NODE_NOT_RESPONDING" || plaintextResult.error.code === "TRANSPORT_ERROR") {
|
|
6716
6939
|
return { status: "node_unreachable" };
|
|
6717
6940
|
}
|
|
6718
6941
|
return { status: "decrypt_failed" };
|
|
@@ -6964,6 +7187,8 @@ var DataVaultService = class extends BaseService {
|
|
|
6964
7187
|
if (this.usesNetworkEncryption) {
|
|
6965
7188
|
const valueDelResult2 = await this.tc.kv.delete(`vault/${key}`);
|
|
6966
7189
|
if (!valueDelResult2.ok) {
|
|
7190
|
+
const routing = routingFailure(valueDelResult2.error);
|
|
7191
|
+
if (routing) return vaultError(routing);
|
|
6967
7192
|
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
6968
7193
|
}
|
|
6969
7194
|
return ok(void 0);
|
|
@@ -6973,10 +7198,14 @@ var DataVaultService = class extends BaseService {
|
|
|
6973
7198
|
this.tc.kv.delete(`vault/${key}`)
|
|
6974
7199
|
]);
|
|
6975
7200
|
if (!keyDelResult.ok && !valueDelResult.ok) {
|
|
7201
|
+
const routing = routingFailure(keyDelResult.error) ?? routingFailure(valueDelResult.error);
|
|
7202
|
+
if (routing) return vaultError(routing);
|
|
6976
7203
|
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
6977
7204
|
}
|
|
6978
7205
|
return ok(void 0);
|
|
6979
7206
|
} catch (error) {
|
|
7207
|
+
const routing = routingFailure(error);
|
|
7208
|
+
if (routing) return vaultError(routing);
|
|
6980
7209
|
return vaultError({
|
|
6981
7210
|
code: "STORAGE_ERROR",
|
|
6982
7211
|
cause: toError(error)
|
|
@@ -7018,6 +7247,8 @@ var DataVaultService = class extends BaseService {
|
|
|
7018
7247
|
...options?.cursor === void 0 ? {} : { cursor: options.cursor }
|
|
7019
7248
|
});
|
|
7020
7249
|
if (!listResult.ok) {
|
|
7250
|
+
const routing = routingFailure(listResult.error);
|
|
7251
|
+
if (routing) return vaultError(routing);
|
|
7021
7252
|
return vaultError({
|
|
7022
7253
|
code: "STORAGE_ERROR",
|
|
7023
7254
|
cause: new Error(
|
|
@@ -7038,6 +7269,8 @@ var DataVaultService = class extends BaseService {
|
|
|
7038
7269
|
...listResult.data.nextCursor === void 0 ? {} : { nextCursor: listResult.data.nextCursor }
|
|
7039
7270
|
});
|
|
7040
7271
|
} catch (error) {
|
|
7272
|
+
const routing = routingFailure(error);
|
|
7273
|
+
if (routing) return vaultError(routing);
|
|
7041
7274
|
return vaultError({
|
|
7042
7275
|
code: "STORAGE_ERROR",
|
|
7043
7276
|
cause: toError(error)
|
|
@@ -7869,6 +8102,8 @@ function defaultEncryptionMessage(input) {
|
|
|
7869
8102
|
return input.message ?? input.cause.message;
|
|
7870
8103
|
case "INVALID_INPUT":
|
|
7871
8104
|
return input.message;
|
|
8105
|
+
default:
|
|
8106
|
+
return input.message ?? input.code;
|
|
7872
8107
|
}
|
|
7873
8108
|
}
|
|
7874
8109
|
function encryptionError(input) {
|
|
@@ -7933,6 +8168,8 @@ async function discoverNetwork(input) {
|
|
|
7933
8168
|
return { ok: true, data: { descriptor: validated.data, source: "node" } };
|
|
7934
8169
|
}
|
|
7935
8170
|
} catch (err3) {
|
|
8171
|
+
const routing = routingFailure(err3);
|
|
8172
|
+
if (routing) return { ok: false, error: encryptionError(routing) };
|
|
7936
8173
|
}
|
|
7937
8174
|
}
|
|
7938
8175
|
if (input.wellKnown !== void 0) {
|
|
@@ -7950,6 +8187,8 @@ async function discoverNetwork(input) {
|
|
|
7950
8187
|
};
|
|
7951
8188
|
}
|
|
7952
8189
|
} catch (err3) {
|
|
8190
|
+
const routing = routingFailure(err3);
|
|
8191
|
+
if (routing) return { ok: false, error: encryptionError(routing) };
|
|
7953
8192
|
}
|
|
7954
8193
|
}
|
|
7955
8194
|
return {
|
|
@@ -8534,6 +8773,14 @@ function encOk(data) {
|
|
|
8534
8773
|
function encErr(error) {
|
|
8535
8774
|
return { ok: false, error };
|
|
8536
8775
|
}
|
|
8776
|
+
function decryptionError(input) {
|
|
8777
|
+
const unavailable = input.code === "NODE_NOT_RESPONDING" || input.code === "TRANSPORT_ERROR" && input.cause.name !== "AbortError";
|
|
8778
|
+
return encryptionError(unavailable ? {
|
|
8779
|
+
...input,
|
|
8780
|
+
code: "DECRYPTION_DEPENDENCY_UNAVAILABLE",
|
|
8781
|
+
message: "The decryption service isn't responding right now. Please try again."
|
|
8782
|
+
} : input);
|
|
8783
|
+
}
|
|
8537
8784
|
var EncryptionService = class extends BaseService {
|
|
8538
8785
|
constructor(config) {
|
|
8539
8786
|
super();
|
|
@@ -8581,6 +8828,8 @@ var EncryptionService = class extends BaseService {
|
|
|
8581
8828
|
});
|
|
8582
8829
|
return encOk(result.envelope);
|
|
8583
8830
|
} catch (error) {
|
|
8831
|
+
const routing = routingFailure(error);
|
|
8832
|
+
if (routing) return encErr(encryptionError(routing));
|
|
8584
8833
|
return encErr(
|
|
8585
8834
|
encryptionError({
|
|
8586
8835
|
code: "TRANSPORT_ERROR",
|
|
@@ -8607,12 +8856,12 @@ var EncryptionService = class extends BaseService {
|
|
|
8607
8856
|
descriptor = options.descriptor;
|
|
8608
8857
|
} else {
|
|
8609
8858
|
const discovered = await this.discoverNetwork(envelope.networkId);
|
|
8610
|
-
if (!discovered.ok) return discovered;
|
|
8859
|
+
if (!discovered.ok) return encErr(decryptionError(discovered.error));
|
|
8611
8860
|
descriptor = discovered.data;
|
|
8612
8861
|
}
|
|
8613
8862
|
const usable = ensureNetworkUsableForDecrypt(descriptor);
|
|
8614
8863
|
if (!usable.ok) return usable;
|
|
8615
|
-
const targetNode = options?.targetNode ?? descriptor.members[0]?.nodeId;
|
|
8864
|
+
const targetNode = this._config.resolveTargetNode ? await this._config.resolveTargetNode(envelope.networkId) : options?.targetNode ?? descriptor.members[0]?.nodeId;
|
|
8616
8865
|
if (targetNode === void 0) {
|
|
8617
8866
|
return encErr(
|
|
8618
8867
|
encryptionError({
|
|
@@ -8621,6 +8870,9 @@ var EncryptionService = class extends BaseService {
|
|
|
8621
8870
|
})
|
|
8622
8871
|
);
|
|
8623
8872
|
}
|
|
8873
|
+
if (this._config.resolveTargetNode && (options?.targetNode !== void 0 && options.targetNode !== targetNode || !descriptor.members.some((member) => member.nodeId === targetNode))) {
|
|
8874
|
+
return encErr(encryptionError({ code: "INVALID_INPUT", message: "Encryption destination does not match the registry" }));
|
|
8875
|
+
}
|
|
8624
8876
|
const receiverKey = generateRandomReceiverKey({ crypto: this.crypto });
|
|
8625
8877
|
const receiverPublicKey = base64Encode(receiverKey.publicKey);
|
|
8626
8878
|
const receiverPublicKeyHash = canonicalHashHex(
|
|
@@ -8679,6 +8931,8 @@ var EncryptionService = class extends BaseService {
|
|
|
8679
8931
|
});
|
|
8680
8932
|
this.assertActive();
|
|
8681
8933
|
} catch (error) {
|
|
8934
|
+
const routing = routingFailure(error);
|
|
8935
|
+
if (routing) return encErr(decryptionError(routing));
|
|
8682
8936
|
if (error instanceof import_decrypt_transport_response_error.DecryptTransportResponseError) {
|
|
8683
8937
|
return encErr(
|
|
8684
8938
|
encryptionError(
|
|
@@ -8695,7 +8949,7 @@ var EncryptionService = class extends BaseService {
|
|
|
8695
8949
|
);
|
|
8696
8950
|
}
|
|
8697
8951
|
return encErr(
|
|
8698
|
-
|
|
8952
|
+
decryptionError({
|
|
8699
8953
|
code: "TRANSPORT_ERROR",
|
|
8700
8954
|
cause: toError2(error)
|
|
8701
8955
|
})
|
|
@@ -8722,6 +8976,8 @@ var EncryptionService = class extends BaseService {
|
|
|
8722
8976
|
);
|
|
8723
8977
|
return encOk(plaintext);
|
|
8724
8978
|
} catch (error) {
|
|
8979
|
+
const routing = routingFailure(error);
|
|
8980
|
+
if (routing) return encErr(decryptionError(routing));
|
|
8725
8981
|
return encErr(
|
|
8726
8982
|
encryptionError({
|
|
8727
8983
|
code: "INVALID_RESPONSE",
|
|
@@ -8771,7 +9027,11 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
|
|
|
8771
9027
|
MAX_SECRET_SCOPE_LENGTH,
|
|
8772
9028
|
NETWORK_NAME_PATTERN,
|
|
8773
9029
|
NetworkIdError,
|
|
9030
|
+
NodeIdentityMismatchError,
|
|
9031
|
+
NodeNotRespondingError,
|
|
9032
|
+
PartialNodeWriteError,
|
|
8774
9033
|
PrefixedKVService,
|
|
9034
|
+
ResourceNotRoutedError,
|
|
8775
9035
|
RetryPolicySchema,
|
|
8776
9036
|
SECRET_NAME_RE,
|
|
8777
9037
|
SECRET_SCOPE_RE,
|
|
@@ -8819,6 +9079,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
|
|
|
8819
9079
|
checkDecryptInvocationInput,
|
|
8820
9080
|
clearTinyCloudDebugLogs,
|
|
8821
9081
|
createKVResponseSchema,
|
|
9082
|
+
createResourceRoutingFetch,
|
|
8822
9083
|
createResultSchema,
|
|
8823
9084
|
createVaultCrypto,
|
|
8824
9085
|
decryptEnvelopeWithKey,
|
|
@@ -8861,6 +9122,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
|
|
|
8861
9122
|
requiresSecretsOptIn,
|
|
8862
9123
|
resolveSecretListPrefix,
|
|
8863
9124
|
resolveSecretPath,
|
|
9125
|
+
routingFailure,
|
|
8864
9126
|
serviceError,
|
|
8865
9127
|
storageLimitReachedError,
|
|
8866
9128
|
storageQuotaExceededError,
|
|
@@ -8881,6 +9143,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
|
|
|
8881
9143
|
validateTelemetrySpanEvent,
|
|
8882
9144
|
validatedCapabilityOf,
|
|
8883
9145
|
verifyDecryptResponse,
|
|
9146
|
+
verifyResourceDestination,
|
|
8884
9147
|
wrapError
|
|
8885
9148
|
});
|
|
8886
9149
|
//# sourceMappingURL=index.cjs.map
|