@tinycloud/sdk-services 3.1.0-beta.2 → 3.1.0-beta.7
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-C8zRy0ww.d.cts} +73 -1
- package/dist/{BaseService-CuV-ZVPs.d.ts → BaseService-DU-lE-Qx.d.ts} +73 -1
- package/dist/encryption/index.cjs +191 -100
- package/dist/encryption/index.cjs.map +1 -1
- package/dist/encryption/index.d.cts +1 -1
- package/dist/encryption/index.d.ts +1 -1
- package/dist/encryption/index.js +191 -100
- package/dist/encryption/index.js.map +1 -1
- package/dist/index.cjs +446 -177
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -39
- package/dist/index.d.ts +29 -39
- package/dist/index.js +426 -159
- package/dist/index.js.map +1 -1
- package/dist/kv/index.cjs +333 -35
- package/dist/kv/index.cjs.map +1 -1
- package/dist/kv/index.d.cts +26 -12
- package/dist/kv/index.d.ts +26 -12
- package/dist/kv/index.js +333 -35
- package/dist/kv/index.js.map +1 -1
- package/dist/sql/index.cjs +139 -30
- package/dist/sql/index.cjs.map +1 -1
- package/dist/sql/index.d.cts +5 -2
- package/dist/sql/index.d.ts +5 -2
- package/dist/sql/index.js +130 -21
- package/dist/sql/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -896,6 +896,106 @@ var ServiceContext = class {
|
|
|
896
896
|
}
|
|
897
897
|
};
|
|
898
898
|
|
|
899
|
+
// src/errors.ts
|
|
900
|
+
import { CAPABILITY_REGISTRY } from "@tinycloud/bootstrap";
|
|
901
|
+
|
|
902
|
+
// src/encryption/networkId.ts
|
|
903
|
+
var URN_PREFIX = "urn:tinycloud:encryption:";
|
|
904
|
+
var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
905
|
+
var PKH_EIP155_DID_RE = /^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40})$/;
|
|
906
|
+
var NetworkIdError = class extends Error {
|
|
907
|
+
constructor(message) {
|
|
908
|
+
super(message);
|
|
909
|
+
this.name = "NetworkIdError";
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
function parseNetworkId(networkId) {
|
|
913
|
+
if (typeof networkId !== "string" || networkId.length === 0) {
|
|
914
|
+
throw new NetworkIdError("networkId must be a non-empty string");
|
|
915
|
+
}
|
|
916
|
+
if (!networkId.startsWith(URN_PREFIX)) {
|
|
917
|
+
throw new NetworkIdError(
|
|
918
|
+
`networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
const body = networkId.slice(URN_PREFIX.length);
|
|
922
|
+
const lastColon = body.lastIndexOf(":");
|
|
923
|
+
if (lastColon <= 0 || lastColon === body.length - 1) {
|
|
924
|
+
throw new NetworkIdError(
|
|
925
|
+
`networkId missing ownerDid or name segment (got ${JSON.stringify(networkId)})`
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
const ownerDid = body.slice(0, lastColon);
|
|
929
|
+
const name = body.slice(lastColon + 1);
|
|
930
|
+
if (!ownerDid.startsWith("did:")) {
|
|
931
|
+
throw new NetworkIdError(
|
|
932
|
+
`networkId ownerDid must be a DID (got ${JSON.stringify(ownerDid)})`
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
const didParts = ownerDid.split(":");
|
|
936
|
+
if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
|
|
937
|
+
throw new NetworkIdError(
|
|
938
|
+
`networkId ownerDid is not a well-formed DID (got ${JSON.stringify(ownerDid)})`
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
if (!NETWORK_NAME_RE.test(name)) {
|
|
942
|
+
throw new NetworkIdError(
|
|
943
|
+
`networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
944
|
+
);
|
|
945
|
+
}
|
|
946
|
+
return { networkId, ownerDid, name };
|
|
947
|
+
}
|
|
948
|
+
function buildNetworkId(ownerDid, name) {
|
|
949
|
+
if (typeof ownerDid !== "string" || !ownerDid.startsWith("did:")) {
|
|
950
|
+
throw new NetworkIdError("ownerDid must be a DID");
|
|
951
|
+
}
|
|
952
|
+
if (typeof name !== "string" || !NETWORK_NAME_RE.test(name)) {
|
|
953
|
+
throw new NetworkIdError(
|
|
954
|
+
`network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
const networkId = `${URN_PREFIX}${ownerDid}:${name}`;
|
|
958
|
+
parseNetworkId(networkId);
|
|
959
|
+
return networkId;
|
|
960
|
+
}
|
|
961
|
+
function isNetworkId(networkId) {
|
|
962
|
+
if (typeof networkId !== "string") {
|
|
963
|
+
return false;
|
|
964
|
+
}
|
|
965
|
+
try {
|
|
966
|
+
parseNetworkId(networkId);
|
|
967
|
+
return true;
|
|
968
|
+
} catch {
|
|
969
|
+
return false;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
function parsePkhOwnerDid(ownerDid) {
|
|
973
|
+
const match = ownerDid.match(PKH_EIP155_DID_RE);
|
|
974
|
+
if (!match) return null;
|
|
975
|
+
return {
|
|
976
|
+
chainId: match[1],
|
|
977
|
+
address: match[2].toLowerCase()
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
function ownerDidMatches(a, b) {
|
|
981
|
+
const aPkh = parsePkhOwnerDid(a);
|
|
982
|
+
const bPkh = parsePkhOwnerDid(b);
|
|
983
|
+
if (aPkh && bPkh) {
|
|
984
|
+
return aPkh.chainId === bPkh.chainId && aPkh.address === bPkh.address;
|
|
985
|
+
}
|
|
986
|
+
return a === b;
|
|
987
|
+
}
|
|
988
|
+
function networkDiscoveryKey(name) {
|
|
989
|
+
if (!NETWORK_NAME_RE.test(name)) {
|
|
990
|
+
throw new NetworkIdError(
|
|
991
|
+
`network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
return `.well-known/encryption/network/${name}`;
|
|
995
|
+
}
|
|
996
|
+
var ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;
|
|
997
|
+
var NETWORK_NAME_PATTERN = NETWORK_NAME_RE;
|
|
998
|
+
|
|
899
999
|
// src/errors.ts
|
|
900
1000
|
function parsePermissionHint(value) {
|
|
901
1001
|
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
@@ -955,11 +1055,31 @@ function networkError(service, message, cause) {
|
|
|
955
1055
|
cause
|
|
956
1056
|
};
|
|
957
1057
|
}
|
|
958
|
-
|
|
1058
|
+
var RequestTimeoutError = class extends Error {
|
|
1059
|
+
constructor(timeoutMs) {
|
|
1060
|
+
super(`Request timed out after ${timeoutMs}ms.`);
|
|
1061
|
+
this.name = "TimeoutError";
|
|
1062
|
+
this.timeoutMs = timeoutMs;
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
function isRequestTimeoutError(error) {
|
|
1066
|
+
return error instanceof RequestTimeoutError || error instanceof Error && error.name === "TimeoutError" && typeof error.timeoutMs === "number";
|
|
1067
|
+
}
|
|
1068
|
+
function timeoutError(service, timeoutMs, cause) {
|
|
1069
|
+
if (timeoutMs === void 0) {
|
|
1070
|
+
return {
|
|
1071
|
+
code: ErrorCodes.TIMEOUT,
|
|
1072
|
+
message: "Request timed out.",
|
|
1073
|
+
service,
|
|
1074
|
+
...cause === void 0 ? {} : { cause }
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
959
1077
|
return {
|
|
960
1078
|
code: ErrorCodes.TIMEOUT,
|
|
961
|
-
message:
|
|
962
|
-
service
|
|
1079
|
+
message: `Request timed out after ${timeoutMs}ms.`,
|
|
1080
|
+
service,
|
|
1081
|
+
...cause === void 0 ? {} : { cause },
|
|
1082
|
+
meta: { timeoutMs }
|
|
963
1083
|
};
|
|
964
1084
|
}
|
|
965
1085
|
function abortedError(service) {
|
|
@@ -993,6 +1113,70 @@ function parseAuthError(responseText) {
|
|
|
993
1113
|
function authUnauthorizedError(service, message, meta) {
|
|
994
1114
|
return serviceError(ErrorCodes.AUTH_UNAUTHORIZED, message, service, { meta });
|
|
995
1115
|
}
|
|
1116
|
+
var grantableActions = new Set(
|
|
1117
|
+
CAPABILITY_REGISTRY.map(({ urn }) => urn).filter((urn) => !urn.endsWith("/*"))
|
|
1118
|
+
);
|
|
1119
|
+
var capabilityServices = new Set(
|
|
1120
|
+
CAPABILITY_REGISTRY.map(({ service }) => service.slice("tinycloud.".length))
|
|
1121
|
+
);
|
|
1122
|
+
function parseCapabilityResource(resource, service) {
|
|
1123
|
+
if (resource.length === 0 || resource.length > 1024 || !capabilityServices.has(service) || /[\s\p{C}*#]/u.test(resource) || resource.includes("://")) return void 0;
|
|
1124
|
+
const segments = resource.split("/");
|
|
1125
|
+
if (!/^tinycloud:[A-Za-z0-9][A-Za-z0-9:._-]*$/.test(segments[0])) return void 0;
|
|
1126
|
+
const serviceIndex = segments.findIndex(
|
|
1127
|
+
(segment, index) => index > 0 && capabilityServices.has(segment)
|
|
1128
|
+
);
|
|
1129
|
+
if (serviceIndex < 1 || segments[serviceIndex] !== service) return void 0;
|
|
1130
|
+
if (segments.slice(1, serviceIndex).some(
|
|
1131
|
+
(segment) => segment.length === 0 || segment === "." || segment === ".."
|
|
1132
|
+
)) return void 0;
|
|
1133
|
+
const pathSegments = segments.slice(serviceIndex + 1);
|
|
1134
|
+
if (pathSegments.some((segment) => segment === "." || segment === "..")) return void 0;
|
|
1135
|
+
if (service !== "kv" && (pathSegments.length === 0 || pathSegments[pathSegments.length - 1] === "")) return void 0;
|
|
1136
|
+
return {
|
|
1137
|
+
space: segments.slice(0, serviceIndex).join("/"),
|
|
1138
|
+
path: pathSegments.join("/")
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
function safeCapabilityResource(value, service, action) {
|
|
1142
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 1024) return false;
|
|
1143
|
+
if (service === "encryption" && value.startsWith("urn:tinycloud:encryption:")) {
|
|
1144
|
+
if (!/^[A-Za-z0-9_][A-Za-z0-9_.:/-]*$/.test(value) || value.includes("//") || value.split("/").some((segment) => segment === "." || segment === "..")) return false;
|
|
1145
|
+
try {
|
|
1146
|
+
parseNetworkId(value);
|
|
1147
|
+
return true;
|
|
1148
|
+
} catch {
|
|
1149
|
+
return false;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
const parsed = parseCapabilityResource(value, service);
|
|
1153
|
+
return parsed !== void 0 && (!parsed.path.endsWith("/") || service === "kv" && action === "tinycloud.kv/list") && (parsed.path !== "" || service === "kv" && action === "tinycloud.kv/list");
|
|
1154
|
+
}
|
|
1155
|
+
function validatedCapabilityOf(error) {
|
|
1156
|
+
const verdict = authorizationVerdictOf(error);
|
|
1157
|
+
if (verdict !== "unauthenticated" && verdict !== "forbidden") return void 0;
|
|
1158
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1159
|
+
let node = error;
|
|
1160
|
+
for (let depth = 0; depth < MAX_CAUSE_DEPTH && typeof node === "object" && node !== null && !seen.has(node); depth += 1) {
|
|
1161
|
+
seen.add(node);
|
|
1162
|
+
const record = node;
|
|
1163
|
+
const status = errorStatusOf(record);
|
|
1164
|
+
const meta = record.meta;
|
|
1165
|
+
if ((status === 401 || status === 403 || status === void 0 && record.code === ErrorCodes.AUTH_UNAUTHORIZED) && typeof meta === "object" && meta !== null && !Array.isArray(meta)) {
|
|
1166
|
+
const metadata = meta;
|
|
1167
|
+
const { resource, requiredAction } = metadata;
|
|
1168
|
+
if (typeof requiredAction === "string" && grantableActions.has(requiredAction)) {
|
|
1169
|
+
const actionService = requiredAction.slice("tinycloud.".length, requiredAction.indexOf("/"));
|
|
1170
|
+
const explicitService = record.service === void 0 ? metadata.service : record.service;
|
|
1171
|
+
if (safeCapabilityResource(resource, actionService, requiredAction) && (explicitService === void 0 || explicitService === actionService || explicitService === `tinycloud.${actionService}`)) {
|
|
1172
|
+
return { resource, requiredAction };
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
node = record.cause;
|
|
1177
|
+
}
|
|
1178
|
+
return void 0;
|
|
1179
|
+
}
|
|
996
1180
|
var MAX_CAUSE_DEPTH = 8;
|
|
997
1181
|
function errorStatusOf(node) {
|
|
998
1182
|
const meta = node.meta;
|
|
@@ -1059,6 +1243,9 @@ function isStorageFullError(error) {
|
|
|
1059
1243
|
}
|
|
1060
1244
|
function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
|
|
1061
1245
|
if (error instanceof Error) {
|
|
1246
|
+
if (isRequestTimeoutError(error)) {
|
|
1247
|
+
return timeoutError(service, error.timeoutMs, error);
|
|
1248
|
+
}
|
|
1062
1249
|
if (error.name === "AbortError") {
|
|
1063
1250
|
return abortedError(service);
|
|
1064
1251
|
}
|
|
@@ -1083,6 +1270,7 @@ function errorResult(error) {
|
|
|
1083
1270
|
}
|
|
1084
1271
|
|
|
1085
1272
|
// src/base/BaseService.ts
|
|
1273
|
+
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
1086
1274
|
var BaseService = class {
|
|
1087
1275
|
constructor() {
|
|
1088
1276
|
/**
|
|
@@ -1263,6 +1451,64 @@ var BaseService = class {
|
|
|
1263
1451
|
}
|
|
1264
1452
|
return controller.signal;
|
|
1265
1453
|
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Create the abort signal for one request, bounded by the request timeout.
|
|
1456
|
+
*
|
|
1457
|
+
* The signal aborts when the service signs out, the context aborts, the
|
|
1458
|
+
* caller's `signal` aborts, or the timeout elapses, whichever comes first.
|
|
1459
|
+
* A timeout aborts with a {@link RequestTimeoutError} (`TimeoutError`), which
|
|
1460
|
+
* `wrapError` maps to `ErrorCodes.TIMEOUT`; the other sources keep their own
|
|
1461
|
+
* reason (normally `AbortError`, mapped to `ErrorCodes.ABORTED`).
|
|
1462
|
+
*
|
|
1463
|
+
* The timeout is `timeoutMs` when given, otherwise `config.timeout`. Only a
|
|
1464
|
+
* positive, finite value (at most 2^31-1 ms) applies; `undefined`, `0`, and
|
|
1465
|
+
* `Infinity` mean no timeout, so a per-call `0` opts out of a configured one.
|
|
1466
|
+
*
|
|
1467
|
+
* Callers must call `dispose()` once the response body has been consumed,
|
|
1468
|
+
* which clears the timer and removes the listeners this adds to the
|
|
1469
|
+
* long-lived service and context signals.
|
|
1470
|
+
*
|
|
1471
|
+
* @param signal - The caller's abort signal for this request
|
|
1472
|
+
* @param timeoutMs - Per-request timeout override in milliseconds
|
|
1473
|
+
*/
|
|
1474
|
+
createRequestSignal(signal, timeoutMs) {
|
|
1475
|
+
const controller = new AbortController();
|
|
1476
|
+
const detachers = [];
|
|
1477
|
+
let timer;
|
|
1478
|
+
const dispose = () => {
|
|
1479
|
+
if (timer !== void 0) {
|
|
1480
|
+
clearTimeout(timer);
|
|
1481
|
+
timer = void 0;
|
|
1482
|
+
}
|
|
1483
|
+
for (const detach of detachers.splice(0)) {
|
|
1484
|
+
detach();
|
|
1485
|
+
}
|
|
1486
|
+
};
|
|
1487
|
+
const abort = (reason) => {
|
|
1488
|
+
dispose();
|
|
1489
|
+
controller.abort(reason);
|
|
1490
|
+
};
|
|
1491
|
+
const requestSignal = { signal: controller.signal, dispose };
|
|
1492
|
+
const parents = [
|
|
1493
|
+
this.abortController.signal,
|
|
1494
|
+
this.context?.abortSignal,
|
|
1495
|
+
signal
|
|
1496
|
+
].filter((parent) => parent !== void 0);
|
|
1497
|
+
for (const parent of parents) {
|
|
1498
|
+
if (parent.aborted) {
|
|
1499
|
+
abort(parent.reason);
|
|
1500
|
+
return requestSignal;
|
|
1501
|
+
}
|
|
1502
|
+
const onAbort = () => abort(parent.reason);
|
|
1503
|
+
parent.addEventListener("abort", onAbort, { once: true });
|
|
1504
|
+
detachers.push(() => parent.removeEventListener("abort", onAbort));
|
|
1505
|
+
}
|
|
1506
|
+
const timeout = timeoutMs ?? this._config.timeout;
|
|
1507
|
+
if (typeof timeout === "number" && timeout > 0 && timeout <= MAX_TIMER_DELAY_MS) {
|
|
1508
|
+
timer = setTimeout(() => abort(new RequestTimeoutError(timeout)), timeout);
|
|
1509
|
+
}
|
|
1510
|
+
return requestSignal;
|
|
1511
|
+
}
|
|
1266
1512
|
/**
|
|
1267
1513
|
* Wrap an operation with error handling and telemetry.
|
|
1268
1514
|
*
|
|
@@ -1523,7 +1769,7 @@ var KVService = class extends BaseService {
|
|
|
1523
1769
|
* @param path - Resource path
|
|
1524
1770
|
* @param action - KV action
|
|
1525
1771
|
* @param body - Optional request body
|
|
1526
|
-
* @param signal -
|
|
1772
|
+
* @param signal - Request signal from createRequestSignal()
|
|
1527
1773
|
* @returns Fetch response
|
|
1528
1774
|
*/
|
|
1529
1775
|
async invokeOperation(path, action, body, signal, extraHeaders) {
|
|
@@ -1539,7 +1785,7 @@ var KVService = class extends BaseService {
|
|
|
1539
1785
|
method: "POST",
|
|
1540
1786
|
headers: requestHeaders,
|
|
1541
1787
|
body,
|
|
1542
|
-
signal
|
|
1788
|
+
signal
|
|
1543
1789
|
});
|
|
1544
1790
|
}
|
|
1545
1791
|
/**
|
|
@@ -1716,6 +1962,7 @@ var KVService = class extends BaseService {
|
|
|
1716
1962
|
"kv"
|
|
1717
1963
|
));
|
|
1718
1964
|
}
|
|
1965
|
+
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
1719
1966
|
try {
|
|
1720
1967
|
const session = this.context.session;
|
|
1721
1968
|
const invocationHeaders = this.context.invokeAny(
|
|
@@ -1737,15 +1984,17 @@ var KVService = class extends BaseService {
|
|
|
1737
1984
|
const response = await this.context.fetch(`${this.host}/invoke`, {
|
|
1738
1985
|
method: "POST",
|
|
1739
1986
|
headers,
|
|
1740
|
-
signal:
|
|
1987
|
+
signal: request.signal
|
|
1741
1988
|
});
|
|
1742
1989
|
if (!response.ok) {
|
|
1743
|
-
const errorText = await response.text();
|
|
1990
|
+
const errorText = response.status === 401 || response.status === 403 ? await this.readAuthorizationText(response) : await response.text();
|
|
1744
1991
|
if (response.status === 401 || response.status === 403) {
|
|
1745
1992
|
return this.authorizationFailure(
|
|
1746
1993
|
`Failed to batch read ${keys.length} key(s)`,
|
|
1747
1994
|
response,
|
|
1748
|
-
errorText
|
|
1995
|
+
errorText,
|
|
1996
|
+
paths,
|
|
1997
|
+
action
|
|
1749
1998
|
);
|
|
1750
1999
|
}
|
|
1751
2000
|
if (response.status === 413) {
|
|
@@ -1800,6 +2049,8 @@ var KVService = class extends BaseService {
|
|
|
1800
2049
|
return ok({ results, count: results.length });
|
|
1801
2050
|
} catch (error) {
|
|
1802
2051
|
return err(wrapError("kv", error));
|
|
2052
|
+
} finally {
|
|
2053
|
+
request.dispose();
|
|
1803
2054
|
}
|
|
1804
2055
|
}
|
|
1805
2056
|
/**
|
|
@@ -1835,7 +2086,7 @@ var KVService = class extends BaseService {
|
|
|
1835
2086
|
return text;
|
|
1836
2087
|
}
|
|
1837
2088
|
}
|
|
1838
|
-
async createSignedReadUrlError(response, key) {
|
|
2089
|
+
async createSignedReadUrlError(response, key, path) {
|
|
1839
2090
|
let errorText = response.statusText;
|
|
1840
2091
|
try {
|
|
1841
2092
|
const text = await response.text();
|
|
@@ -1848,7 +2099,9 @@ var KVService = class extends BaseService {
|
|
|
1848
2099
|
return this.authorizationFailure(
|
|
1849
2100
|
`Failed to create signed read URL for key ${JSON.stringify(key)}`,
|
|
1850
2101
|
response,
|
|
1851
|
-
errorText
|
|
2102
|
+
errorText,
|
|
2103
|
+
[path],
|
|
2104
|
+
KVAction.GET
|
|
1852
2105
|
);
|
|
1853
2106
|
}
|
|
1854
2107
|
const code = response.status === 400 ? ErrorCodes.INVALID_INPUT : ErrorCodes.NETWORK_ERROR;
|
|
@@ -1861,21 +2114,42 @@ var KVService = class extends BaseService {
|
|
|
1861
2114
|
)
|
|
1862
2115
|
);
|
|
1863
2116
|
}
|
|
2117
|
+
/** A known 401/403 keeps its typed status even if reading its body fails. */
|
|
2118
|
+
async readAuthorizationText(response) {
|
|
2119
|
+
try {
|
|
2120
|
+
return await response.text();
|
|
2121
|
+
} catch {
|
|
2122
|
+
return "";
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
1864
2125
|
/**
|
|
1865
2126
|
* AUTH_UNAUTHORIZED for a 401/403 response. The message keeps the HTTP
|
|
1866
2127
|
* status and the server text (`<context>: <status> - <text>`, the same
|
|
1867
2128
|
* shape as every other KV failure) so callers that rethrow only the message
|
|
1868
2129
|
* still see the status; `meta.status` carries it for typed callers.
|
|
1869
|
-
*
|
|
2130
|
+
* Server hints must match the requested path/action (whether the node sends
|
|
2131
|
+
* a relative path or full resource); metadata uses only the request-derived
|
|
2132
|
+
* canonical resource under the authenticated session's space.
|
|
1870
2133
|
*/
|
|
1871
|
-
authorizationFailure(context, response, errorText, extraMeta = {}) {
|
|
2134
|
+
authorizationFailure(context, response, errorText, paths, expectedAction, extraMeta = {}) {
|
|
1872
2135
|
const { resource, action } = parseAuthError(errorText);
|
|
2136
|
+
const requestedPath = paths.find(
|
|
2137
|
+
(path) => resource === path || resource === `${this.context.session.spaceId}/kv/${path}`
|
|
2138
|
+
);
|
|
2139
|
+
const capability = requestedPath !== void 0 && action === expectedAction ? validatedCapabilityOf({
|
|
2140
|
+
service: "kv",
|
|
2141
|
+
code: ErrorCodes.AUTH_UNAUTHORIZED,
|
|
2142
|
+
meta: {
|
|
2143
|
+
status: response.status,
|
|
2144
|
+
resource: `${this.context.session.spaceId}/kv/${requestedPath}`,
|
|
2145
|
+
requiredAction: action
|
|
2146
|
+
}
|
|
2147
|
+
}) : void 0;
|
|
1873
2148
|
const detail = errorText.trim().length > 0 ? errorText : response.statusText || "authorization failed";
|
|
1874
2149
|
return err(authUnauthorizedError("kv", `${context}: ${response.status} - ${detail}`, {
|
|
1875
2150
|
status: response.status,
|
|
1876
|
-
...
|
|
1877
|
-
...
|
|
1878
|
-
...extraMeta
|
|
2151
|
+
...extraMeta,
|
|
2152
|
+
...capability
|
|
1879
2153
|
}));
|
|
1880
2154
|
}
|
|
1881
2155
|
normalizeSignedReadUrlResponse(data) {
|
|
@@ -1909,23 +2183,35 @@ var KVService = class extends BaseService {
|
|
|
1909
2183
|
));
|
|
1910
2184
|
}
|
|
1911
2185
|
const path = this.getFullPath(key, options?.prefix);
|
|
2186
|
+
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
1912
2187
|
try {
|
|
1913
2188
|
const response = await this.invokeOperation(
|
|
1914
2189
|
path,
|
|
1915
2190
|
KVAction.GET,
|
|
1916
2191
|
void 0,
|
|
1917
|
-
|
|
2192
|
+
request.signal,
|
|
1918
2193
|
options?.maxResponseBytes === void 0 ? void 0 : { "x-tinycloud-max-response-bytes": String(options.maxResponseBytes) }
|
|
1919
2194
|
);
|
|
1920
2195
|
if (!response.ok) {
|
|
1921
2196
|
if (response.status === 401 || response.status === 403) {
|
|
1922
|
-
const errorText2 = await
|
|
2197
|
+
const errorText2 = await this.readAuthorizationText(response);
|
|
1923
2198
|
const permissionHint = parsePermissionHintFromErrorText(errorText2);
|
|
2199
|
+
const structuredCapability = permissionHint?.service === "tinycloud.kv" && permissionHint.space === this.context.session.spaceId && permissionHint.path === path ? validatedCapabilityOf({
|
|
2200
|
+
service: "kv",
|
|
2201
|
+
code: ErrorCodes.AUTH_UNAUTHORIZED,
|
|
2202
|
+
meta: {
|
|
2203
|
+
status: response.status,
|
|
2204
|
+
resource: `${this.context.session.spaceId}/kv/${path}`,
|
|
2205
|
+
requiredAction: KVAction.GET
|
|
2206
|
+
}
|
|
2207
|
+
}) : void 0;
|
|
1924
2208
|
return this.authorizationFailure(
|
|
1925
2209
|
`Failed to get key ${JSON.stringify(key)}`,
|
|
1926
2210
|
response,
|
|
1927
2211
|
errorText2,
|
|
1928
|
-
|
|
2212
|
+
[path],
|
|
2213
|
+
KVAction.GET,
|
|
2214
|
+
structuredCapability ? { permissionHint, ...structuredCapability } : {}
|
|
1929
2215
|
);
|
|
1930
2216
|
}
|
|
1931
2217
|
if (response.status === 404) {
|
|
@@ -1960,6 +2246,8 @@ var KVService = class extends BaseService {
|
|
|
1960
2246
|
});
|
|
1961
2247
|
} catch (error) {
|
|
1962
2248
|
return err(wrapError("kv", error));
|
|
2249
|
+
} finally {
|
|
2250
|
+
request.dispose();
|
|
1963
2251
|
}
|
|
1964
2252
|
});
|
|
1965
2253
|
}
|
|
@@ -1987,12 +2275,13 @@ var KVService = class extends BaseService {
|
|
|
1987
2275
|
}
|
|
1988
2276
|
const path = this.getFullPath(key, options?.prefix);
|
|
1989
2277
|
const body = this.serializePutValue(value, options?.contentType);
|
|
2278
|
+
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
1990
2279
|
try {
|
|
1991
2280
|
const response = await this.invokeOperation(
|
|
1992
2281
|
path,
|
|
1993
2282
|
KVAction.PUT,
|
|
1994
2283
|
body,
|
|
1995
|
-
|
|
2284
|
+
request.signal,
|
|
1996
2285
|
{
|
|
1997
2286
|
...options?.ifMatch === void 0 ? {} : { "if-match": options.ifMatch },
|
|
1998
2287
|
...options?.ifNoneMatch === void 0 ? {} : { "if-none-match": options.ifNoneMatch }
|
|
@@ -2002,7 +2291,9 @@ var KVService = class extends BaseService {
|
|
|
2002
2291
|
return this.authorizationFailure(
|
|
2003
2292
|
`Failed to put key ${JSON.stringify(key)}`,
|
|
2004
2293
|
response,
|
|
2005
|
-
await
|
|
2294
|
+
await this.readAuthorizationText(response),
|
|
2295
|
+
[path],
|
|
2296
|
+
KVAction.PUT
|
|
2006
2297
|
);
|
|
2007
2298
|
}
|
|
2008
2299
|
if (!response.ok) {
|
|
@@ -2046,6 +2337,8 @@ var KVService = class extends BaseService {
|
|
|
2046
2337
|
});
|
|
2047
2338
|
} catch (error) {
|
|
2048
2339
|
return err(wrapError("kv", error));
|
|
2340
|
+
} finally {
|
|
2341
|
+
request.dispose();
|
|
2049
2342
|
}
|
|
2050
2343
|
});
|
|
2051
2344
|
}
|
|
@@ -2085,6 +2378,7 @@ var KVService = class extends BaseService {
|
|
|
2085
2378
|
seen.add(path);
|
|
2086
2379
|
}
|
|
2087
2380
|
let requestMayHaveDispatched = false;
|
|
2381
|
+
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
2088
2382
|
try {
|
|
2089
2383
|
const body = new FormData();
|
|
2090
2384
|
for (let index = 0; index < items.length; index++) {
|
|
@@ -2107,7 +2401,7 @@ var KVService = class extends BaseService {
|
|
|
2107
2401
|
method: "POST",
|
|
2108
2402
|
headers,
|
|
2109
2403
|
body,
|
|
2110
|
-
signal:
|
|
2404
|
+
signal: request.signal
|
|
2111
2405
|
};
|
|
2112
2406
|
const fetchFn = this.context.fetch;
|
|
2113
2407
|
requestMayHaveDispatched = true;
|
|
@@ -2131,7 +2425,9 @@ var KVService = class extends BaseService {
|
|
|
2131
2425
|
return this.authorizationFailure(
|
|
2132
2426
|
`Failed to batch put ${items.length} key(s)`,
|
|
2133
2427
|
response,
|
|
2134
|
-
errorText
|
|
2428
|
+
errorText,
|
|
2429
|
+
paths,
|
|
2430
|
+
KVAction.PUT
|
|
2135
2431
|
);
|
|
2136
2432
|
}
|
|
2137
2433
|
const quotaError = this.handleQuotaErrorResponse(
|
|
@@ -2156,14 +2452,16 @@ var KVService = class extends BaseService {
|
|
|
2156
2452
|
rawBody = await response.json();
|
|
2157
2453
|
} catch (jsonError) {
|
|
2158
2454
|
const cause = jsonError instanceof Error ? jsonError : new Error(String(jsonError));
|
|
2455
|
+
const timedOut = isRequestTimeoutError(cause);
|
|
2159
2456
|
return err(
|
|
2160
2457
|
serviceError(
|
|
2161
|
-
ErrorCodes.NETWORK_ERROR,
|
|
2162
|
-
`KV batchPut response was not valid JSON: ${cause.message}`,
|
|
2458
|
+
timedOut ? ErrorCodes.TIMEOUT : ErrorCodes.NETWORK_ERROR,
|
|
2459
|
+
timedOut ? `KV batchPut response body did not arrive in time: ${cause.message}` : `KV batchPut response was not valid JSON: ${cause.message}`,
|
|
2163
2460
|
"kv",
|
|
2164
2461
|
{
|
|
2165
2462
|
cause,
|
|
2166
2463
|
meta: {
|
|
2464
|
+
...timedOut ? { timeoutMs: cause.timeoutMs } : {},
|
|
2167
2465
|
requestMayHaveDispatched: true,
|
|
2168
2466
|
responseReceived: true,
|
|
2169
2467
|
status: response.status,
|
|
@@ -2218,6 +2516,8 @@ var KVService = class extends BaseService {
|
|
|
2218
2516
|
...wrapped,
|
|
2219
2517
|
meta: { ...wrapped.meta, requestMayHaveDispatched }
|
|
2220
2518
|
});
|
|
2519
|
+
} finally {
|
|
2520
|
+
request.dispose();
|
|
2221
2521
|
}
|
|
2222
2522
|
});
|
|
2223
2523
|
}
|
|
@@ -2240,23 +2540,26 @@ var KVService = class extends BaseService {
|
|
|
2240
2540
|
if (options?.path) {
|
|
2241
2541
|
listPath = listPath ? `${listPath}/${options.path}` : options.path;
|
|
2242
2542
|
}
|
|
2543
|
+
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
2243
2544
|
try {
|
|
2244
2545
|
const response = await this.invokeOperation(
|
|
2245
2546
|
listPath,
|
|
2246
2547
|
KVAction.LIST,
|
|
2247
2548
|
void 0,
|
|
2248
|
-
|
|
2549
|
+
request.signal,
|
|
2249
2550
|
options?.limit === void 0 && options?.cursor === void 0 ? void 0 : {
|
|
2250
2551
|
...options?.limit === void 0 ? {} : { "x-tinycloud-limit": String(options.limit) },
|
|
2251
2552
|
...options?.cursor === void 0 ? {} : { "x-tinycloud-cursor": options.cursor }
|
|
2252
2553
|
}
|
|
2253
2554
|
);
|
|
2254
2555
|
if (!response.ok) {
|
|
2255
|
-
if (response.status === 401) {
|
|
2556
|
+
if (response.status === 401 || response.status === 403) {
|
|
2256
2557
|
return this.authorizationFailure(
|
|
2257
2558
|
"Failed to list keys",
|
|
2258
2559
|
response,
|
|
2259
|
-
await
|
|
2560
|
+
await this.readAuthorizationText(response),
|
|
2561
|
+
[listPath],
|
|
2562
|
+
KVAction.LIST
|
|
2260
2563
|
);
|
|
2261
2564
|
}
|
|
2262
2565
|
const errorText = await response.text();
|
|
@@ -2284,6 +2587,8 @@ var KVService = class extends BaseService {
|
|
|
2284
2587
|
});
|
|
2285
2588
|
} catch (error) {
|
|
2286
2589
|
return err(wrapError("kv", error));
|
|
2590
|
+
} finally {
|
|
2591
|
+
request.dispose();
|
|
2287
2592
|
}
|
|
2288
2593
|
});
|
|
2289
2594
|
}
|
|
@@ -2296,20 +2601,23 @@ var KVService = class extends BaseService {
|
|
|
2296
2601
|
return err(authRequiredError("kv"));
|
|
2297
2602
|
}
|
|
2298
2603
|
const path = this.getFullPath(key, options?.prefix);
|
|
2604
|
+
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
2299
2605
|
try {
|
|
2300
2606
|
const response = await this.invokeOperation(
|
|
2301
2607
|
path,
|
|
2302
2608
|
KVAction.DELETE,
|
|
2303
2609
|
void 0,
|
|
2304
|
-
|
|
2610
|
+
request.signal,
|
|
2305
2611
|
options?.ifMatch === void 0 ? void 0 : { "if-match": options.ifMatch }
|
|
2306
2612
|
);
|
|
2307
2613
|
if (!response.ok) {
|
|
2308
|
-
if (response.status === 401) {
|
|
2614
|
+
if (response.status === 401 || response.status === 403) {
|
|
2309
2615
|
return this.authorizationFailure(
|
|
2310
2616
|
`Failed to delete key ${JSON.stringify(key)}`,
|
|
2311
2617
|
response,
|
|
2312
|
-
await
|
|
2618
|
+
await this.readAuthorizationText(response),
|
|
2619
|
+
[path],
|
|
2620
|
+
KVAction.DELETE
|
|
2313
2621
|
);
|
|
2314
2622
|
}
|
|
2315
2623
|
if (response.status === 404) {
|
|
@@ -2347,6 +2655,8 @@ var KVService = class extends BaseService {
|
|
|
2347
2655
|
});
|
|
2348
2656
|
} catch (error) {
|
|
2349
2657
|
return err(wrapError("kv", error));
|
|
2658
|
+
} finally {
|
|
2659
|
+
request.dispose();
|
|
2350
2660
|
}
|
|
2351
2661
|
});
|
|
2352
2662
|
}
|
|
@@ -2359,19 +2669,22 @@ var KVService = class extends BaseService {
|
|
|
2359
2669
|
return err(authRequiredError("kv"));
|
|
2360
2670
|
}
|
|
2361
2671
|
const path = this.getFullPath(key, options?.prefix);
|
|
2672
|
+
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
2362
2673
|
try {
|
|
2363
2674
|
const response = await this.invokeOperation(
|
|
2364
2675
|
path,
|
|
2365
2676
|
KVAction.HEAD,
|
|
2366
2677
|
void 0,
|
|
2367
|
-
|
|
2678
|
+
request.signal
|
|
2368
2679
|
);
|
|
2369
2680
|
if (!response.ok) {
|
|
2370
|
-
if (response.status === 401) {
|
|
2681
|
+
if (response.status === 401 || response.status === 403) {
|
|
2371
2682
|
return this.authorizationFailure(
|
|
2372
2683
|
`Failed to get metadata for key ${JSON.stringify(key)}`,
|
|
2373
2684
|
response,
|
|
2374
|
-
await
|
|
2685
|
+
await this.readAuthorizationText(response),
|
|
2686
|
+
[path],
|
|
2687
|
+
KVAction.HEAD
|
|
2375
2688
|
);
|
|
2376
2689
|
}
|
|
2377
2690
|
if (response.status === 404) {
|
|
@@ -2393,6 +2706,8 @@ var KVService = class extends BaseService {
|
|
|
2393
2706
|
});
|
|
2394
2707
|
} catch (error) {
|
|
2395
2708
|
return err(wrapError("kv", error));
|
|
2709
|
+
} finally {
|
|
2710
|
+
request.dispose();
|
|
2396
2711
|
}
|
|
2397
2712
|
});
|
|
2398
2713
|
}
|
|
@@ -2430,15 +2745,16 @@ var KVService = class extends BaseService {
|
|
|
2430
2745
|
if (options?.etag !== void 0) {
|
|
2431
2746
|
body.etag = options.etag;
|
|
2432
2747
|
}
|
|
2748
|
+
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
2433
2749
|
try {
|
|
2434
2750
|
const response = await this.context.fetch(`${this.host}/signed/kv`, {
|
|
2435
2751
|
method: "POST",
|
|
2436
2752
|
headers: this.withJsonContentType(headers),
|
|
2437
2753
|
body: JSON.stringify(body),
|
|
2438
|
-
signal:
|
|
2754
|
+
signal: request.signal
|
|
2439
2755
|
});
|
|
2440
2756
|
if (!response.ok) {
|
|
2441
|
-
return this.createSignedReadUrlError(response, key);
|
|
2757
|
+
return this.createSignedReadUrlError(response, key, path);
|
|
2442
2758
|
}
|
|
2443
2759
|
const signedUrl = this.normalizeSignedReadUrlResponse(
|
|
2444
2760
|
await response.json()
|
|
@@ -2455,6 +2771,8 @@ var KVService = class extends BaseService {
|
|
|
2455
2771
|
return ok(signedUrl);
|
|
2456
2772
|
} catch (error) {
|
|
2457
2773
|
return err(wrapError("kv", error));
|
|
2774
|
+
} finally {
|
|
2775
|
+
request.dispose();
|
|
2458
2776
|
}
|
|
2459
2777
|
});
|
|
2460
2778
|
}
|
|
@@ -2668,6 +2986,7 @@ var SQLService = class extends BaseService {
|
|
|
2668
2986
|
if (!this.requireAuth()) {
|
|
2669
2987
|
return err(authRequiredError("sql"));
|
|
2670
2988
|
}
|
|
2989
|
+
const request = this.createRequestSignal(options?.signal);
|
|
2671
2990
|
try {
|
|
2672
2991
|
const body = {
|
|
2673
2992
|
action: "query",
|
|
@@ -2676,19 +2995,22 @@ var SQLService = class extends BaseService {
|
|
|
2676
2995
|
};
|
|
2677
2996
|
if (options?.maxRows !== void 0) body.maxRows = options.maxRows;
|
|
2678
2997
|
if (options?.maxBytes !== void 0) body.maxBytes = options.maxBytes;
|
|
2998
|
+
const action = this.actionForSql(sql, SQLAction.READ);
|
|
2679
2999
|
const response = await this.invokeSQL(
|
|
2680
3000
|
dbName,
|
|
2681
|
-
|
|
3001
|
+
action,
|
|
2682
3002
|
body,
|
|
2683
|
-
|
|
3003
|
+
request.signal
|
|
2684
3004
|
);
|
|
2685
3005
|
if (!response.ok) {
|
|
2686
|
-
return this.handleErrorResponse(response, "query");
|
|
3006
|
+
return this.handleErrorResponse(response, "query", dbName, [action]);
|
|
2687
3007
|
}
|
|
2688
3008
|
const data = await response.json();
|
|
2689
3009
|
return ok(data);
|
|
2690
3010
|
} catch (error) {
|
|
2691
3011
|
return err(wrapError("sql", error));
|
|
3012
|
+
} finally {
|
|
3013
|
+
request.dispose();
|
|
2692
3014
|
}
|
|
2693
3015
|
});
|
|
2694
3016
|
}
|
|
@@ -2697,6 +3019,7 @@ var SQLService = class extends BaseService {
|
|
|
2697
3019
|
if (!this.requireAuth()) {
|
|
2698
3020
|
return err(authRequiredError("sql"));
|
|
2699
3021
|
}
|
|
3022
|
+
const request = this.createRequestSignal(options?.signal);
|
|
2700
3023
|
try {
|
|
2701
3024
|
const body = {
|
|
2702
3025
|
action: "execute",
|
|
@@ -2712,19 +3035,22 @@ var SQLService = class extends BaseService {
|
|
|
2712
3035
|
(statement) => this.actionForSql(statement, SQLAction.SCHEMA)
|
|
2713
3036
|
)
|
|
2714
3037
|
];
|
|
3038
|
+
const requestedActions = this.dedupeActions(actions);
|
|
2715
3039
|
const response = await this.invokeSQL(
|
|
2716
3040
|
dbName,
|
|
2717
|
-
|
|
3041
|
+
requestedActions,
|
|
2718
3042
|
body,
|
|
2719
|
-
|
|
3043
|
+
request.signal
|
|
2720
3044
|
);
|
|
2721
3045
|
if (!response.ok) {
|
|
2722
|
-
return this.handleErrorResponse(response, "execute");
|
|
3046
|
+
return this.handleErrorResponse(response, "execute", dbName, requestedActions);
|
|
2723
3047
|
}
|
|
2724
3048
|
const data = await response.json();
|
|
2725
3049
|
return ok(data);
|
|
2726
3050
|
} catch (error) {
|
|
2727
3051
|
return err(wrapError("sql", error));
|
|
3052
|
+
} finally {
|
|
3053
|
+
request.dispose();
|
|
2728
3054
|
}
|
|
2729
3055
|
});
|
|
2730
3056
|
}
|
|
@@ -2733,20 +3059,24 @@ var SQLService = class extends BaseService {
|
|
|
2733
3059
|
if (!this.requireAuth()) {
|
|
2734
3060
|
return err(authRequiredError("sql"));
|
|
2735
3061
|
}
|
|
3062
|
+
const request = this.createRequestSignal(options?.signal);
|
|
2736
3063
|
try {
|
|
3064
|
+
const requestedActions = this.actionsForSqlBatch(statements);
|
|
2737
3065
|
const response = await this.invokeSQL(
|
|
2738
3066
|
dbName,
|
|
2739
|
-
|
|
3067
|
+
requestedActions,
|
|
2740
3068
|
{ action: "batch", statements },
|
|
2741
|
-
|
|
3069
|
+
request.signal
|
|
2742
3070
|
);
|
|
2743
3071
|
if (!response.ok) {
|
|
2744
|
-
return this.handleErrorResponse(response, "batch");
|
|
3072
|
+
return this.handleErrorResponse(response, "batch", dbName, requestedActions);
|
|
2745
3073
|
}
|
|
2746
3074
|
const data = await response.json();
|
|
2747
3075
|
return ok(data);
|
|
2748
3076
|
} catch (error) {
|
|
2749
3077
|
return err(wrapError("sql", error));
|
|
3078
|
+
} finally {
|
|
3079
|
+
request.dispose();
|
|
2750
3080
|
}
|
|
2751
3081
|
});
|
|
2752
3082
|
}
|
|
@@ -2755,20 +3085,23 @@ var SQLService = class extends BaseService {
|
|
|
2755
3085
|
if (!this.requireAuth()) {
|
|
2756
3086
|
return err(authRequiredError("sql"));
|
|
2757
3087
|
}
|
|
3088
|
+
const request = this.createRequestSignal(options?.signal);
|
|
2758
3089
|
try {
|
|
2759
3090
|
const response = await this.invokeSQL(
|
|
2760
3091
|
dbName,
|
|
2761
3092
|
SQLAction.WRITE,
|
|
2762
3093
|
{ action: "execute_statement", name, params: params ?? [] },
|
|
2763
|
-
|
|
3094
|
+
request.signal
|
|
2764
3095
|
);
|
|
2765
3096
|
if (!response.ok) {
|
|
2766
|
-
return this.handleErrorResponse(response, "executeStatement");
|
|
3097
|
+
return this.handleErrorResponse(response, "executeStatement", dbName, [SQLAction.WRITE]);
|
|
2767
3098
|
}
|
|
2768
3099
|
const data = await response.json();
|
|
2769
3100
|
return ok(data);
|
|
2770
3101
|
} catch (error) {
|
|
2771
3102
|
return err(wrapError("sql", error));
|
|
3103
|
+
} finally {
|
|
3104
|
+
request.dispose();
|
|
2772
3105
|
}
|
|
2773
3106
|
});
|
|
2774
3107
|
}
|
|
@@ -2777,15 +3110,16 @@ var SQLService = class extends BaseService {
|
|
|
2777
3110
|
if (!this.requireAuth()) {
|
|
2778
3111
|
return err(authRequiredError("sql"));
|
|
2779
3112
|
}
|
|
3113
|
+
const request = this.createRequestSignal(options?.signal);
|
|
2780
3114
|
try {
|
|
2781
3115
|
const response = await this.invokeSQL(
|
|
2782
3116
|
dbName,
|
|
2783
3117
|
SQLAction.READ,
|
|
2784
3118
|
{ action: "export" },
|
|
2785
|
-
|
|
3119
|
+
request.signal
|
|
2786
3120
|
);
|
|
2787
3121
|
if (!response.ok) {
|
|
2788
|
-
return this.handleErrorResponse(response, "export");
|
|
3122
|
+
return this.handleErrorResponse(response, "export", dbName, [SQLAction.READ]);
|
|
2789
3123
|
}
|
|
2790
3124
|
const resp = response;
|
|
2791
3125
|
if (typeof resp.blob === "function") {
|
|
@@ -2796,6 +3130,8 @@ var SQLService = class extends BaseService {
|
|
|
2796
3130
|
return ok(text);
|
|
2797
3131
|
} catch (error) {
|
|
2798
3132
|
return err(wrapError("sql", error));
|
|
3133
|
+
} finally {
|
|
3134
|
+
request.dispose();
|
|
2799
3135
|
}
|
|
2800
3136
|
});
|
|
2801
3137
|
}
|
|
@@ -2886,7 +3222,7 @@ var SQLService = class extends BaseService {
|
|
|
2886
3222
|
"Content-Type": "application/json"
|
|
2887
3223
|
},
|
|
2888
3224
|
body: JSON.stringify(body),
|
|
2889
|
-
signal
|
|
3225
|
+
signal
|
|
2890
3226
|
});
|
|
2891
3227
|
}
|
|
2892
3228
|
actionForSql(sql, fallback) {
|
|
@@ -2919,7 +3255,7 @@ var SQLService = class extends BaseService {
|
|
|
2919
3255
|
}))
|
|
2920
3256
|
);
|
|
2921
3257
|
}
|
|
2922
|
-
async handleErrorResponse(response, operation) {
|
|
3258
|
+
async handleErrorResponse(response, operation, dbName, requestedActions) {
|
|
2923
3259
|
const errorText = await response.text();
|
|
2924
3260
|
const meta = responseErrorMeta(response.status, response.statusText, errorText);
|
|
2925
3261
|
if (response.status === 402) {
|
|
@@ -2938,9 +3274,11 @@ var SQLService = class extends BaseService {
|
|
|
2938
3274
|
errorBody
|
|
2939
3275
|
);
|
|
2940
3276
|
if (response.status === 401) {
|
|
2941
|
-
const {
|
|
2942
|
-
if (action
|
|
2943
|
-
|
|
3277
|
+
const { action } = parseAuthError(errorText);
|
|
3278
|
+
if (action && requestedActions.includes(action)) {
|
|
3279
|
+
meta.requiredAction = action;
|
|
3280
|
+
meta.resource = `${this.context.session.spaceId}/sql/${dbName}`;
|
|
3281
|
+
}
|
|
2944
3282
|
}
|
|
2945
3283
|
return err(
|
|
2946
3284
|
serviceError(errorCode, message, "sql", { meta })
|
|
@@ -3148,12 +3486,13 @@ var DuckDbService = class extends BaseService {
|
|
|
3148
3486
|
if (!this.requireAuth()) {
|
|
3149
3487
|
return err(authRequiredError("duckdb"));
|
|
3150
3488
|
}
|
|
3489
|
+
const request = this.createRequestSignal(options?.signal);
|
|
3151
3490
|
try {
|
|
3152
3491
|
const response = await this.invokeDuckDb(
|
|
3153
3492
|
dbName,
|
|
3154
3493
|
DuckDbAction.READ,
|
|
3155
3494
|
{ action: "query", sql, params: params ?? [] },
|
|
3156
|
-
|
|
3495
|
+
request.signal
|
|
3157
3496
|
);
|
|
3158
3497
|
if (!response.ok) {
|
|
3159
3498
|
return this.handleErrorResponse(response, "query");
|
|
@@ -3162,6 +3501,8 @@ var DuckDbService = class extends BaseService {
|
|
|
3162
3501
|
return ok(data);
|
|
3163
3502
|
} catch (error) {
|
|
3164
3503
|
return err(wrapError("duckdb", error));
|
|
3504
|
+
} finally {
|
|
3505
|
+
request.dispose();
|
|
3165
3506
|
}
|
|
3166
3507
|
});
|
|
3167
3508
|
}
|
|
@@ -3170,12 +3511,13 @@ var DuckDbService = class extends BaseService {
|
|
|
3170
3511
|
if (!this.requireAuth()) {
|
|
3171
3512
|
return err(authRequiredError("duckdb"));
|
|
3172
3513
|
}
|
|
3514
|
+
const request = this.createRequestSignal(options?.signal);
|
|
3173
3515
|
try {
|
|
3174
3516
|
const response = await this.invokeDuckDb(
|
|
3175
3517
|
dbName,
|
|
3176
3518
|
DuckDbAction.READ,
|
|
3177
3519
|
{ action: "query", sql, params: params ?? [] },
|
|
3178
|
-
|
|
3520
|
+
request.signal,
|
|
3179
3521
|
{ Accept: "application/vnd.apache.arrow.stream" }
|
|
3180
3522
|
);
|
|
3181
3523
|
if (!response.ok) {
|
|
@@ -3185,6 +3527,8 @@ var DuckDbService = class extends BaseService {
|
|
|
3185
3527
|
return ok(buffer);
|
|
3186
3528
|
} catch (error) {
|
|
3187
3529
|
return err(wrapError("duckdb", error));
|
|
3530
|
+
} finally {
|
|
3531
|
+
request.dispose();
|
|
3188
3532
|
}
|
|
3189
3533
|
});
|
|
3190
3534
|
}
|
|
@@ -3193,6 +3537,7 @@ var DuckDbService = class extends BaseService {
|
|
|
3193
3537
|
if (!this.requireAuth()) {
|
|
3194
3538
|
return err(authRequiredError("duckdb"));
|
|
3195
3539
|
}
|
|
3540
|
+
const request = this.createRequestSignal(options?.signal);
|
|
3196
3541
|
try {
|
|
3197
3542
|
const body = {
|
|
3198
3543
|
action: "execute",
|
|
@@ -3206,7 +3551,7 @@ var DuckDbService = class extends BaseService {
|
|
|
3206
3551
|
dbName,
|
|
3207
3552
|
DuckDbAction.WRITE,
|
|
3208
3553
|
body,
|
|
3209
|
-
|
|
3554
|
+
request.signal
|
|
3210
3555
|
);
|
|
3211
3556
|
if (!response.ok) {
|
|
3212
3557
|
return this.handleErrorResponse(response, "execute");
|
|
@@ -3215,6 +3560,8 @@ var DuckDbService = class extends BaseService {
|
|
|
3215
3560
|
return ok(data);
|
|
3216
3561
|
} catch (error) {
|
|
3217
3562
|
return err(wrapError("duckdb", error));
|
|
3563
|
+
} finally {
|
|
3564
|
+
request.dispose();
|
|
3218
3565
|
}
|
|
3219
3566
|
});
|
|
3220
3567
|
}
|
|
@@ -3223,6 +3570,7 @@ var DuckDbService = class extends BaseService {
|
|
|
3223
3570
|
if (!this.requireAuth()) {
|
|
3224
3571
|
return err(authRequiredError("duckdb"));
|
|
3225
3572
|
}
|
|
3573
|
+
const request = this.createRequestSignal(options?.signal);
|
|
3226
3574
|
try {
|
|
3227
3575
|
const body = {
|
|
3228
3576
|
action: "batch",
|
|
@@ -3235,7 +3583,7 @@ var DuckDbService = class extends BaseService {
|
|
|
3235
3583
|
dbName,
|
|
3236
3584
|
DuckDbAction.WRITE,
|
|
3237
3585
|
body,
|
|
3238
|
-
|
|
3586
|
+
request.signal
|
|
3239
3587
|
);
|
|
3240
3588
|
if (!response.ok) {
|
|
3241
3589
|
return this.handleErrorResponse(response, "batch");
|
|
@@ -3244,6 +3592,8 @@ var DuckDbService = class extends BaseService {
|
|
|
3244
3592
|
return ok(data);
|
|
3245
3593
|
} catch (error) {
|
|
3246
3594
|
return err(wrapError("duckdb", error));
|
|
3595
|
+
} finally {
|
|
3596
|
+
request.dispose();
|
|
3247
3597
|
}
|
|
3248
3598
|
});
|
|
3249
3599
|
}
|
|
@@ -3252,12 +3602,13 @@ var DuckDbService = class extends BaseService {
|
|
|
3252
3602
|
if (!this.requireAuth()) {
|
|
3253
3603
|
return err(authRequiredError("duckdb"));
|
|
3254
3604
|
}
|
|
3605
|
+
const request = this.createRequestSignal(options?.signal);
|
|
3255
3606
|
try {
|
|
3256
3607
|
const response = await this.invokeDuckDb(
|
|
3257
3608
|
dbName,
|
|
3258
3609
|
DuckDbAction.WRITE,
|
|
3259
3610
|
{ action: "executeStatement", name, params: params ?? [] },
|
|
3260
|
-
|
|
3611
|
+
request.signal
|
|
3261
3612
|
);
|
|
3262
3613
|
if (!response.ok) {
|
|
3263
3614
|
return this.handleErrorResponse(response, "executeStatement");
|
|
@@ -3266,6 +3617,8 @@ var DuckDbService = class extends BaseService {
|
|
|
3266
3617
|
return ok(data);
|
|
3267
3618
|
} catch (error) {
|
|
3268
3619
|
return err(wrapError("duckdb", error));
|
|
3620
|
+
} finally {
|
|
3621
|
+
request.dispose();
|
|
3269
3622
|
}
|
|
3270
3623
|
});
|
|
3271
3624
|
}
|
|
@@ -3274,12 +3627,13 @@ var DuckDbService = class extends BaseService {
|
|
|
3274
3627
|
if (!this.requireAuth()) {
|
|
3275
3628
|
return err(authRequiredError("duckdb"));
|
|
3276
3629
|
}
|
|
3630
|
+
const request = this.createRequestSignal(options?.signal);
|
|
3277
3631
|
try {
|
|
3278
3632
|
const response = await this.invokeDuckDb(
|
|
3279
3633
|
dbName,
|
|
3280
3634
|
DuckDbAction.READ,
|
|
3281
3635
|
{ action: "describe" },
|
|
3282
|
-
|
|
3636
|
+
request.signal
|
|
3283
3637
|
);
|
|
3284
3638
|
if (!response.ok) {
|
|
3285
3639
|
return this.handleErrorResponse(response, "describe");
|
|
@@ -3288,6 +3642,8 @@ var DuckDbService = class extends BaseService {
|
|
|
3288
3642
|
return ok(data);
|
|
3289
3643
|
} catch (error) {
|
|
3290
3644
|
return err(wrapError("duckdb", error));
|
|
3645
|
+
} finally {
|
|
3646
|
+
request.dispose();
|
|
3291
3647
|
}
|
|
3292
3648
|
});
|
|
3293
3649
|
}
|
|
@@ -3296,12 +3652,13 @@ var DuckDbService = class extends BaseService {
|
|
|
3296
3652
|
if (!this.requireAuth()) {
|
|
3297
3653
|
return err(authRequiredError("duckdb"));
|
|
3298
3654
|
}
|
|
3655
|
+
const request = this.createRequestSignal(options?.signal);
|
|
3299
3656
|
try {
|
|
3300
3657
|
const response = await this.invokeDuckDb(
|
|
3301
3658
|
dbName,
|
|
3302
3659
|
DuckDbAction.EXPORT,
|
|
3303
3660
|
{ action: "export" },
|
|
3304
|
-
|
|
3661
|
+
request.signal
|
|
3305
3662
|
);
|
|
3306
3663
|
if (!response.ok) {
|
|
3307
3664
|
return this.handleErrorResponse(response, "export");
|
|
@@ -3310,6 +3667,8 @@ var DuckDbService = class extends BaseService {
|
|
|
3310
3667
|
return ok(blob);
|
|
3311
3668
|
} catch (error) {
|
|
3312
3669
|
return err(wrapError("duckdb", error));
|
|
3670
|
+
} finally {
|
|
3671
|
+
request.dispose();
|
|
3313
3672
|
}
|
|
3314
3673
|
});
|
|
3315
3674
|
}
|
|
@@ -3318,6 +3677,7 @@ var DuckDbService = class extends BaseService {
|
|
|
3318
3677
|
if (!this.requireAuth()) {
|
|
3319
3678
|
return err(authRequiredError("duckdb"));
|
|
3320
3679
|
}
|
|
3680
|
+
const request = this.createRequestSignal(options?.signal);
|
|
3321
3681
|
try {
|
|
3322
3682
|
const session = this.context.session;
|
|
3323
3683
|
const headers = this.context.invoke(
|
|
@@ -3333,7 +3693,7 @@ var DuckDbService = class extends BaseService {
|
|
|
3333
3693
|
"Content-Type": "application/x-duckdb"
|
|
3334
3694
|
},
|
|
3335
3695
|
body: new Blob([data]),
|
|
3336
|
-
signal:
|
|
3696
|
+
signal: request.signal
|
|
3337
3697
|
});
|
|
3338
3698
|
if (!response.ok) {
|
|
3339
3699
|
return this.handleErrorResponse(response, "import");
|
|
@@ -3341,6 +3701,8 @@ var DuckDbService = class extends BaseService {
|
|
|
3341
3701
|
return ok(void 0);
|
|
3342
3702
|
} catch (error) {
|
|
3343
3703
|
return err(wrapError("duckdb", error));
|
|
3704
|
+
} finally {
|
|
3705
|
+
request.dispose();
|
|
3344
3706
|
}
|
|
3345
3707
|
});
|
|
3346
3708
|
}
|
|
@@ -3356,7 +3718,7 @@ var DuckDbService = class extends BaseService {
|
|
|
3356
3718
|
...extraHeaders
|
|
3357
3719
|
},
|
|
3358
3720
|
body: JSON.stringify(body),
|
|
3359
|
-
signal
|
|
3721
|
+
signal
|
|
3360
3722
|
});
|
|
3361
3723
|
}
|
|
3362
3724
|
async handleErrorResponse(response, operation) {
|
|
@@ -5919,103 +6281,6 @@ function canonicalHashHex(sha256, value) {
|
|
|
5919
6281
|
return hexEncode2(sha256(utf8Encode(canonical)));
|
|
5920
6282
|
}
|
|
5921
6283
|
|
|
5922
|
-
// src/encryption/networkId.ts
|
|
5923
|
-
var URN_PREFIX = "urn:tinycloud:encryption:";
|
|
5924
|
-
var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
5925
|
-
var PKH_EIP155_DID_RE = /^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40})$/;
|
|
5926
|
-
var NetworkIdError = class extends Error {
|
|
5927
|
-
constructor(message) {
|
|
5928
|
-
super(message);
|
|
5929
|
-
this.name = "NetworkIdError";
|
|
5930
|
-
}
|
|
5931
|
-
};
|
|
5932
|
-
function parseNetworkId(networkId) {
|
|
5933
|
-
if (typeof networkId !== "string" || networkId.length === 0) {
|
|
5934
|
-
throw new NetworkIdError("networkId must be a non-empty string");
|
|
5935
|
-
}
|
|
5936
|
-
if (!networkId.startsWith(URN_PREFIX)) {
|
|
5937
|
-
throw new NetworkIdError(
|
|
5938
|
-
`networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
|
|
5939
|
-
);
|
|
5940
|
-
}
|
|
5941
|
-
const body = networkId.slice(URN_PREFIX.length);
|
|
5942
|
-
const lastColon = body.lastIndexOf(":");
|
|
5943
|
-
if (lastColon <= 0 || lastColon === body.length - 1) {
|
|
5944
|
-
throw new NetworkIdError(
|
|
5945
|
-
`networkId missing ownerDid or name segment (got ${JSON.stringify(networkId)})`
|
|
5946
|
-
);
|
|
5947
|
-
}
|
|
5948
|
-
const ownerDid = body.slice(0, lastColon);
|
|
5949
|
-
const name = body.slice(lastColon + 1);
|
|
5950
|
-
if (!ownerDid.startsWith("did:")) {
|
|
5951
|
-
throw new NetworkIdError(
|
|
5952
|
-
`networkId ownerDid must be a DID (got ${JSON.stringify(ownerDid)})`
|
|
5953
|
-
);
|
|
5954
|
-
}
|
|
5955
|
-
const didParts = ownerDid.split(":");
|
|
5956
|
-
if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
|
|
5957
|
-
throw new NetworkIdError(
|
|
5958
|
-
`networkId ownerDid is not a well-formed DID (got ${JSON.stringify(ownerDid)})`
|
|
5959
|
-
);
|
|
5960
|
-
}
|
|
5961
|
-
if (!NETWORK_NAME_RE.test(name)) {
|
|
5962
|
-
throw new NetworkIdError(
|
|
5963
|
-
`networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
5964
|
-
);
|
|
5965
|
-
}
|
|
5966
|
-
return { networkId, ownerDid, name };
|
|
5967
|
-
}
|
|
5968
|
-
function buildNetworkId(ownerDid, name) {
|
|
5969
|
-
if (typeof ownerDid !== "string" || !ownerDid.startsWith("did:")) {
|
|
5970
|
-
throw new NetworkIdError("ownerDid must be a DID");
|
|
5971
|
-
}
|
|
5972
|
-
if (typeof name !== "string" || !NETWORK_NAME_RE.test(name)) {
|
|
5973
|
-
throw new NetworkIdError(
|
|
5974
|
-
`network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
5975
|
-
);
|
|
5976
|
-
}
|
|
5977
|
-
const networkId = `${URN_PREFIX}${ownerDid}:${name}`;
|
|
5978
|
-
parseNetworkId(networkId);
|
|
5979
|
-
return networkId;
|
|
5980
|
-
}
|
|
5981
|
-
function isNetworkId(networkId) {
|
|
5982
|
-
if (typeof networkId !== "string") {
|
|
5983
|
-
return false;
|
|
5984
|
-
}
|
|
5985
|
-
try {
|
|
5986
|
-
parseNetworkId(networkId);
|
|
5987
|
-
return true;
|
|
5988
|
-
} catch {
|
|
5989
|
-
return false;
|
|
5990
|
-
}
|
|
5991
|
-
}
|
|
5992
|
-
function parsePkhOwnerDid(ownerDid) {
|
|
5993
|
-
const match = ownerDid.match(PKH_EIP155_DID_RE);
|
|
5994
|
-
if (!match) return null;
|
|
5995
|
-
return {
|
|
5996
|
-
chainId: match[1],
|
|
5997
|
-
address: match[2].toLowerCase()
|
|
5998
|
-
};
|
|
5999
|
-
}
|
|
6000
|
-
function ownerDidMatches(a, b) {
|
|
6001
|
-
const aPkh = parsePkhOwnerDid(a);
|
|
6002
|
-
const bPkh = parsePkhOwnerDid(b);
|
|
6003
|
-
if (aPkh && bPkh) {
|
|
6004
|
-
return aPkh.chainId === bPkh.chainId && aPkh.address === bPkh.address;
|
|
6005
|
-
}
|
|
6006
|
-
return a === b;
|
|
6007
|
-
}
|
|
6008
|
-
function networkDiscoveryKey(name) {
|
|
6009
|
-
if (!NETWORK_NAME_RE.test(name)) {
|
|
6010
|
-
throw new NetworkIdError(
|
|
6011
|
-
`network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
|
|
6012
|
-
);
|
|
6013
|
-
}
|
|
6014
|
-
return `.well-known/encryption/network/${name}`;
|
|
6015
|
-
}
|
|
6016
|
-
var ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;
|
|
6017
|
-
var NETWORK_NAME_PATTERN = NETWORK_NAME_RE;
|
|
6018
|
-
|
|
6019
6284
|
// src/encryption/types.ts
|
|
6020
6285
|
var DEFAULT_ENCRYPTION_ALG = "x25519-aes256gcm/v1";
|
|
6021
6286
|
var ENVELOPE_VERSION = 1;
|
|
@@ -6958,6 +7223,7 @@ export {
|
|
|
6958
7223
|
ok,
|
|
6959
7224
|
openWrappedKey,
|
|
6960
7225
|
parseAuthError,
|
|
7226
|
+
parseCapabilityResource,
|
|
6961
7227
|
parseNetworkId,
|
|
6962
7228
|
parsePermissionHint,
|
|
6963
7229
|
parsePermissionHintFromErrorText,
|
|
@@ -6983,6 +7249,7 @@ export {
|
|
|
6983
7249
|
validateServiceResponseEvent,
|
|
6984
7250
|
validateServiceSession,
|
|
6985
7251
|
validateTelemetrySpanEvent,
|
|
7252
|
+
validatedCapabilityOf,
|
|
6986
7253
|
verifyDecryptResponse,
|
|
6987
7254
|
wrapError
|
|
6988
7255
|
};
|