@tinycloud/sdk-services 3.1.0-beta.1 → 3.1.0-beta.11
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-BhpVpA9b.d.cts → BaseService-B_UKhVht.d.ts} +74 -2
- package/dist/{BaseService-CuV-ZVPs.d.ts → BaseService-DWbPbov7.d.cts} +74 -2
- package/dist/encryption/index.cjs +199 -100
- package/dist/encryption/index.cjs.map +1 -1
- package/dist/encryption/index.d.cts +2 -2
- package/dist/encryption/index.d.ts +2 -2
- package/dist/encryption/index.js +199 -100
- package/dist/encryption/index.js.map +1 -1
- package/dist/index.cjs +739 -258
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -42
- package/dist/index.d.ts +66 -42
- package/dist/index.js +714 -240
- 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 +568 -74
- package/dist/kv/index.cjs.map +1 -1
- package/dist/kv/index.d.cts +197 -15
- package/dist/kv/index.d.ts +197 -15
- package/dist/kv/index.js +568 -74
- package/dist/kv/index.js.map +1 -1
- package/dist/sql/index.cjs +191 -35
- package/dist/sql/index.cjs.map +1 -1
- package/dist/sql/index.d.cts +6 -3
- package/dist/sql/index.d.ts +6 -3
- package/dist/sql/index.js +182 -26
- package/dist/sql/index.js.map +1 -1
- package/dist/{types-qwMmB7qB.d.cts → types-CVwwxqXF.d.cts} +8 -0
- package/dist/{types-qwMmB7qB.d.ts → types-CVwwxqXF.d.ts} +8 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -5,6 +5,10 @@ var ErrorCodes = {
|
|
|
5
5
|
AUTH_EXPIRED: "AUTH_EXPIRED",
|
|
6
6
|
AUTH_REQUIRED: "AUTH_REQUIRED",
|
|
7
7
|
AUTH_UNAUTHORIZED: "AUTH_UNAUTHORIZED",
|
|
8
|
+
/** HTTP 401: the delegation the invocation cites was revoked. */
|
|
9
|
+
AUTH_DELEGATION_REVOKED: "AUTH_DELEGATION_REVOKED",
|
|
10
|
+
/** HTTP 401: a delegation the cited one rests on was revoked. */
|
|
11
|
+
AUTH_DELEGATION_ANCESTOR_REVOKED: "AUTH_DELEGATION_ANCESTOR_REVOKED",
|
|
8
12
|
NETWORK_ERROR: "NETWORK_ERROR",
|
|
9
13
|
TIMEOUT: "TIMEOUT",
|
|
10
14
|
ABORTED: "ABORTED",
|
|
@@ -16,6 +20,10 @@ var ErrorCodes = {
|
|
|
16
20
|
KV_PRECONDITION_FAILED: "KV_PRECONDITION_FAILED",
|
|
17
21
|
KV_CONFLICT: "KV_CONFLICT",
|
|
18
22
|
KV_RESPONSE_TOO_LARGE: "KV_RESPONSE_TOO_LARGE",
|
|
23
|
+
/** HTTP 410 from the change feed: discard sync state and restart without a cursor; `meta.reason` says why. */
|
|
24
|
+
KV_SYNC_RESET_REQUIRED: "KV_SYNC_RESET_REQUIRED",
|
|
25
|
+
/** HTTP 403 from the change feed: the retention grant was refused; `meta.reason` says why. */
|
|
26
|
+
KV_RETENTION_GRANT_REFUSED: "KV_RETENTION_GRANT_REFUSED",
|
|
19
27
|
// SQL-specific errors
|
|
20
28
|
SQL_ERROR: "SQL_ERROR",
|
|
21
29
|
SQL_PERMISSION_DENIED: "SQL_PERMISSION_DENIED",
|
|
@@ -896,6 +904,106 @@ var ServiceContext = class {
|
|
|
896
904
|
}
|
|
897
905
|
};
|
|
898
906
|
|
|
907
|
+
// src/errors.ts
|
|
908
|
+
import { CAPABILITY_REGISTRY } from "@tinycloud/bootstrap";
|
|
909
|
+
|
|
910
|
+
// src/encryption/networkId.ts
|
|
911
|
+
var URN_PREFIX = "urn:tinycloud:encryption:";
|
|
912
|
+
var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
913
|
+
var PKH_EIP155_DID_RE = /^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40})$/;
|
|
914
|
+
var NetworkIdError = class extends Error {
|
|
915
|
+
constructor(message) {
|
|
916
|
+
super(message);
|
|
917
|
+
this.name = "NetworkIdError";
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
function parseNetworkId(networkId) {
|
|
921
|
+
if (typeof networkId !== "string" || networkId.length === 0) {
|
|
922
|
+
throw new NetworkIdError("networkId must be a non-empty string");
|
|
923
|
+
}
|
|
924
|
+
if (!networkId.startsWith(URN_PREFIX)) {
|
|
925
|
+
throw new NetworkIdError(
|
|
926
|
+
`networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
const body = networkId.slice(URN_PREFIX.length);
|
|
930
|
+
const lastColon = body.lastIndexOf(":");
|
|
931
|
+
if (lastColon <= 0 || lastColon === body.length - 1) {
|
|
932
|
+
throw new NetworkIdError(
|
|
933
|
+
`networkId missing ownerDid or name segment (got ${JSON.stringify(networkId)})`
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
const ownerDid = body.slice(0, lastColon);
|
|
937
|
+
const name = body.slice(lastColon + 1);
|
|
938
|
+
if (!ownerDid.startsWith("did:")) {
|
|
939
|
+
throw new NetworkIdError(
|
|
940
|
+
`networkId ownerDid must be a DID (got ${JSON.stringify(ownerDid)})`
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
const didParts = ownerDid.split(":");
|
|
944
|
+
if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
|
|
945
|
+
throw new NetworkIdError(
|
|
946
|
+
`networkId ownerDid is not a well-formed DID (got ${JSON.stringify(ownerDid)})`
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
if (!NETWORK_NAME_RE.test(name)) {
|
|
950
|
+
throw new NetworkIdError(
|
|
951
|
+
`networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
return { networkId, ownerDid, name };
|
|
955
|
+
}
|
|
956
|
+
function buildNetworkId(ownerDid, name) {
|
|
957
|
+
if (typeof ownerDid !== "string" || !ownerDid.startsWith("did:")) {
|
|
958
|
+
throw new NetworkIdError("ownerDid must be a DID");
|
|
959
|
+
}
|
|
960
|
+
if (typeof name !== "string" || !NETWORK_NAME_RE.test(name)) {
|
|
961
|
+
throw new NetworkIdError(
|
|
962
|
+
`network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
const networkId = `${URN_PREFIX}${ownerDid}:${name}`;
|
|
966
|
+
parseNetworkId(networkId);
|
|
967
|
+
return networkId;
|
|
968
|
+
}
|
|
969
|
+
function isNetworkId(networkId) {
|
|
970
|
+
if (typeof networkId !== "string") {
|
|
971
|
+
return false;
|
|
972
|
+
}
|
|
973
|
+
try {
|
|
974
|
+
parseNetworkId(networkId);
|
|
975
|
+
return true;
|
|
976
|
+
} catch {
|
|
977
|
+
return false;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
function parsePkhOwnerDid(ownerDid) {
|
|
981
|
+
const match = ownerDid.match(PKH_EIP155_DID_RE);
|
|
982
|
+
if (!match) return null;
|
|
983
|
+
return {
|
|
984
|
+
chainId: match[1],
|
|
985
|
+
address: match[2].toLowerCase()
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
function ownerDidMatches(a, b) {
|
|
989
|
+
const aPkh = parsePkhOwnerDid(a);
|
|
990
|
+
const bPkh = parsePkhOwnerDid(b);
|
|
991
|
+
if (aPkh && bPkh) {
|
|
992
|
+
return aPkh.chainId === bPkh.chainId && aPkh.address === bPkh.address;
|
|
993
|
+
}
|
|
994
|
+
return a === b;
|
|
995
|
+
}
|
|
996
|
+
function networkDiscoveryKey(name) {
|
|
997
|
+
if (!NETWORK_NAME_RE.test(name)) {
|
|
998
|
+
throw new NetworkIdError(
|
|
999
|
+
`network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
return `.well-known/encryption/network/${name}`;
|
|
1003
|
+
}
|
|
1004
|
+
var ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;
|
|
1005
|
+
var NETWORK_NAME_PATTERN = NETWORK_NAME_RE;
|
|
1006
|
+
|
|
899
1007
|
// src/errors.ts
|
|
900
1008
|
function parsePermissionHint(value) {
|
|
901
1009
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
@@ -955,11 +1063,31 @@ function networkError(service, message, cause) {
|
|
|
955
1063
|
cause
|
|
956
1064
|
};
|
|
957
1065
|
}
|
|
958
|
-
|
|
1066
|
+
var RequestTimeoutError = class extends Error {
|
|
1067
|
+
constructor(timeoutMs) {
|
|
1068
|
+
super(`Request timed out after ${timeoutMs}ms.`);
|
|
1069
|
+
this.name = "TimeoutError";
|
|
1070
|
+
this.timeoutMs = timeoutMs;
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
function isRequestTimeoutError(error) {
|
|
1074
|
+
return error instanceof RequestTimeoutError || error instanceof Error && error.name === "TimeoutError" && typeof error.timeoutMs === "number";
|
|
1075
|
+
}
|
|
1076
|
+
function timeoutError(service, timeoutMs, cause) {
|
|
1077
|
+
if (timeoutMs === void 0) {
|
|
1078
|
+
return {
|
|
1079
|
+
code: ErrorCodes.TIMEOUT,
|
|
1080
|
+
message: "Request timed out.",
|
|
1081
|
+
service,
|
|
1082
|
+
...cause === void 0 ? {} : { cause }
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
959
1085
|
return {
|
|
960
1086
|
code: ErrorCodes.TIMEOUT,
|
|
961
|
-
message:
|
|
962
|
-
service
|
|
1087
|
+
message: `Request timed out after ${timeoutMs}ms.`,
|
|
1088
|
+
service,
|
|
1089
|
+
...cause === void 0 ? {} : { cause },
|
|
1090
|
+
meta: { timeoutMs }
|
|
963
1091
|
};
|
|
964
1092
|
}
|
|
965
1093
|
function abortedError(service) {
|
|
@@ -993,6 +1121,70 @@ function parseAuthError(responseText) {
|
|
|
993
1121
|
function authUnauthorizedError(service, message, meta) {
|
|
994
1122
|
return serviceError(ErrorCodes.AUTH_UNAUTHORIZED, message, service, { meta });
|
|
995
1123
|
}
|
|
1124
|
+
var grantableActions = new Set(
|
|
1125
|
+
CAPABILITY_REGISTRY.map(({ urn }) => urn).filter((urn) => !urn.endsWith("/*"))
|
|
1126
|
+
);
|
|
1127
|
+
var capabilityServices = new Set(
|
|
1128
|
+
CAPABILITY_REGISTRY.map(({ service }) => service.slice("tinycloud.".length))
|
|
1129
|
+
);
|
|
1130
|
+
function parseCapabilityResource(resource, service) {
|
|
1131
|
+
if (resource.length === 0 || resource.length > 1024 || !capabilityServices.has(service) || /[\s\p{C}*#]/u.test(resource) || resource.includes("://")) return void 0;
|
|
1132
|
+
const segments = resource.split("/");
|
|
1133
|
+
if (!/^tinycloud:[A-Za-z0-9][A-Za-z0-9:._-]*$/.test(segments[0])) return void 0;
|
|
1134
|
+
const serviceIndex = segments.findIndex(
|
|
1135
|
+
(segment, index) => index > 0 && capabilityServices.has(segment)
|
|
1136
|
+
);
|
|
1137
|
+
if (serviceIndex < 1 || segments[serviceIndex] !== service) return void 0;
|
|
1138
|
+
if (segments.slice(1, serviceIndex).some(
|
|
1139
|
+
(segment) => segment.length === 0 || segment === "." || segment === ".."
|
|
1140
|
+
)) return void 0;
|
|
1141
|
+
const pathSegments = segments.slice(serviceIndex + 1);
|
|
1142
|
+
if (pathSegments.some((segment) => segment === "." || segment === "..")) return void 0;
|
|
1143
|
+
if (service !== "kv" && (pathSegments.length === 0 || pathSegments[pathSegments.length - 1] === "")) return void 0;
|
|
1144
|
+
return {
|
|
1145
|
+
space: segments.slice(0, serviceIndex).join("/"),
|
|
1146
|
+
path: pathSegments.join("/")
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
function safeCapabilityResource(value, service, action) {
|
|
1150
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 1024) return false;
|
|
1151
|
+
if (service === "encryption" && value.startsWith("urn:tinycloud:encryption:")) {
|
|
1152
|
+
if (!/^[A-Za-z0-9_][A-Za-z0-9_.:/-]*$/.test(value) || value.includes("//") || value.split("/").some((segment) => segment === "." || segment === "..")) return false;
|
|
1153
|
+
try {
|
|
1154
|
+
parseNetworkId(value);
|
|
1155
|
+
return true;
|
|
1156
|
+
} catch {
|
|
1157
|
+
return false;
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
const parsed = parseCapabilityResource(value, service);
|
|
1161
|
+
return parsed !== void 0 && (!parsed.path.endsWith("/") || service === "kv" && action === "tinycloud.kv/list") && (parsed.path !== "" || service === "kv" && action === "tinycloud.kv/list");
|
|
1162
|
+
}
|
|
1163
|
+
function validatedCapabilityOf(error) {
|
|
1164
|
+
const verdict = authorizationVerdictOf(error);
|
|
1165
|
+
if (verdict !== "unauthenticated" && verdict !== "forbidden") return void 0;
|
|
1166
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1167
|
+
let node = error;
|
|
1168
|
+
for (let depth = 0; depth < MAX_CAUSE_DEPTH && typeof node === "object" && node !== null && !seen.has(node); depth += 1) {
|
|
1169
|
+
seen.add(node);
|
|
1170
|
+
const record = node;
|
|
1171
|
+
const status = errorStatusOf(record);
|
|
1172
|
+
const meta = record.meta;
|
|
1173
|
+
if ((status === 401 || status === 403 || status === void 0 && record.code === ErrorCodes.AUTH_UNAUTHORIZED) && typeof meta === "object" && meta !== null && !Array.isArray(meta)) {
|
|
1174
|
+
const metadata = meta;
|
|
1175
|
+
const { resource, requiredAction } = metadata;
|
|
1176
|
+
if (typeof requiredAction === "string" && grantableActions.has(requiredAction)) {
|
|
1177
|
+
const actionService = requiredAction.slice("tinycloud.".length, requiredAction.indexOf("/"));
|
|
1178
|
+
const explicitService = record.service === void 0 ? metadata.service : record.service;
|
|
1179
|
+
if (safeCapabilityResource(resource, actionService, requiredAction) && (explicitService === void 0 || explicitService === actionService || explicitService === `tinycloud.${actionService}`)) {
|
|
1180
|
+
return { resource, requiredAction };
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
node = record.cause;
|
|
1185
|
+
}
|
|
1186
|
+
return void 0;
|
|
1187
|
+
}
|
|
996
1188
|
var MAX_CAUSE_DEPTH = 8;
|
|
997
1189
|
function errorStatusOf(node) {
|
|
998
1190
|
const meta = node.meta;
|
|
@@ -1040,8 +1232,28 @@ function storageLimitReachedError(service, message, meta) {
|
|
|
1040
1232
|
meta
|
|
1041
1233
|
};
|
|
1042
1234
|
}
|
|
1235
|
+
var STORAGE_FULL_MESSAGE = "TinyCloud storage is full, so this change was not saved. Reading still works. Free up space or upgrade your plan to save again.";
|
|
1236
|
+
var STORAGE_WRITE_TOO_LARGE_MESSAGE = "This change is larger than the TinyCloud storage you have left, so it was not saved. Reading still works. Free up space or upgrade your plan to save it.";
|
|
1237
|
+
function parseStorageQuotaBytes(responseText) {
|
|
1238
|
+
const match = responseText.match(/Used:\s*(\d+)\s*bytes,\s*Limit:\s*(\d+)\s*bytes/i);
|
|
1239
|
+
if (!match) return void 0;
|
|
1240
|
+
return {
|
|
1241
|
+
usedBytes: parseInt(match[1], 10),
|
|
1242
|
+
limitBytes: parseInt(match[2], 10)
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
function storageRejectionError(service, status, meta, responseText) {
|
|
1246
|
+
const quotaMeta = { ...meta, ...parseStorageQuotaBytes(responseText) };
|
|
1247
|
+
return status === 402 ? storageQuotaExceededError(service, STORAGE_FULL_MESSAGE, quotaMeta) : storageLimitReachedError(service, STORAGE_WRITE_TOO_LARGE_MESSAGE, quotaMeta);
|
|
1248
|
+
}
|
|
1249
|
+
function isStorageFullError(error) {
|
|
1250
|
+
return error?.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error?.code === ErrorCodes.STORAGE_LIMIT_REACHED;
|
|
1251
|
+
}
|
|
1043
1252
|
function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
|
|
1044
1253
|
if (error instanceof Error) {
|
|
1254
|
+
if (isRequestTimeoutError(error)) {
|
|
1255
|
+
return timeoutError(service, error.timeoutMs, error);
|
|
1256
|
+
}
|
|
1045
1257
|
if (error.name === "AbortError") {
|
|
1046
1258
|
return abortedError(service);
|
|
1047
1259
|
}
|
|
@@ -1066,6 +1278,7 @@ function errorResult(error) {
|
|
|
1066
1278
|
}
|
|
1067
1279
|
|
|
1068
1280
|
// src/base/BaseService.ts
|
|
1281
|
+
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
1069
1282
|
var BaseService = class {
|
|
1070
1283
|
constructor() {
|
|
1071
1284
|
/**
|
|
@@ -1246,6 +1459,64 @@ var BaseService = class {
|
|
|
1246
1459
|
}
|
|
1247
1460
|
return controller.signal;
|
|
1248
1461
|
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Create the abort signal for one request, bounded by the request timeout.
|
|
1464
|
+
*
|
|
1465
|
+
* The signal aborts when the service signs out, the context aborts, the
|
|
1466
|
+
* caller's `signal` aborts, or the timeout elapses, whichever comes first.
|
|
1467
|
+
* A timeout aborts with a {@link RequestTimeoutError} (`TimeoutError`), which
|
|
1468
|
+
* `wrapError` maps to `ErrorCodes.TIMEOUT`; the other sources keep their own
|
|
1469
|
+
* reason (normally `AbortError`, mapped to `ErrorCodes.ABORTED`).
|
|
1470
|
+
*
|
|
1471
|
+
* The timeout is `timeoutMs` when given, otherwise `config.timeout`. Only a
|
|
1472
|
+
* positive, finite value (at most 2^31-1 ms) applies; `undefined`, `0`, and
|
|
1473
|
+
* `Infinity` mean no timeout, so a per-call `0` opts out of a configured one.
|
|
1474
|
+
*
|
|
1475
|
+
* Callers must call `dispose()` once the response body has been consumed,
|
|
1476
|
+
* which clears the timer and removes the listeners this adds to the
|
|
1477
|
+
* long-lived service and context signals.
|
|
1478
|
+
*
|
|
1479
|
+
* @param signal - The caller's abort signal for this request
|
|
1480
|
+
* @param timeoutMs - Per-request timeout override in milliseconds
|
|
1481
|
+
*/
|
|
1482
|
+
createRequestSignal(signal, timeoutMs) {
|
|
1483
|
+
const controller = new AbortController();
|
|
1484
|
+
const detachers = [];
|
|
1485
|
+
let timer;
|
|
1486
|
+
const dispose = () => {
|
|
1487
|
+
if (timer !== void 0) {
|
|
1488
|
+
clearTimeout(timer);
|
|
1489
|
+
timer = void 0;
|
|
1490
|
+
}
|
|
1491
|
+
for (const detach of detachers.splice(0)) {
|
|
1492
|
+
detach();
|
|
1493
|
+
}
|
|
1494
|
+
};
|
|
1495
|
+
const abort = (reason) => {
|
|
1496
|
+
dispose();
|
|
1497
|
+
controller.abort(reason);
|
|
1498
|
+
};
|
|
1499
|
+
const requestSignal = { signal: controller.signal, dispose };
|
|
1500
|
+
const parents = [
|
|
1501
|
+
this.abortController.signal,
|
|
1502
|
+
this.context?.abortSignal,
|
|
1503
|
+
signal
|
|
1504
|
+
].filter((parent) => parent !== void 0);
|
|
1505
|
+
for (const parent of parents) {
|
|
1506
|
+
if (parent.aborted) {
|
|
1507
|
+
abort(parent.reason);
|
|
1508
|
+
return requestSignal;
|
|
1509
|
+
}
|
|
1510
|
+
const onAbort = () => abort(parent.reason);
|
|
1511
|
+
parent.addEventListener("abort", onAbort, { once: true });
|
|
1512
|
+
detachers.push(() => parent.removeEventListener("abort", onAbort));
|
|
1513
|
+
}
|
|
1514
|
+
const timeout = timeoutMs ?? this._config.timeout;
|
|
1515
|
+
if (typeof timeout === "number" && timeout > 0 && timeout <= MAX_TIMER_DELAY_MS) {
|
|
1516
|
+
timer = setTimeout(() => abort(new RequestTimeoutError(timeout)), timeout);
|
|
1517
|
+
}
|
|
1518
|
+
return requestSignal;
|
|
1519
|
+
}
|
|
1249
1520
|
/**
|
|
1250
1521
|
* Wrap an operation with error handling and telemetry.
|
|
1251
1522
|
*
|
|
@@ -1354,6 +1625,30 @@ var PrefixedKVService = class _PrefixedKVService {
|
|
|
1354
1625
|
removePrefix
|
|
1355
1626
|
});
|
|
1356
1627
|
}
|
|
1628
|
+
/**
|
|
1629
|
+
* Read the change feed for everything under this prefix. A view with an
|
|
1630
|
+
* empty prefix (`""` or `"/"`) is refused: the node does not serve a
|
|
1631
|
+
* whole-space feed.
|
|
1632
|
+
*/
|
|
1633
|
+
async changes(options) {
|
|
1634
|
+
if (this._prefix.replace(/\/+$/, "") === "") {
|
|
1635
|
+
return err(serviceError(
|
|
1636
|
+
ErrorCodes.INVALID_INPUT,
|
|
1637
|
+
"KV changes needs a non-empty prefix; this view has none",
|
|
1638
|
+
"kv"
|
|
1639
|
+
));
|
|
1640
|
+
}
|
|
1641
|
+
const syncPrefix = `${this._prefix}/`;
|
|
1642
|
+
const response = await this._kv.changes({ ...options, prefix: syncPrefix });
|
|
1643
|
+
if (!response.ok) return response;
|
|
1644
|
+
return ok({
|
|
1645
|
+
...response.data,
|
|
1646
|
+
changes: response.data.changes.map((change) => ({
|
|
1647
|
+
...change,
|
|
1648
|
+
key: change.key.startsWith(syncPrefix) ? change.key.slice(syncPrefix.length) : change.key
|
|
1649
|
+
}))
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1357
1652
|
/**
|
|
1358
1653
|
* Delete a key.
|
|
1359
1654
|
*/
|
|
@@ -1406,11 +1701,24 @@ var KVAction = {
|
|
|
1406
1701
|
PUT: "tinycloud.kv/put",
|
|
1407
1702
|
LIST: "tinycloud.kv/list",
|
|
1408
1703
|
DELETE: "tinycloud.kv/del",
|
|
1409
|
-
HEAD: "tinycloud.kv/metadata"
|
|
1704
|
+
HEAD: "tinycloud.kv/metadata",
|
|
1705
|
+
/** The change feed; never implied by `*` or `tinycloud.kv/*`. */
|
|
1706
|
+
SYNC: "tinycloud.kv/sync",
|
|
1707
|
+
/** Retention attestation; presented through `retentionGrant`, never invoked or implied. */
|
|
1708
|
+
RETAIN: "tinycloud.kv/retain"
|
|
1410
1709
|
};
|
|
1411
1710
|
|
|
1412
1711
|
// src/kv/KVService.ts
|
|
1413
1712
|
var MAX_KV_BATCH_READ_ITEMS = 100;
|
|
1713
|
+
function isJsonObject(value) {
|
|
1714
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1715
|
+
}
|
|
1716
|
+
var REVOKED_BODY = /^(?:Invalid invocation: )?delegation-(?:(revoked): [A-Za-z0-9]+|(ancestor-revoked): ancestor=[A-Za-z0-9]+ invoked=[A-Za-z0-9]+)$/;
|
|
1717
|
+
function revocationCodeOf(body) {
|
|
1718
|
+
const match = REVOKED_BODY.exec(body.trim());
|
|
1719
|
+
if (match === null) return void 0;
|
|
1720
|
+
return match[1] !== void 0 ? ErrorCodes.AUTH_DELEGATION_REVOKED : ErrorCodes.AUTH_DELEGATION_ANCESTOR_REVOKED;
|
|
1721
|
+
}
|
|
1414
1722
|
function encodeKvBatchPartName(path) {
|
|
1415
1723
|
return encodeURIComponent(path).replace(
|
|
1416
1724
|
/[!'()*]/g,
|
|
@@ -1433,47 +1741,21 @@ var KVService = class extends BaseService {
|
|
|
1433
1741
|
get config() {
|
|
1434
1742
|
return this._config;
|
|
1435
1743
|
}
|
|
1436
|
-
// Parses "Used: X bytes, Limit: Y bytes" from tinycloud-node error responses
|
|
1437
|
-
parseQuotaInfo(errorText) {
|
|
1438
|
-
const match = errorText.match(
|
|
1439
|
-
/Used:\s*(\d+)\s*bytes,\s*Limit:\s*(\d+)\s*bytes/i
|
|
1440
|
-
);
|
|
1441
|
-
if (match) {
|
|
1442
|
-
return {
|
|
1443
|
-
usedBytes: parseInt(match[1], 10),
|
|
1444
|
-
limitBytes: parseInt(match[2], 10)
|
|
1445
|
-
};
|
|
1446
|
-
}
|
|
1447
|
-
return void 0;
|
|
1448
|
-
}
|
|
1449
1744
|
handleQuotaErrorResponse(response, errorText, key) {
|
|
1450
|
-
if (response.status
|
|
1451
|
-
|
|
1452
|
-
return err(
|
|
1453
|
-
storageQuotaExceededError(
|
|
1454
|
-
"kv",
|
|
1455
|
-
`Storage quota exceeded for key ${JSON.stringify(key)}: ${errorText}`,
|
|
1456
|
-
{
|
|
1457
|
-
status: response.status,
|
|
1458
|
-
...quotaInfo ? { usedBytes: quotaInfo.usedBytes, limitBytes: quotaInfo.limitBytes } : {}
|
|
1459
|
-
}
|
|
1460
|
-
)
|
|
1461
|
-
);
|
|
1745
|
+
if (response.status !== 402 && response.status !== 413) {
|
|
1746
|
+
return void 0;
|
|
1462
1747
|
}
|
|
1463
|
-
if (response.status === 413) {
|
|
1464
|
-
|
|
1465
|
-
return err(
|
|
1466
|
-
storageLimitReachedError(
|
|
1467
|
-
"kv",
|
|
1468
|
-
`Storage limit reached for key ${JSON.stringify(key)}: ${errorText}`,
|
|
1469
|
-
{
|
|
1470
|
-
status: response.status,
|
|
1471
|
-
...quotaInfo ? { usedBytes: quotaInfo.usedBytes, limitBytes: quotaInfo.limitBytes } : {}
|
|
1472
|
-
}
|
|
1473
|
-
)
|
|
1474
|
-
);
|
|
1748
|
+
if (response.status === 413 && !parseStorageQuotaBytes(errorText)) {
|
|
1749
|
+
return void 0;
|
|
1475
1750
|
}
|
|
1476
|
-
return
|
|
1751
|
+
return err(
|
|
1752
|
+
storageRejectionError(
|
|
1753
|
+
"kv",
|
|
1754
|
+
response.status,
|
|
1755
|
+
{ status: response.status, statusText: response.statusText, key },
|
|
1756
|
+
errorText
|
|
1757
|
+
)
|
|
1758
|
+
);
|
|
1477
1759
|
}
|
|
1478
1760
|
/**
|
|
1479
1761
|
* Classify a KV 404 by reading the response body once.
|
|
@@ -1532,7 +1814,7 @@ var KVService = class extends BaseService {
|
|
|
1532
1814
|
* @param path - Resource path
|
|
1533
1815
|
* @param action - KV action
|
|
1534
1816
|
* @param body - Optional request body
|
|
1535
|
-
* @param signal -
|
|
1817
|
+
* @param signal - Request signal from createRequestSignal()
|
|
1536
1818
|
* @returns Fetch response
|
|
1537
1819
|
*/
|
|
1538
1820
|
async invokeOperation(path, action, body, signal, extraHeaders) {
|
|
@@ -1548,7 +1830,7 @@ var KVService = class extends BaseService {
|
|
|
1548
1830
|
method: "POST",
|
|
1549
1831
|
headers: requestHeaders,
|
|
1550
1832
|
body,
|
|
1551
|
-
signal
|
|
1833
|
+
signal
|
|
1552
1834
|
});
|
|
1553
1835
|
}
|
|
1554
1836
|
/**
|
|
@@ -1725,6 +2007,7 @@ var KVService = class extends BaseService {
|
|
|
1725
2007
|
"kv"
|
|
1726
2008
|
));
|
|
1727
2009
|
}
|
|
2010
|
+
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
1728
2011
|
try {
|
|
1729
2012
|
const session = this.context.session;
|
|
1730
2013
|
const invocationHeaders = this.context.invokeAny(
|
|
@@ -1746,15 +2029,17 @@ var KVService = class extends BaseService {
|
|
|
1746
2029
|
const response = await this.context.fetch(`${this.host}/invoke`, {
|
|
1747
2030
|
method: "POST",
|
|
1748
2031
|
headers,
|
|
1749
|
-
signal:
|
|
2032
|
+
signal: request.signal
|
|
1750
2033
|
});
|
|
1751
2034
|
if (!response.ok) {
|
|
1752
|
-
const errorText = await response.text();
|
|
2035
|
+
const errorText = response.status === 401 || response.status === 403 ? await this.readAuthorizationText(response) : await response.text();
|
|
1753
2036
|
if (response.status === 401 || response.status === 403) {
|
|
1754
2037
|
return this.authorizationFailure(
|
|
1755
2038
|
`Failed to batch read ${keys.length} key(s)`,
|
|
1756
2039
|
response,
|
|
1757
|
-
errorText
|
|
2040
|
+
errorText,
|
|
2041
|
+
paths,
|
|
2042
|
+
action
|
|
1758
2043
|
);
|
|
1759
2044
|
}
|
|
1760
2045
|
if (response.status === 413) {
|
|
@@ -1809,6 +2094,8 @@ var KVService = class extends BaseService {
|
|
|
1809
2094
|
return ok({ results, count: results.length });
|
|
1810
2095
|
} catch (error) {
|
|
1811
2096
|
return err(wrapError("kv", error));
|
|
2097
|
+
} finally {
|
|
2098
|
+
request.dispose();
|
|
1812
2099
|
}
|
|
1813
2100
|
}
|
|
1814
2101
|
/**
|
|
@@ -1844,7 +2131,7 @@ var KVService = class extends BaseService {
|
|
|
1844
2131
|
return text;
|
|
1845
2132
|
}
|
|
1846
2133
|
}
|
|
1847
|
-
async createSignedReadUrlError(response, key) {
|
|
2134
|
+
async createSignedReadUrlError(response, key, path) {
|
|
1848
2135
|
let errorText = response.statusText;
|
|
1849
2136
|
try {
|
|
1850
2137
|
const text = await response.text();
|
|
@@ -1857,7 +2144,9 @@ var KVService = class extends BaseService {
|
|
|
1857
2144
|
return this.authorizationFailure(
|
|
1858
2145
|
`Failed to create signed read URL for key ${JSON.stringify(key)}`,
|
|
1859
2146
|
response,
|
|
1860
|
-
errorText
|
|
2147
|
+
errorText,
|
|
2148
|
+
[path],
|
|
2149
|
+
KVAction.GET
|
|
1861
2150
|
);
|
|
1862
2151
|
}
|
|
1863
2152
|
const code = response.status === 400 ? ErrorCodes.INVALID_INPUT : ErrorCodes.NETWORK_ERROR;
|
|
@@ -1870,21 +2159,42 @@ var KVService = class extends BaseService {
|
|
|
1870
2159
|
)
|
|
1871
2160
|
);
|
|
1872
2161
|
}
|
|
2162
|
+
/** A known 401/403 keeps its typed status even if reading its body fails. */
|
|
2163
|
+
async readAuthorizationText(response) {
|
|
2164
|
+
try {
|
|
2165
|
+
return await response.text();
|
|
2166
|
+
} catch {
|
|
2167
|
+
return "";
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
1873
2170
|
/**
|
|
1874
2171
|
* AUTH_UNAUTHORIZED for a 401/403 response. The message keeps the HTTP
|
|
1875
2172
|
* status and the server text (`<context>: <status> - <text>`, the same
|
|
1876
2173
|
* shape as every other KV failure) so callers that rethrow only the message
|
|
1877
2174
|
* still see the status; `meta.status` carries it for typed callers.
|
|
1878
|
-
*
|
|
2175
|
+
* Server hints must match the requested path/action (whether the node sends
|
|
2176
|
+
* a relative path or full resource); metadata uses only the request-derived
|
|
2177
|
+
* canonical resource under the authenticated session's space.
|
|
1879
2178
|
*/
|
|
1880
|
-
authorizationFailure(context, response, errorText, extraMeta = {}) {
|
|
2179
|
+
authorizationFailure(context, response, errorText, paths, expectedAction, extraMeta = {}) {
|
|
1881
2180
|
const { resource, action } = parseAuthError(errorText);
|
|
2181
|
+
const requestedPath = paths.find(
|
|
2182
|
+
(path) => resource === path || resource === `${this.context.session.spaceId}/kv/${path}`
|
|
2183
|
+
);
|
|
2184
|
+
const capability = requestedPath !== void 0 && action === expectedAction ? validatedCapabilityOf({
|
|
2185
|
+
service: "kv",
|
|
2186
|
+
code: ErrorCodes.AUTH_UNAUTHORIZED,
|
|
2187
|
+
meta: {
|
|
2188
|
+
status: response.status,
|
|
2189
|
+
resource: `${this.context.session.spaceId}/kv/${requestedPath}`,
|
|
2190
|
+
requiredAction: action
|
|
2191
|
+
}
|
|
2192
|
+
}) : void 0;
|
|
1882
2193
|
const detail = errorText.trim().length > 0 ? errorText : response.statusText || "authorization failed";
|
|
1883
2194
|
return err(authUnauthorizedError("kv", `${context}: ${response.status} - ${detail}`, {
|
|
1884
2195
|
status: response.status,
|
|
1885
|
-
...
|
|
1886
|
-
...
|
|
1887
|
-
...extraMeta
|
|
2196
|
+
...extraMeta,
|
|
2197
|
+
...capability
|
|
1888
2198
|
}));
|
|
1889
2199
|
}
|
|
1890
2200
|
normalizeSignedReadUrlResponse(data) {
|
|
@@ -1918,23 +2228,35 @@ var KVService = class extends BaseService {
|
|
|
1918
2228
|
));
|
|
1919
2229
|
}
|
|
1920
2230
|
const path = this.getFullPath(key, options?.prefix);
|
|
2231
|
+
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
1921
2232
|
try {
|
|
1922
2233
|
const response = await this.invokeOperation(
|
|
1923
2234
|
path,
|
|
1924
2235
|
KVAction.GET,
|
|
1925
2236
|
void 0,
|
|
1926
|
-
|
|
2237
|
+
request.signal,
|
|
1927
2238
|
options?.maxResponseBytes === void 0 ? void 0 : { "x-tinycloud-max-response-bytes": String(options.maxResponseBytes) }
|
|
1928
2239
|
);
|
|
1929
2240
|
if (!response.ok) {
|
|
1930
2241
|
if (response.status === 401 || response.status === 403) {
|
|
1931
|
-
const errorText2 = await
|
|
2242
|
+
const errorText2 = await this.readAuthorizationText(response);
|
|
1932
2243
|
const permissionHint = parsePermissionHintFromErrorText(errorText2);
|
|
2244
|
+
const structuredCapability = permissionHint?.service === "tinycloud.kv" && permissionHint.space === this.context.session.spaceId && permissionHint.path === path ? validatedCapabilityOf({
|
|
2245
|
+
service: "kv",
|
|
2246
|
+
code: ErrorCodes.AUTH_UNAUTHORIZED,
|
|
2247
|
+
meta: {
|
|
2248
|
+
status: response.status,
|
|
2249
|
+
resource: `${this.context.session.spaceId}/kv/${path}`,
|
|
2250
|
+
requiredAction: KVAction.GET
|
|
2251
|
+
}
|
|
2252
|
+
}) : void 0;
|
|
1933
2253
|
return this.authorizationFailure(
|
|
1934
2254
|
`Failed to get key ${JSON.stringify(key)}`,
|
|
1935
2255
|
response,
|
|
1936
2256
|
errorText2,
|
|
1937
|
-
|
|
2257
|
+
[path],
|
|
2258
|
+
KVAction.GET,
|
|
2259
|
+
structuredCapability ? { permissionHint, ...structuredCapability } : {}
|
|
1938
2260
|
);
|
|
1939
2261
|
}
|
|
1940
2262
|
if (response.status === 404) {
|
|
@@ -1969,6 +2291,8 @@ var KVService = class extends BaseService {
|
|
|
1969
2291
|
});
|
|
1970
2292
|
} catch (error) {
|
|
1971
2293
|
return err(wrapError("kv", error));
|
|
2294
|
+
} finally {
|
|
2295
|
+
request.dispose();
|
|
1972
2296
|
}
|
|
1973
2297
|
});
|
|
1974
2298
|
}
|
|
@@ -1996,12 +2320,13 @@ var KVService = class extends BaseService {
|
|
|
1996
2320
|
}
|
|
1997
2321
|
const path = this.getFullPath(key, options?.prefix);
|
|
1998
2322
|
const body = this.serializePutValue(value, options?.contentType);
|
|
2323
|
+
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
1999
2324
|
try {
|
|
2000
2325
|
const response = await this.invokeOperation(
|
|
2001
2326
|
path,
|
|
2002
2327
|
KVAction.PUT,
|
|
2003
2328
|
body,
|
|
2004
|
-
|
|
2329
|
+
request.signal,
|
|
2005
2330
|
{
|
|
2006
2331
|
...options?.ifMatch === void 0 ? {} : { "if-match": options.ifMatch },
|
|
2007
2332
|
...options?.ifNoneMatch === void 0 ? {} : { "if-none-match": options.ifNoneMatch }
|
|
@@ -2011,7 +2336,9 @@ var KVService = class extends BaseService {
|
|
|
2011
2336
|
return this.authorizationFailure(
|
|
2012
2337
|
`Failed to put key ${JSON.stringify(key)}`,
|
|
2013
2338
|
response,
|
|
2014
|
-
await
|
|
2339
|
+
await this.readAuthorizationText(response),
|
|
2340
|
+
[path],
|
|
2341
|
+
KVAction.PUT
|
|
2015
2342
|
);
|
|
2016
2343
|
}
|
|
2017
2344
|
if (!response.ok) {
|
|
@@ -2055,6 +2382,8 @@ var KVService = class extends BaseService {
|
|
|
2055
2382
|
});
|
|
2056
2383
|
} catch (error) {
|
|
2057
2384
|
return err(wrapError("kv", error));
|
|
2385
|
+
} finally {
|
|
2386
|
+
request.dispose();
|
|
2058
2387
|
}
|
|
2059
2388
|
});
|
|
2060
2389
|
}
|
|
@@ -2094,6 +2423,7 @@ var KVService = class extends BaseService {
|
|
|
2094
2423
|
seen.add(path);
|
|
2095
2424
|
}
|
|
2096
2425
|
let requestMayHaveDispatched = false;
|
|
2426
|
+
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
2097
2427
|
try {
|
|
2098
2428
|
const body = new FormData();
|
|
2099
2429
|
for (let index = 0; index < items.length; index++) {
|
|
@@ -2116,7 +2446,7 @@ var KVService = class extends BaseService {
|
|
|
2116
2446
|
method: "POST",
|
|
2117
2447
|
headers,
|
|
2118
2448
|
body,
|
|
2119
|
-
signal:
|
|
2449
|
+
signal: request.signal
|
|
2120
2450
|
};
|
|
2121
2451
|
const fetchFn = this.context.fetch;
|
|
2122
2452
|
requestMayHaveDispatched = true;
|
|
@@ -2140,7 +2470,9 @@ var KVService = class extends BaseService {
|
|
|
2140
2470
|
return this.authorizationFailure(
|
|
2141
2471
|
`Failed to batch put ${items.length} key(s)`,
|
|
2142
2472
|
response,
|
|
2143
|
-
errorText
|
|
2473
|
+
errorText,
|
|
2474
|
+
paths,
|
|
2475
|
+
KVAction.PUT
|
|
2144
2476
|
);
|
|
2145
2477
|
}
|
|
2146
2478
|
const quotaError = this.handleQuotaErrorResponse(
|
|
@@ -2165,14 +2497,16 @@ var KVService = class extends BaseService {
|
|
|
2165
2497
|
rawBody = await response.json();
|
|
2166
2498
|
} catch (jsonError) {
|
|
2167
2499
|
const cause = jsonError instanceof Error ? jsonError : new Error(String(jsonError));
|
|
2500
|
+
const timedOut = isRequestTimeoutError(cause);
|
|
2168
2501
|
return err(
|
|
2169
2502
|
serviceError(
|
|
2170
|
-
ErrorCodes.NETWORK_ERROR,
|
|
2171
|
-
`KV batchPut response was not valid JSON: ${cause.message}`,
|
|
2503
|
+
timedOut ? ErrorCodes.TIMEOUT : ErrorCodes.NETWORK_ERROR,
|
|
2504
|
+
timedOut ? `KV batchPut response body did not arrive in time: ${cause.message}` : `KV batchPut response was not valid JSON: ${cause.message}`,
|
|
2172
2505
|
"kv",
|
|
2173
2506
|
{
|
|
2174
2507
|
cause,
|
|
2175
2508
|
meta: {
|
|
2509
|
+
...timedOut ? { timeoutMs: cause.timeoutMs } : {},
|
|
2176
2510
|
requestMayHaveDispatched: true,
|
|
2177
2511
|
responseReceived: true,
|
|
2178
2512
|
status: response.status,
|
|
@@ -2227,6 +2561,8 @@ var KVService = class extends BaseService {
|
|
|
2227
2561
|
...wrapped,
|
|
2228
2562
|
meta: { ...wrapped.meta, requestMayHaveDispatched }
|
|
2229
2563
|
});
|
|
2564
|
+
} finally {
|
|
2565
|
+
request.dispose();
|
|
2230
2566
|
}
|
|
2231
2567
|
});
|
|
2232
2568
|
}
|
|
@@ -2249,23 +2585,26 @@ var KVService = class extends BaseService {
|
|
|
2249
2585
|
if (options?.path) {
|
|
2250
2586
|
listPath = listPath ? `${listPath}/${options.path}` : options.path;
|
|
2251
2587
|
}
|
|
2588
|
+
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
2252
2589
|
try {
|
|
2253
2590
|
const response = await this.invokeOperation(
|
|
2254
2591
|
listPath,
|
|
2255
2592
|
KVAction.LIST,
|
|
2256
2593
|
void 0,
|
|
2257
|
-
|
|
2594
|
+
request.signal,
|
|
2258
2595
|
options?.limit === void 0 && options?.cursor === void 0 ? void 0 : {
|
|
2259
2596
|
...options?.limit === void 0 ? {} : { "x-tinycloud-limit": String(options.limit) },
|
|
2260
2597
|
...options?.cursor === void 0 ? {} : { "x-tinycloud-cursor": options.cursor }
|
|
2261
2598
|
}
|
|
2262
2599
|
);
|
|
2263
2600
|
if (!response.ok) {
|
|
2264
|
-
if (response.status === 401) {
|
|
2601
|
+
if (response.status === 401 || response.status === 403) {
|
|
2265
2602
|
return this.authorizationFailure(
|
|
2266
2603
|
"Failed to list keys",
|
|
2267
2604
|
response,
|
|
2268
|
-
await
|
|
2605
|
+
await this.readAuthorizationText(response),
|
|
2606
|
+
[listPath],
|
|
2607
|
+
KVAction.LIST
|
|
2269
2608
|
);
|
|
2270
2609
|
}
|
|
2271
2610
|
const errorText = await response.text();
|
|
@@ -2293,9 +2632,174 @@ var KVService = class extends BaseService {
|
|
|
2293
2632
|
});
|
|
2294
2633
|
} catch (error) {
|
|
2295
2634
|
return err(wrapError("kv", error));
|
|
2635
|
+
} finally {
|
|
2636
|
+
request.dispose();
|
|
2296
2637
|
}
|
|
2297
2638
|
});
|
|
2298
2639
|
}
|
|
2640
|
+
/**
|
|
2641
|
+
* Read one page of the `tinycloud.kv/sync` change feed for a prefix.
|
|
2642
|
+
*/
|
|
2643
|
+
async changes(options) {
|
|
2644
|
+
return this.withTelemetry("changes", options?.prefix, async () => {
|
|
2645
|
+
if (!this.requireAuth()) {
|
|
2646
|
+
return err(authRequiredError("kv"));
|
|
2647
|
+
}
|
|
2648
|
+
if (typeof options?.prefix !== "string" || options.prefix.length === 0) {
|
|
2649
|
+
return err(serviceError(
|
|
2650
|
+
ErrorCodes.INVALID_INPUT,
|
|
2651
|
+
"KV changes prefix must be a non-empty string",
|
|
2652
|
+
"kv"
|
|
2653
|
+
));
|
|
2654
|
+
}
|
|
2655
|
+
if (options.limit !== void 0 && (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 1e3)) {
|
|
2656
|
+
return err(serviceError(
|
|
2657
|
+
ErrorCodes.INVALID_INPUT,
|
|
2658
|
+
"KV changes limit must be an integer from 1 through 1000",
|
|
2659
|
+
"kv"
|
|
2660
|
+
));
|
|
2661
|
+
}
|
|
2662
|
+
const headers = {};
|
|
2663
|
+
if (options.limit !== void 0) headers["x-tinycloud-limit"] = String(options.limit);
|
|
2664
|
+
if (options.cursor !== void 0) headers["x-tinycloud-cursor"] = options.cursor;
|
|
2665
|
+
if (options.retentionGrant !== void 0) {
|
|
2666
|
+
headers["x-tinycloud-retention-grant"] = options.retentionGrant;
|
|
2667
|
+
}
|
|
2668
|
+
const request = this.createRequestSignal(options.signal, options.timeout);
|
|
2669
|
+
try {
|
|
2670
|
+
const response = await this.invokeOperation(
|
|
2671
|
+
options.prefix,
|
|
2672
|
+
KVAction.SYNC,
|
|
2673
|
+
void 0,
|
|
2674
|
+
request.signal,
|
|
2675
|
+
headers
|
|
2676
|
+
);
|
|
2677
|
+
if (!response.ok) {
|
|
2678
|
+
return await this.changesFailure(response, options.prefix, request.signal);
|
|
2679
|
+
}
|
|
2680
|
+
const text = await response.text();
|
|
2681
|
+
let body;
|
|
2682
|
+
try {
|
|
2683
|
+
body = JSON.parse(text);
|
|
2684
|
+
} catch {
|
|
2685
|
+
body = void 0;
|
|
2686
|
+
}
|
|
2687
|
+
const page = this.normalizeChangesResponse(body);
|
|
2688
|
+
if (!page) {
|
|
2689
|
+
return err(serviceError(
|
|
2690
|
+
ErrorCodes.NETWORK_ERROR,
|
|
2691
|
+
"KV changes response was not a tinycloud.kv/sync page",
|
|
2692
|
+
"kv",
|
|
2693
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2694
|
+
));
|
|
2695
|
+
}
|
|
2696
|
+
return ok(page);
|
|
2697
|
+
} catch (error) {
|
|
2698
|
+
return err(wrapError("kv", error));
|
|
2699
|
+
} finally {
|
|
2700
|
+
request.dispose();
|
|
2701
|
+
}
|
|
2702
|
+
});
|
|
2703
|
+
}
|
|
2704
|
+
/**
|
|
2705
|
+
* Map a failed `kv/sync` response. 410 and the retention 403 carry
|
|
2706
|
+
* `{"error":{"code","reason"}}`; a revoked grant is a 401 whose whole body
|
|
2707
|
+
* is the node's revocation error (see REVOKED_BODY). A body read that
|
|
2708
|
+
* fails because the request was cancelled or timed out rethrows, so the
|
|
2709
|
+
* caller sees `ABORTED`/`TIMEOUT` rather than a mapped status.
|
|
2710
|
+
*/
|
|
2711
|
+
async changesFailure(response, prefix, signal) {
|
|
2712
|
+
if (response.status === 404) {
|
|
2713
|
+
return this.classifyNotFound(response, prefix);
|
|
2714
|
+
}
|
|
2715
|
+
let errorText;
|
|
2716
|
+
try {
|
|
2717
|
+
errorText = await response.text();
|
|
2718
|
+
} catch (error) {
|
|
2719
|
+
if (signal.aborted) throw error;
|
|
2720
|
+
errorText = "";
|
|
2721
|
+
}
|
|
2722
|
+
const meta = { status: response.status, statusText: response.statusText };
|
|
2723
|
+
let reason;
|
|
2724
|
+
let bodyCode;
|
|
2725
|
+
try {
|
|
2726
|
+
const body = JSON.parse(errorText);
|
|
2727
|
+
const error = isJsonObject(body) ? body.error : void 0;
|
|
2728
|
+
if (isJsonObject(error)) {
|
|
2729
|
+
if (typeof error.code === "string") bodyCode = error.code;
|
|
2730
|
+
if (typeof error.reason === "string") reason = error.reason;
|
|
2731
|
+
}
|
|
2732
|
+
} catch {
|
|
2733
|
+
}
|
|
2734
|
+
const context = `Failed to read KV changes for ${JSON.stringify(prefix)}`;
|
|
2735
|
+
if (response.status === 410) {
|
|
2736
|
+
return err(serviceError(
|
|
2737
|
+
ErrorCodes.KV_SYNC_RESET_REQUIRED,
|
|
2738
|
+
`${context}: the node requires a reset (${reason ?? "reason not given"}); discard sync state and restart without a cursor`,
|
|
2739
|
+
"kv",
|
|
2740
|
+
{ meta: { ...meta, reason } }
|
|
2741
|
+
));
|
|
2742
|
+
}
|
|
2743
|
+
if (response.status === 403 && bodyCode === "RETENTION_GRANT_REFUSED") {
|
|
2744
|
+
return err(serviceError(
|
|
2745
|
+
ErrorCodes.KV_RETENTION_GRANT_REFUSED,
|
|
2746
|
+
`${context}: the retention grant was refused (${reason ?? "reason not given"})`,
|
|
2747
|
+
"kv",
|
|
2748
|
+
{ meta: { ...meta, reason } }
|
|
2749
|
+
));
|
|
2750
|
+
}
|
|
2751
|
+
if (response.status === 401) {
|
|
2752
|
+
const revoked = revocationCodeOf(errorText);
|
|
2753
|
+
if (revoked !== void 0) {
|
|
2754
|
+
return err(serviceError(revoked, `${context}: 401 - ${errorText}`, "kv", { meta }));
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
if (response.status === 401 || response.status === 403) {
|
|
2758
|
+
return this.authorizationFailure(context, response, errorText, [prefix], KVAction.SYNC);
|
|
2759
|
+
}
|
|
2760
|
+
return err(serviceError(
|
|
2761
|
+
response.status === 400 ? ErrorCodes.INVALID_INPUT : ErrorCodes.NETWORK_ERROR,
|
|
2762
|
+
`${context}: ${response.status} - ${errorText}`,
|
|
2763
|
+
"kv",
|
|
2764
|
+
{ meta }
|
|
2765
|
+
));
|
|
2766
|
+
}
|
|
2767
|
+
normalizeChangesResponse(data) {
|
|
2768
|
+
if (!isJsonObject(data)) return void 0;
|
|
2769
|
+
const { changes, more, cursor, source, authority } = data;
|
|
2770
|
+
if (!Array.isArray(changes) || typeof more !== "boolean" || typeof cursor !== "string" || !isJsonObject(source) || typeof source.nodeDid !== "string" || typeof source.space !== "string" || typeof source.prefix !== "string" || !isJsonObject(authority)) {
|
|
2771
|
+
return void 0;
|
|
2772
|
+
}
|
|
2773
|
+
const { notBefore, expiresAt, retainUntil } = authority;
|
|
2774
|
+
if (notBefore !== null && typeof notBefore !== "string" || expiresAt !== null && typeof expiresAt !== "string" || retainUntil !== null && typeof retainUntil !== "string") {
|
|
2775
|
+
return void 0;
|
|
2776
|
+
}
|
|
2777
|
+
const parsed = [];
|
|
2778
|
+
for (const change of changes) {
|
|
2779
|
+
if (!isJsonObject(change) || typeof change.key !== "string") return void 0;
|
|
2780
|
+
if (change.deleted === true) {
|
|
2781
|
+
parsed.push({ key: change.key, deleted: true });
|
|
2782
|
+
continue;
|
|
2783
|
+
}
|
|
2784
|
+
const { metadata } = change;
|
|
2785
|
+
if (change.deleted !== false || typeof change.etag !== "string" || !isJsonObject(metadata) || !Object.values(metadata).every((value) => typeof value === "string")) {
|
|
2786
|
+
return void 0;
|
|
2787
|
+
}
|
|
2788
|
+
parsed.push({
|
|
2789
|
+
key: change.key,
|
|
2790
|
+
deleted: false,
|
|
2791
|
+
etag: change.etag,
|
|
2792
|
+
metadata
|
|
2793
|
+
});
|
|
2794
|
+
}
|
|
2795
|
+
return {
|
|
2796
|
+
changes: parsed,
|
|
2797
|
+
more,
|
|
2798
|
+
cursor,
|
|
2799
|
+
source: { nodeDid: source.nodeDid, space: source.space, prefix: source.prefix },
|
|
2800
|
+
authority: { notBefore, expiresAt, retainUntil }
|
|
2801
|
+
};
|
|
2802
|
+
}
|
|
2299
2803
|
/**
|
|
2300
2804
|
* Delete a key.
|
|
2301
2805
|
*/
|
|
@@ -2305,20 +2809,23 @@ var KVService = class extends BaseService {
|
|
|
2305
2809
|
return err(authRequiredError("kv"));
|
|
2306
2810
|
}
|
|
2307
2811
|
const path = this.getFullPath(key, options?.prefix);
|
|
2812
|
+
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
2308
2813
|
try {
|
|
2309
2814
|
const response = await this.invokeOperation(
|
|
2310
2815
|
path,
|
|
2311
2816
|
KVAction.DELETE,
|
|
2312
2817
|
void 0,
|
|
2313
|
-
|
|
2818
|
+
request.signal,
|
|
2314
2819
|
options?.ifMatch === void 0 ? void 0 : { "if-match": options.ifMatch }
|
|
2315
2820
|
);
|
|
2316
2821
|
if (!response.ok) {
|
|
2317
|
-
if (response.status === 401) {
|
|
2822
|
+
if (response.status === 401 || response.status === 403) {
|
|
2318
2823
|
return this.authorizationFailure(
|
|
2319
2824
|
`Failed to delete key ${JSON.stringify(key)}`,
|
|
2320
2825
|
response,
|
|
2321
|
-
await
|
|
2826
|
+
await this.readAuthorizationText(response),
|
|
2827
|
+
[path],
|
|
2828
|
+
KVAction.DELETE
|
|
2322
2829
|
);
|
|
2323
2830
|
}
|
|
2324
2831
|
if (response.status === 404) {
|
|
@@ -2356,6 +2863,8 @@ var KVService = class extends BaseService {
|
|
|
2356
2863
|
});
|
|
2357
2864
|
} catch (error) {
|
|
2358
2865
|
return err(wrapError("kv", error));
|
|
2866
|
+
} finally {
|
|
2867
|
+
request.dispose();
|
|
2359
2868
|
}
|
|
2360
2869
|
});
|
|
2361
2870
|
}
|
|
@@ -2368,19 +2877,22 @@ var KVService = class extends BaseService {
|
|
|
2368
2877
|
return err(authRequiredError("kv"));
|
|
2369
2878
|
}
|
|
2370
2879
|
const path = this.getFullPath(key, options?.prefix);
|
|
2880
|
+
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
2371
2881
|
try {
|
|
2372
2882
|
const response = await this.invokeOperation(
|
|
2373
2883
|
path,
|
|
2374
2884
|
KVAction.HEAD,
|
|
2375
2885
|
void 0,
|
|
2376
|
-
|
|
2886
|
+
request.signal
|
|
2377
2887
|
);
|
|
2378
2888
|
if (!response.ok) {
|
|
2379
|
-
if (response.status === 401) {
|
|
2889
|
+
if (response.status === 401 || response.status === 403) {
|
|
2380
2890
|
return this.authorizationFailure(
|
|
2381
2891
|
`Failed to get metadata for key ${JSON.stringify(key)}`,
|
|
2382
2892
|
response,
|
|
2383
|
-
await
|
|
2893
|
+
await this.readAuthorizationText(response),
|
|
2894
|
+
[path],
|
|
2895
|
+
KVAction.HEAD
|
|
2384
2896
|
);
|
|
2385
2897
|
}
|
|
2386
2898
|
if (response.status === 404) {
|
|
@@ -2402,6 +2914,8 @@ var KVService = class extends BaseService {
|
|
|
2402
2914
|
});
|
|
2403
2915
|
} catch (error) {
|
|
2404
2916
|
return err(wrapError("kv", error));
|
|
2917
|
+
} finally {
|
|
2918
|
+
request.dispose();
|
|
2405
2919
|
}
|
|
2406
2920
|
});
|
|
2407
2921
|
}
|
|
@@ -2439,15 +2953,16 @@ var KVService = class extends BaseService {
|
|
|
2439
2953
|
if (options?.etag !== void 0) {
|
|
2440
2954
|
body.etag = options.etag;
|
|
2441
2955
|
}
|
|
2956
|
+
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
2442
2957
|
try {
|
|
2443
2958
|
const response = await this.context.fetch(`${this.host}/signed/kv`, {
|
|
2444
2959
|
method: "POST",
|
|
2445
2960
|
headers: this.withJsonContentType(headers),
|
|
2446
2961
|
body: JSON.stringify(body),
|
|
2447
|
-
signal:
|
|
2962
|
+
signal: request.signal
|
|
2448
2963
|
});
|
|
2449
2964
|
if (!response.ok) {
|
|
2450
|
-
return this.createSignedReadUrlError(response, key);
|
|
2965
|
+
return this.createSignedReadUrlError(response, key, path);
|
|
2451
2966
|
}
|
|
2452
2967
|
const signedUrl = this.normalizeSignedReadUrlResponse(
|
|
2453
2968
|
await response.json()
|
|
@@ -2464,6 +2979,8 @@ var KVService = class extends BaseService {
|
|
|
2464
2979
|
return ok(signedUrl);
|
|
2465
2980
|
} catch (error) {
|
|
2466
2981
|
return err(wrapError("kv", error));
|
|
2982
|
+
} finally {
|
|
2983
|
+
request.dispose();
|
|
2467
2984
|
}
|
|
2468
2985
|
});
|
|
2469
2986
|
}
|
|
@@ -2677,6 +3194,7 @@ var SQLService = class extends BaseService {
|
|
|
2677
3194
|
if (!this.requireAuth()) {
|
|
2678
3195
|
return err(authRequiredError("sql"));
|
|
2679
3196
|
}
|
|
3197
|
+
const request = this.createRequestSignal(options?.signal);
|
|
2680
3198
|
try {
|
|
2681
3199
|
const body = {
|
|
2682
3200
|
action: "query",
|
|
@@ -2685,19 +3203,22 @@ var SQLService = class extends BaseService {
|
|
|
2685
3203
|
};
|
|
2686
3204
|
if (options?.maxRows !== void 0) body.maxRows = options.maxRows;
|
|
2687
3205
|
if (options?.maxBytes !== void 0) body.maxBytes = options.maxBytes;
|
|
3206
|
+
const action = this.actionForSql(sql, SQLAction.READ);
|
|
2688
3207
|
const response = await this.invokeSQL(
|
|
2689
3208
|
dbName,
|
|
2690
|
-
|
|
3209
|
+
action,
|
|
2691
3210
|
body,
|
|
2692
|
-
|
|
3211
|
+
request.signal
|
|
2693
3212
|
);
|
|
2694
3213
|
if (!response.ok) {
|
|
2695
|
-
return this.handleErrorResponse(response, "query");
|
|
3214
|
+
return this.handleErrorResponse(response, "query", dbName, [action]);
|
|
2696
3215
|
}
|
|
2697
3216
|
const data = await response.json();
|
|
2698
3217
|
return ok(data);
|
|
2699
3218
|
} catch (error) {
|
|
2700
3219
|
return err(wrapError("sql", error));
|
|
3220
|
+
} finally {
|
|
3221
|
+
request.dispose();
|
|
2701
3222
|
}
|
|
2702
3223
|
});
|
|
2703
3224
|
}
|
|
@@ -2706,6 +3227,7 @@ var SQLService = class extends BaseService {
|
|
|
2706
3227
|
if (!this.requireAuth()) {
|
|
2707
3228
|
return err(authRequiredError("sql"));
|
|
2708
3229
|
}
|
|
3230
|
+
const request = this.createRequestSignal(options?.signal);
|
|
2709
3231
|
try {
|
|
2710
3232
|
const body = {
|
|
2711
3233
|
action: "execute",
|
|
@@ -2721,19 +3243,22 @@ var SQLService = class extends BaseService {
|
|
|
2721
3243
|
(statement) => this.actionForSql(statement, SQLAction.SCHEMA)
|
|
2722
3244
|
)
|
|
2723
3245
|
];
|
|
3246
|
+
const requestedActions = this.dedupeActions(actions);
|
|
2724
3247
|
const response = await this.invokeSQL(
|
|
2725
3248
|
dbName,
|
|
2726
|
-
|
|
3249
|
+
requestedActions,
|
|
2727
3250
|
body,
|
|
2728
|
-
|
|
3251
|
+
request.signal
|
|
2729
3252
|
);
|
|
2730
3253
|
if (!response.ok) {
|
|
2731
|
-
return this.handleErrorResponse(response, "execute");
|
|
3254
|
+
return this.handleErrorResponse(response, "execute", dbName, requestedActions);
|
|
2732
3255
|
}
|
|
2733
3256
|
const data = await response.json();
|
|
2734
3257
|
return ok(data);
|
|
2735
3258
|
} catch (error) {
|
|
2736
3259
|
return err(wrapError("sql", error));
|
|
3260
|
+
} finally {
|
|
3261
|
+
request.dispose();
|
|
2737
3262
|
}
|
|
2738
3263
|
});
|
|
2739
3264
|
}
|
|
@@ -2742,20 +3267,24 @@ var SQLService = class extends BaseService {
|
|
|
2742
3267
|
if (!this.requireAuth()) {
|
|
2743
3268
|
return err(authRequiredError("sql"));
|
|
2744
3269
|
}
|
|
3270
|
+
const request = this.createRequestSignal(options?.signal);
|
|
2745
3271
|
try {
|
|
3272
|
+
const requestedActions = this.actionsForSqlBatch(statements);
|
|
2746
3273
|
const response = await this.invokeSQL(
|
|
2747
3274
|
dbName,
|
|
2748
|
-
|
|
3275
|
+
requestedActions,
|
|
2749
3276
|
{ action: "batch", statements },
|
|
2750
|
-
|
|
3277
|
+
request.signal
|
|
2751
3278
|
);
|
|
2752
3279
|
if (!response.ok) {
|
|
2753
|
-
return this.handleErrorResponse(response, "batch");
|
|
3280
|
+
return this.handleErrorResponse(response, "batch", dbName, requestedActions);
|
|
2754
3281
|
}
|
|
2755
3282
|
const data = await response.json();
|
|
2756
3283
|
return ok(data);
|
|
2757
3284
|
} catch (error) {
|
|
2758
3285
|
return err(wrapError("sql", error));
|
|
3286
|
+
} finally {
|
|
3287
|
+
request.dispose();
|
|
2759
3288
|
}
|
|
2760
3289
|
});
|
|
2761
3290
|
}
|
|
@@ -2764,20 +3293,23 @@ var SQLService = class extends BaseService {
|
|
|
2764
3293
|
if (!this.requireAuth()) {
|
|
2765
3294
|
return err(authRequiredError("sql"));
|
|
2766
3295
|
}
|
|
3296
|
+
const request = this.createRequestSignal(options?.signal);
|
|
2767
3297
|
try {
|
|
2768
3298
|
const response = await this.invokeSQL(
|
|
2769
3299
|
dbName,
|
|
2770
3300
|
SQLAction.WRITE,
|
|
2771
3301
|
{ action: "execute_statement", name, params: params ?? [] },
|
|
2772
|
-
|
|
3302
|
+
request.signal
|
|
2773
3303
|
);
|
|
2774
3304
|
if (!response.ok) {
|
|
2775
|
-
return this.handleErrorResponse(response, "executeStatement");
|
|
3305
|
+
return this.handleErrorResponse(response, "executeStatement", dbName, [SQLAction.WRITE]);
|
|
2776
3306
|
}
|
|
2777
3307
|
const data = await response.json();
|
|
2778
3308
|
return ok(data);
|
|
2779
3309
|
} catch (error) {
|
|
2780
3310
|
return err(wrapError("sql", error));
|
|
3311
|
+
} finally {
|
|
3312
|
+
request.dispose();
|
|
2781
3313
|
}
|
|
2782
3314
|
});
|
|
2783
3315
|
}
|
|
@@ -2786,15 +3318,16 @@ var SQLService = class extends BaseService {
|
|
|
2786
3318
|
if (!this.requireAuth()) {
|
|
2787
3319
|
return err(authRequiredError("sql"));
|
|
2788
3320
|
}
|
|
3321
|
+
const request = this.createRequestSignal(options?.signal);
|
|
2789
3322
|
try {
|
|
2790
3323
|
const response = await this.invokeSQL(
|
|
2791
3324
|
dbName,
|
|
2792
3325
|
SQLAction.READ,
|
|
2793
3326
|
{ action: "export" },
|
|
2794
|
-
|
|
3327
|
+
request.signal
|
|
2795
3328
|
);
|
|
2796
3329
|
if (!response.ok) {
|
|
2797
|
-
return this.handleErrorResponse(response, "export");
|
|
3330
|
+
return this.handleErrorResponse(response, "export", dbName, [SQLAction.READ]);
|
|
2798
3331
|
}
|
|
2799
3332
|
const resp = response;
|
|
2800
3333
|
if (typeof resp.blob === "function") {
|
|
@@ -2805,26 +3338,31 @@ var SQLService = class extends BaseService {
|
|
|
2805
3338
|
return ok(text);
|
|
2806
3339
|
} catch (error) {
|
|
2807
3340
|
return err(wrapError("sql", error));
|
|
3341
|
+
} finally {
|
|
3342
|
+
request.dispose();
|
|
2808
3343
|
}
|
|
2809
3344
|
});
|
|
2810
3345
|
}
|
|
2811
3346
|
async applyMigrationsOnDbUnlocked(dbName, options) {
|
|
2812
3347
|
return this.withTelemetry("migrations.apply", dbName, async () => {
|
|
2813
|
-
const created = await this.ensureMigrationsTable(dbName, options.signal);
|
|
2814
|
-
if (!created.ok) return created;
|
|
2815
3348
|
const listed = await this.queryOnDb(
|
|
2816
3349
|
dbName,
|
|
2817
3350
|
`SELECT id FROM ${MIGRATIONS_TABLE} WHERE namespace = ? ORDER BY applied_at, id`,
|
|
2818
3351
|
[options.namespace],
|
|
2819
3352
|
{ signal: options.signal }
|
|
2820
3353
|
);
|
|
2821
|
-
|
|
3354
|
+
const tableMissing = !listed.ok && (listed.error.code === ErrorCodes.SQL_DATABASE_NOT_FOUND || isMissingTableError(listed.error.message));
|
|
3355
|
+
if (!listed.ok && !tableMissing) return listed;
|
|
2822
3356
|
const appliedIds = new Set(
|
|
2823
|
-
listed.data.rows.map((row) => rowValue(row, 0)).filter((id) => typeof id === "string")
|
|
3357
|
+
(listed.ok ? listed.data.rows : []).map((row) => rowValue(row, 0)).filter((id) => typeof id === "string")
|
|
2824
3358
|
);
|
|
2825
3359
|
const skipped = options.migrations.filter((migration) => appliedIds.has(migration.id)).map((migration) => migration.id);
|
|
2826
3360
|
const pending = options.migrations.filter((migration) => !appliedIds.has(migration.id));
|
|
2827
3361
|
const applied = [];
|
|
3362
|
+
if (pending.length > 0 && tableMissing) {
|
|
3363
|
+
const created = await this.ensureMigrationsTable(dbName, options.signal);
|
|
3364
|
+
if (!created.ok) return created;
|
|
3365
|
+
}
|
|
2828
3366
|
for (const migration of pending) {
|
|
2829
3367
|
const result = await this.applyOneMigration(dbName, options.namespace, migration, options.signal);
|
|
2830
3368
|
if (!result.ok) return result;
|
|
@@ -2892,7 +3430,7 @@ var SQLService = class extends BaseService {
|
|
|
2892
3430
|
"Content-Type": "application/json"
|
|
2893
3431
|
},
|
|
2894
3432
|
body: JSON.stringify(body),
|
|
2895
|
-
signal
|
|
3433
|
+
signal
|
|
2896
3434
|
});
|
|
2897
3435
|
}
|
|
2898
3436
|
actionForSql(sql, fallback) {
|
|
@@ -2925,8 +3463,12 @@ var SQLService = class extends BaseService {
|
|
|
2925
3463
|
}))
|
|
2926
3464
|
);
|
|
2927
3465
|
}
|
|
2928
|
-
async handleErrorResponse(response, operation) {
|
|
3466
|
+
async handleErrorResponse(response, operation, dbName, requestedActions) {
|
|
2929
3467
|
const errorText = await response.text();
|
|
3468
|
+
const meta = responseErrorMeta(response.status, response.statusText, errorText);
|
|
3469
|
+
if (response.status === 402) {
|
|
3470
|
+
return err(storageRejectionError("sql", 402, meta, errorText));
|
|
3471
|
+
}
|
|
2930
3472
|
const errorBody = parseServiceErrorBody(errorText);
|
|
2931
3473
|
const errorCode = this.mapHttpStatusToErrorCode(
|
|
2932
3474
|
response.status,
|
|
@@ -2939,11 +3481,12 @@ var SQLService = class extends BaseService {
|
|
|
2939
3481
|
errorText,
|
|
2940
3482
|
errorBody
|
|
2941
3483
|
);
|
|
2942
|
-
const meta = responseErrorMeta(response.status, response.statusText, errorText);
|
|
2943
3484
|
if (response.status === 401) {
|
|
2944
|
-
const {
|
|
2945
|
-
if (action
|
|
2946
|
-
|
|
3485
|
+
const { action } = parseAuthError(errorText);
|
|
3486
|
+
if (action && requestedActions.includes(action)) {
|
|
3487
|
+
meta.requiredAction = action;
|
|
3488
|
+
meta.resource = `${this.context.session.spaceId}/sql/${dbName}`;
|
|
3489
|
+
}
|
|
2947
3490
|
}
|
|
2948
3491
|
return err(
|
|
2949
3492
|
serviceError(errorCode, message, "sql", { meta })
|
|
@@ -3040,6 +3583,9 @@ function serializeSqlValues(values) {
|
|
|
3040
3583
|
(value) => value instanceof Uint8Array ? Array.from(value) : value
|
|
3041
3584
|
);
|
|
3042
3585
|
}
|
|
3586
|
+
function isMissingTableError(message) {
|
|
3587
|
+
return /no such table/i.test(message);
|
|
3588
|
+
}
|
|
3043
3589
|
function migrationKey(namespace, id) {
|
|
3044
3590
|
return `${encodeURIComponent(namespace)}:${encodeURIComponent(id)}`;
|
|
3045
3591
|
}
|
|
@@ -3148,12 +3694,13 @@ var DuckDbService = class extends BaseService {
|
|
|
3148
3694
|
if (!this.requireAuth()) {
|
|
3149
3695
|
return err(authRequiredError("duckdb"));
|
|
3150
3696
|
}
|
|
3697
|
+
const request = this.createRequestSignal(options?.signal);
|
|
3151
3698
|
try {
|
|
3152
3699
|
const response = await this.invokeDuckDb(
|
|
3153
3700
|
dbName,
|
|
3154
3701
|
DuckDbAction.READ,
|
|
3155
3702
|
{ action: "query", sql, params: params ?? [] },
|
|
3156
|
-
|
|
3703
|
+
request.signal
|
|
3157
3704
|
);
|
|
3158
3705
|
if (!response.ok) {
|
|
3159
3706
|
return this.handleErrorResponse(response, "query");
|
|
@@ -3162,6 +3709,8 @@ var DuckDbService = class extends BaseService {
|
|
|
3162
3709
|
return ok(data);
|
|
3163
3710
|
} catch (error) {
|
|
3164
3711
|
return err(wrapError("duckdb", error));
|
|
3712
|
+
} finally {
|
|
3713
|
+
request.dispose();
|
|
3165
3714
|
}
|
|
3166
3715
|
});
|
|
3167
3716
|
}
|
|
@@ -3170,12 +3719,13 @@ var DuckDbService = class extends BaseService {
|
|
|
3170
3719
|
if (!this.requireAuth()) {
|
|
3171
3720
|
return err(authRequiredError("duckdb"));
|
|
3172
3721
|
}
|
|
3722
|
+
const request = this.createRequestSignal(options?.signal);
|
|
3173
3723
|
try {
|
|
3174
3724
|
const response = await this.invokeDuckDb(
|
|
3175
3725
|
dbName,
|
|
3176
3726
|
DuckDbAction.READ,
|
|
3177
3727
|
{ action: "query", sql, params: params ?? [] },
|
|
3178
|
-
|
|
3728
|
+
request.signal,
|
|
3179
3729
|
{ Accept: "application/vnd.apache.arrow.stream" }
|
|
3180
3730
|
);
|
|
3181
3731
|
if (!response.ok) {
|
|
@@ -3185,6 +3735,8 @@ var DuckDbService = class extends BaseService {
|
|
|
3185
3735
|
return ok(buffer);
|
|
3186
3736
|
} catch (error) {
|
|
3187
3737
|
return err(wrapError("duckdb", error));
|
|
3738
|
+
} finally {
|
|
3739
|
+
request.dispose();
|
|
3188
3740
|
}
|
|
3189
3741
|
});
|
|
3190
3742
|
}
|
|
@@ -3193,6 +3745,7 @@ var DuckDbService = class extends BaseService {
|
|
|
3193
3745
|
if (!this.requireAuth()) {
|
|
3194
3746
|
return err(authRequiredError("duckdb"));
|
|
3195
3747
|
}
|
|
3748
|
+
const request = this.createRequestSignal(options?.signal);
|
|
3196
3749
|
try {
|
|
3197
3750
|
const body = {
|
|
3198
3751
|
action: "execute",
|
|
@@ -3206,7 +3759,7 @@ var DuckDbService = class extends BaseService {
|
|
|
3206
3759
|
dbName,
|
|
3207
3760
|
DuckDbAction.WRITE,
|
|
3208
3761
|
body,
|
|
3209
|
-
|
|
3762
|
+
request.signal
|
|
3210
3763
|
);
|
|
3211
3764
|
if (!response.ok) {
|
|
3212
3765
|
return this.handleErrorResponse(response, "execute");
|
|
@@ -3215,6 +3768,8 @@ var DuckDbService = class extends BaseService {
|
|
|
3215
3768
|
return ok(data);
|
|
3216
3769
|
} catch (error) {
|
|
3217
3770
|
return err(wrapError("duckdb", error));
|
|
3771
|
+
} finally {
|
|
3772
|
+
request.dispose();
|
|
3218
3773
|
}
|
|
3219
3774
|
});
|
|
3220
3775
|
}
|
|
@@ -3223,6 +3778,7 @@ var DuckDbService = class extends BaseService {
|
|
|
3223
3778
|
if (!this.requireAuth()) {
|
|
3224
3779
|
return err(authRequiredError("duckdb"));
|
|
3225
3780
|
}
|
|
3781
|
+
const request = this.createRequestSignal(options?.signal);
|
|
3226
3782
|
try {
|
|
3227
3783
|
const body = {
|
|
3228
3784
|
action: "batch",
|
|
@@ -3235,7 +3791,7 @@ var DuckDbService = class extends BaseService {
|
|
|
3235
3791
|
dbName,
|
|
3236
3792
|
DuckDbAction.WRITE,
|
|
3237
3793
|
body,
|
|
3238
|
-
|
|
3794
|
+
request.signal
|
|
3239
3795
|
);
|
|
3240
3796
|
if (!response.ok) {
|
|
3241
3797
|
return this.handleErrorResponse(response, "batch");
|
|
@@ -3244,6 +3800,8 @@ var DuckDbService = class extends BaseService {
|
|
|
3244
3800
|
return ok(data);
|
|
3245
3801
|
} catch (error) {
|
|
3246
3802
|
return err(wrapError("duckdb", error));
|
|
3803
|
+
} finally {
|
|
3804
|
+
request.dispose();
|
|
3247
3805
|
}
|
|
3248
3806
|
});
|
|
3249
3807
|
}
|
|
@@ -3252,12 +3810,13 @@ var DuckDbService = class extends BaseService {
|
|
|
3252
3810
|
if (!this.requireAuth()) {
|
|
3253
3811
|
return err(authRequiredError("duckdb"));
|
|
3254
3812
|
}
|
|
3813
|
+
const request = this.createRequestSignal(options?.signal);
|
|
3255
3814
|
try {
|
|
3256
3815
|
const response = await this.invokeDuckDb(
|
|
3257
3816
|
dbName,
|
|
3258
3817
|
DuckDbAction.WRITE,
|
|
3259
3818
|
{ action: "executeStatement", name, params: params ?? [] },
|
|
3260
|
-
|
|
3819
|
+
request.signal
|
|
3261
3820
|
);
|
|
3262
3821
|
if (!response.ok) {
|
|
3263
3822
|
return this.handleErrorResponse(response, "executeStatement");
|
|
@@ -3266,6 +3825,8 @@ var DuckDbService = class extends BaseService {
|
|
|
3266
3825
|
return ok(data);
|
|
3267
3826
|
} catch (error) {
|
|
3268
3827
|
return err(wrapError("duckdb", error));
|
|
3828
|
+
} finally {
|
|
3829
|
+
request.dispose();
|
|
3269
3830
|
}
|
|
3270
3831
|
});
|
|
3271
3832
|
}
|
|
@@ -3274,12 +3835,13 @@ var DuckDbService = class extends BaseService {
|
|
|
3274
3835
|
if (!this.requireAuth()) {
|
|
3275
3836
|
return err(authRequiredError("duckdb"));
|
|
3276
3837
|
}
|
|
3838
|
+
const request = this.createRequestSignal(options?.signal);
|
|
3277
3839
|
try {
|
|
3278
3840
|
const response = await this.invokeDuckDb(
|
|
3279
3841
|
dbName,
|
|
3280
3842
|
DuckDbAction.READ,
|
|
3281
3843
|
{ action: "describe" },
|
|
3282
|
-
|
|
3844
|
+
request.signal
|
|
3283
3845
|
);
|
|
3284
3846
|
if (!response.ok) {
|
|
3285
3847
|
return this.handleErrorResponse(response, "describe");
|
|
@@ -3288,6 +3850,8 @@ var DuckDbService = class extends BaseService {
|
|
|
3288
3850
|
return ok(data);
|
|
3289
3851
|
} catch (error) {
|
|
3290
3852
|
return err(wrapError("duckdb", error));
|
|
3853
|
+
} finally {
|
|
3854
|
+
request.dispose();
|
|
3291
3855
|
}
|
|
3292
3856
|
});
|
|
3293
3857
|
}
|
|
@@ -3296,12 +3860,13 @@ var DuckDbService = class extends BaseService {
|
|
|
3296
3860
|
if (!this.requireAuth()) {
|
|
3297
3861
|
return err(authRequiredError("duckdb"));
|
|
3298
3862
|
}
|
|
3863
|
+
const request = this.createRequestSignal(options?.signal);
|
|
3299
3864
|
try {
|
|
3300
3865
|
const response = await this.invokeDuckDb(
|
|
3301
3866
|
dbName,
|
|
3302
3867
|
DuckDbAction.EXPORT,
|
|
3303
3868
|
{ action: "export" },
|
|
3304
|
-
|
|
3869
|
+
request.signal
|
|
3305
3870
|
);
|
|
3306
3871
|
if (!response.ok) {
|
|
3307
3872
|
return this.handleErrorResponse(response, "export");
|
|
@@ -3310,6 +3875,8 @@ var DuckDbService = class extends BaseService {
|
|
|
3310
3875
|
return ok(blob);
|
|
3311
3876
|
} catch (error) {
|
|
3312
3877
|
return err(wrapError("duckdb", error));
|
|
3878
|
+
} finally {
|
|
3879
|
+
request.dispose();
|
|
3313
3880
|
}
|
|
3314
3881
|
});
|
|
3315
3882
|
}
|
|
@@ -3318,6 +3885,7 @@ var DuckDbService = class extends BaseService {
|
|
|
3318
3885
|
if (!this.requireAuth()) {
|
|
3319
3886
|
return err(authRequiredError("duckdb"));
|
|
3320
3887
|
}
|
|
3888
|
+
const request = this.createRequestSignal(options?.signal);
|
|
3321
3889
|
try {
|
|
3322
3890
|
const session = this.context.session;
|
|
3323
3891
|
const headers = this.context.invoke(
|
|
@@ -3333,7 +3901,7 @@ var DuckDbService = class extends BaseService {
|
|
|
3333
3901
|
"Content-Type": "application/x-duckdb"
|
|
3334
3902
|
},
|
|
3335
3903
|
body: new Blob([data]),
|
|
3336
|
-
signal:
|
|
3904
|
+
signal: request.signal
|
|
3337
3905
|
});
|
|
3338
3906
|
if (!response.ok) {
|
|
3339
3907
|
return this.handleErrorResponse(response, "import");
|
|
@@ -3341,6 +3909,8 @@ var DuckDbService = class extends BaseService {
|
|
|
3341
3909
|
return ok(void 0);
|
|
3342
3910
|
} catch (error) {
|
|
3343
3911
|
return err(wrapError("duckdb", error));
|
|
3912
|
+
} finally {
|
|
3913
|
+
request.dispose();
|
|
3344
3914
|
}
|
|
3345
3915
|
});
|
|
3346
3916
|
}
|
|
@@ -3356,11 +3926,15 @@ var DuckDbService = class extends BaseService {
|
|
|
3356
3926
|
...extraHeaders
|
|
3357
3927
|
},
|
|
3358
3928
|
body: JSON.stringify(body),
|
|
3359
|
-
signal
|
|
3929
|
+
signal
|
|
3360
3930
|
});
|
|
3361
3931
|
}
|
|
3362
3932
|
async handleErrorResponse(response, operation) {
|
|
3363
3933
|
const errorText = await response.text();
|
|
3934
|
+
const meta = responseErrorMeta(response.status, response.statusText, errorText);
|
|
3935
|
+
if (response.status === 402) {
|
|
3936
|
+
return err(storageRejectionError("duckdb", 402, meta, errorText));
|
|
3937
|
+
}
|
|
3364
3938
|
const errorBody = parseServiceErrorBody(errorText);
|
|
3365
3939
|
const errorCode = this.mapHttpStatusToErrorCode(
|
|
3366
3940
|
response.status,
|
|
@@ -3373,7 +3947,6 @@ var DuckDbService = class extends BaseService {
|
|
|
3373
3947
|
errorText,
|
|
3374
3948
|
errorBody
|
|
3375
3949
|
);
|
|
3376
|
-
const meta = responseErrorMeta(response.status, response.statusText, errorText);
|
|
3377
3950
|
if (response.status === 401) {
|
|
3378
3951
|
const { resource, action } = parseAuthError(errorText);
|
|
3379
3952
|
if (action) meta.requiredAction = action;
|
|
@@ -4345,6 +4918,9 @@ function defaultVaultMessage(input) {
|
|
|
4345
4918
|
return input.message ?? `Public key not found for ${input.did}`;
|
|
4346
4919
|
case "STORAGE_ERROR":
|
|
4347
4920
|
return input.message ?? input.cause.message;
|
|
4921
|
+
case "STORAGE_QUOTA_EXCEEDED":
|
|
4922
|
+
case "STORAGE_LIMIT_REACHED":
|
|
4923
|
+
return input.message;
|
|
4348
4924
|
}
|
|
4349
4925
|
}
|
|
4350
4926
|
function vaultError(input) {
|
|
@@ -4355,6 +4931,15 @@ function vaultError(input) {
|
|
|
4355
4931
|
};
|
|
4356
4932
|
return { ok: false, error };
|
|
4357
4933
|
}
|
|
4934
|
+
function kvWriteError(context, error) {
|
|
4935
|
+
if (error.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error.code === ErrorCodes.STORAGE_LIMIT_REACHED) {
|
|
4936
|
+
return vaultError({ code: error.code, message: error.message, meta: error.meta });
|
|
4937
|
+
}
|
|
4938
|
+
return vaultError({
|
|
4939
|
+
code: "STORAGE_ERROR",
|
|
4940
|
+
cause: new Error(`${context}: ${error.message}`)
|
|
4941
|
+
});
|
|
4942
|
+
}
|
|
4358
4943
|
var DataVaultService = class extends BaseService {
|
|
4359
4944
|
/**
|
|
4360
4945
|
* Create a new DataVaultService instance.
|
|
@@ -4637,12 +5222,7 @@ var DataVaultService = class extends BaseService {
|
|
|
4637
5222
|
JSON.stringify(envelopeResult.data)
|
|
4638
5223
|
);
|
|
4639
5224
|
if (!valuePutResult.ok) {
|
|
4640
|
-
return
|
|
4641
|
-
code: "STORAGE_ERROR",
|
|
4642
|
-
cause: new Error(
|
|
4643
|
-
`Failed to store encrypted value: ${valuePutResult.error.message}`
|
|
4644
|
-
)
|
|
4645
|
-
});
|
|
5225
|
+
return kvWriteError("Failed to store encrypted value", valuePutResult.error);
|
|
4646
5226
|
}
|
|
4647
5227
|
return { ok: true, data: void 0 };
|
|
4648
5228
|
} catch (error) {
|
|
@@ -4860,12 +5440,7 @@ var DataVaultService = class extends BaseService {
|
|
|
4860
5440
|
keyPayload
|
|
4861
5441
|
);
|
|
4862
5442
|
if (!keyPutResult.ok) {
|
|
4863
|
-
return
|
|
4864
|
-
code: "STORAGE_ERROR",
|
|
4865
|
-
cause: new Error(
|
|
4866
|
-
`Failed to store key blob: ${keyPutResult.error.message}`
|
|
4867
|
-
)
|
|
4868
|
-
});
|
|
5443
|
+
return kvWriteError("Failed to store key blob", keyPutResult.error);
|
|
4869
5444
|
}
|
|
4870
5445
|
const valuePayload = JSON.stringify({
|
|
4871
5446
|
data: base64Encode(encrypted),
|
|
@@ -4876,12 +5451,7 @@ var DataVaultService = class extends BaseService {
|
|
|
4876
5451
|
valuePayload
|
|
4877
5452
|
);
|
|
4878
5453
|
if (!valuePutResult.ok) {
|
|
4879
|
-
return
|
|
4880
|
-
code: "STORAGE_ERROR",
|
|
4881
|
-
cause: new Error(
|
|
4882
|
-
`Failed to store encrypted value: ${valuePutResult.error.message}`
|
|
4883
|
-
)
|
|
4884
|
-
});
|
|
5454
|
+
return kvWriteError("Failed to store encrypted value", valuePutResult.error);
|
|
4885
5455
|
}
|
|
4886
5456
|
return ok(void 0);
|
|
4887
5457
|
} catch (error) {
|
|
@@ -5212,12 +5782,7 @@ var DataVaultService = class extends BaseService {
|
|
|
5212
5782
|
grantPayload
|
|
5213
5783
|
);
|
|
5214
5784
|
if (!grantPutResult.ok) {
|
|
5215
|
-
return
|
|
5216
|
-
code: "STORAGE_ERROR",
|
|
5217
|
-
cause: new Error(
|
|
5218
|
-
`Failed to store grant: ${grantPutResult.error.message}`
|
|
5219
|
-
)
|
|
5220
|
-
});
|
|
5785
|
+
return kvWriteError("Failed to store grant", grantPutResult.error);
|
|
5221
5786
|
}
|
|
5222
5787
|
return ok(void 0);
|
|
5223
5788
|
} catch (error) {
|
|
@@ -5562,12 +6127,7 @@ var DataVaultService = class extends BaseService {
|
|
|
5562
6127
|
keyPayload
|
|
5563
6128
|
);
|
|
5564
6129
|
if (!keyPutResult.ok) {
|
|
5565
|
-
return
|
|
5566
|
-
code: "STORAGE_ERROR",
|
|
5567
|
-
cause: new Error(
|
|
5568
|
-
`Failed to store rotated key blob: ${keyPutResult.error.message}`
|
|
5569
|
-
)
|
|
5570
|
-
});
|
|
6130
|
+
return kvWriteError("Failed to store rotated key blob", keyPutResult.error);
|
|
5571
6131
|
}
|
|
5572
6132
|
const valuePayload = JSON.stringify({
|
|
5573
6133
|
data: base64Encode(encrypted),
|
|
@@ -5578,12 +6138,7 @@ var DataVaultService = class extends BaseService {
|
|
|
5578
6138
|
valuePayload
|
|
5579
6139
|
);
|
|
5580
6140
|
if (!valuePutResult.ok) {
|
|
5581
|
-
return
|
|
5582
|
-
code: "STORAGE_ERROR",
|
|
5583
|
-
cause: new Error(
|
|
5584
|
-
`Failed to store re-encrypted value: ${valuePutResult.error.message}`
|
|
5585
|
-
)
|
|
5586
|
-
});
|
|
6141
|
+
return kvWriteError("Failed to store re-encrypted value", valuePutResult.error);
|
|
5587
6142
|
}
|
|
5588
6143
|
for (const did of remainingGrantees) {
|
|
5589
6144
|
const grantResult = await this.reencrypt(key, did);
|
|
@@ -5792,6 +6347,7 @@ var SecretsService = class {
|
|
|
5792
6347
|
}
|
|
5793
6348
|
async listAll() {
|
|
5794
6349
|
const entries = /* @__PURE__ */ new Map();
|
|
6350
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
5795
6351
|
let cursor;
|
|
5796
6352
|
do {
|
|
5797
6353
|
const result = await this.vault.listPage({
|
|
@@ -5814,6 +6370,14 @@ var SecretsService = class {
|
|
|
5814
6370
|
"Secret catalog listing was truncated without a continuation cursor."
|
|
5815
6371
|
);
|
|
5816
6372
|
}
|
|
6373
|
+
if (cursor !== void 0) {
|
|
6374
|
+
if (seenCursors.has(cursor)) {
|
|
6375
|
+
return invalidSecretInput(
|
|
6376
|
+
"Secret catalog listing repeated a continuation cursor; the node does not support paged KV lists."
|
|
6377
|
+
);
|
|
6378
|
+
}
|
|
6379
|
+
seenCursors.add(cursor);
|
|
6380
|
+
}
|
|
5817
6381
|
} while (cursor !== void 0);
|
|
5818
6382
|
return {
|
|
5819
6383
|
ok: true,
|
|
@@ -5934,103 +6498,6 @@ function canonicalHashHex(sha256, value) {
|
|
|
5934
6498
|
return hexEncode2(sha256(utf8Encode(canonical)));
|
|
5935
6499
|
}
|
|
5936
6500
|
|
|
5937
|
-
// src/encryption/networkId.ts
|
|
5938
|
-
var URN_PREFIX = "urn:tinycloud:encryption:";
|
|
5939
|
-
var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
5940
|
-
var PKH_EIP155_DID_RE = /^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40})$/;
|
|
5941
|
-
var NetworkIdError = class extends Error {
|
|
5942
|
-
constructor(message) {
|
|
5943
|
-
super(message);
|
|
5944
|
-
this.name = "NetworkIdError";
|
|
5945
|
-
}
|
|
5946
|
-
};
|
|
5947
|
-
function parseNetworkId(networkId) {
|
|
5948
|
-
if (typeof networkId !== "string" || networkId.length === 0) {
|
|
5949
|
-
throw new NetworkIdError("networkId must be a non-empty string");
|
|
5950
|
-
}
|
|
5951
|
-
if (!networkId.startsWith(URN_PREFIX)) {
|
|
5952
|
-
throw new NetworkIdError(
|
|
5953
|
-
`networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
|
|
5954
|
-
);
|
|
5955
|
-
}
|
|
5956
|
-
const body = networkId.slice(URN_PREFIX.length);
|
|
5957
|
-
const lastColon = body.lastIndexOf(":");
|
|
5958
|
-
if (lastColon <= 0 || lastColon === body.length - 1) {
|
|
5959
|
-
throw new NetworkIdError(
|
|
5960
|
-
`networkId missing ownerDid or name segment (got ${JSON.stringify(networkId)})`
|
|
5961
|
-
);
|
|
5962
|
-
}
|
|
5963
|
-
const ownerDid = body.slice(0, lastColon);
|
|
5964
|
-
const name = body.slice(lastColon + 1);
|
|
5965
|
-
if (!ownerDid.startsWith("did:")) {
|
|
5966
|
-
throw new NetworkIdError(
|
|
5967
|
-
`networkId ownerDid must be a DID (got ${JSON.stringify(ownerDid)})`
|
|
5968
|
-
);
|
|
5969
|
-
}
|
|
5970
|
-
const didParts = ownerDid.split(":");
|
|
5971
|
-
if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
|
|
5972
|
-
throw new NetworkIdError(
|
|
5973
|
-
`networkId ownerDid is not a well-formed DID (got ${JSON.stringify(ownerDid)})`
|
|
5974
|
-
);
|
|
5975
|
-
}
|
|
5976
|
-
if (!NETWORK_NAME_RE.test(name)) {
|
|
5977
|
-
throw new NetworkIdError(
|
|
5978
|
-
`networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
5979
|
-
);
|
|
5980
|
-
}
|
|
5981
|
-
return { networkId, ownerDid, name };
|
|
5982
|
-
}
|
|
5983
|
-
function buildNetworkId(ownerDid, name) {
|
|
5984
|
-
if (typeof ownerDid !== "string" || !ownerDid.startsWith("did:")) {
|
|
5985
|
-
throw new NetworkIdError("ownerDid must be a DID");
|
|
5986
|
-
}
|
|
5987
|
-
if (typeof name !== "string" || !NETWORK_NAME_RE.test(name)) {
|
|
5988
|
-
throw new NetworkIdError(
|
|
5989
|
-
`network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
5990
|
-
);
|
|
5991
|
-
}
|
|
5992
|
-
const networkId = `${URN_PREFIX}${ownerDid}:${name}`;
|
|
5993
|
-
parseNetworkId(networkId);
|
|
5994
|
-
return networkId;
|
|
5995
|
-
}
|
|
5996
|
-
function isNetworkId(networkId) {
|
|
5997
|
-
if (typeof networkId !== "string") {
|
|
5998
|
-
return false;
|
|
5999
|
-
}
|
|
6000
|
-
try {
|
|
6001
|
-
parseNetworkId(networkId);
|
|
6002
|
-
return true;
|
|
6003
|
-
} catch {
|
|
6004
|
-
return false;
|
|
6005
|
-
}
|
|
6006
|
-
}
|
|
6007
|
-
function parsePkhOwnerDid(ownerDid) {
|
|
6008
|
-
const match = ownerDid.match(PKH_EIP155_DID_RE);
|
|
6009
|
-
if (!match) return null;
|
|
6010
|
-
return {
|
|
6011
|
-
chainId: match[1],
|
|
6012
|
-
address: match[2].toLowerCase()
|
|
6013
|
-
};
|
|
6014
|
-
}
|
|
6015
|
-
function ownerDidMatches(a, b) {
|
|
6016
|
-
const aPkh = parsePkhOwnerDid(a);
|
|
6017
|
-
const bPkh = parsePkhOwnerDid(b);
|
|
6018
|
-
if (aPkh && bPkh) {
|
|
6019
|
-
return aPkh.chainId === bPkh.chainId && aPkh.address === bPkh.address;
|
|
6020
|
-
}
|
|
6021
|
-
return a === b;
|
|
6022
|
-
}
|
|
6023
|
-
function networkDiscoveryKey(name) {
|
|
6024
|
-
if (!NETWORK_NAME_RE.test(name)) {
|
|
6025
|
-
throw new NetworkIdError(
|
|
6026
|
-
`network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
6027
|
-
);
|
|
6028
|
-
}
|
|
6029
|
-
return `.well-known/encryption/network/${name}`;
|
|
6030
|
-
}
|
|
6031
|
-
var ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;
|
|
6032
|
-
var NETWORK_NAME_PATTERN = NETWORK_NAME_RE;
|
|
6033
|
-
|
|
6034
6501
|
// src/encryption/types.ts
|
|
6035
6502
|
var DEFAULT_ENCRYPTION_ALG = "x25519-aes256gcm/v1";
|
|
6036
6503
|
var ENVELOPE_VERSION = 1;
|
|
@@ -6912,6 +7379,8 @@ export {
|
|
|
6912
7379
|
SQLAction,
|
|
6913
7380
|
SQLMigrations,
|
|
6914
7381
|
SQLService,
|
|
7382
|
+
STORAGE_FULL_MESSAGE,
|
|
7383
|
+
STORAGE_WRITE_TOO_LARGE_MESSAGE,
|
|
6915
7384
|
SecretsService,
|
|
6916
7385
|
ServiceContext,
|
|
6917
7386
|
ServiceErrorEventSchema,
|
|
@@ -6964,22 +7433,26 @@ export {
|
|
|
6964
7433
|
hexEncode2 as hexEncode,
|
|
6965
7434
|
installTinyCloudDebugGlobals,
|
|
6966
7435
|
isNetworkId,
|
|
7436
|
+
isStorageFullError,
|
|
6967
7437
|
networkDiscoveryKey,
|
|
6968
7438
|
networkError,
|
|
6969
7439
|
notFoundError,
|
|
6970
7440
|
ok,
|
|
6971
7441
|
openWrappedKey,
|
|
6972
7442
|
parseAuthError,
|
|
7443
|
+
parseCapabilityResource,
|
|
6973
7444
|
parseNetworkId,
|
|
6974
7445
|
parsePermissionHint,
|
|
6975
7446
|
parsePermissionHintFromErrorText,
|
|
6976
7447
|
parseSecretCatalogKey,
|
|
7448
|
+
parseStorageQuotaBytes,
|
|
6977
7449
|
permissionDeniedError,
|
|
6978
7450
|
resolveSecretListPrefix,
|
|
6979
7451
|
resolveSecretPath,
|
|
6980
7452
|
serviceError,
|
|
6981
7453
|
storageLimitReachedError,
|
|
6982
7454
|
storageQuotaExceededError,
|
|
7455
|
+
storageRejectionError,
|
|
6983
7456
|
timeoutError,
|
|
6984
7457
|
tinyCloudDebugLogger,
|
|
6985
7458
|
utf8Decode,
|
|
@@ -6993,6 +7466,7 @@ export {
|
|
|
6993
7466
|
validateServiceResponseEvent,
|
|
6994
7467
|
validateServiceSession,
|
|
6995
7468
|
validateTelemetrySpanEvent,
|
|
7469
|
+
validatedCapabilityOf,
|
|
6996
7470
|
verifyDecryptResponse,
|
|
6997
7471
|
wrapError
|
|
6998
7472
|
};
|