@tinycloud/sdk-services 3.2.6-beta.2 → 3.3.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-4wwJq1Gn.d.cts → BaseService-Cc6Ueicm.d.cts} +1 -1
- package/dist/{BaseService-BGr2slUz.d.ts → BaseService-DOdXd7sr.d.ts} +1 -1
- package/dist/encryption/index.cjs +10 -0
- 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 +10 -0
- package/dist/encryption/index.js.map +1 -1
- package/dist/index.cjs +360 -210
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +116 -13
- package/dist/index.d.ts +116 -13
- package/dist/index.js +359 -210
- 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 +68 -21
- package/dist/kv/index.cjs.map +1 -1
- package/dist/kv/index.d.cts +7 -4
- package/dist/kv/index.d.ts +7 -4
- package/dist/kv/index.js +68 -21
- package/dist/kv/index.js.map +1 -1
- package/dist/kv/replication/contractConstants.cjs +115 -0
- package/dist/kv/replication/contractConstants.cjs.map +1 -0
- package/dist/kv/replication/contractConstants.d.cts +77 -0
- package/dist/kv/replication/contractConstants.d.ts +77 -0
- package/dist/kv/replication/contractConstants.js +75 -0
- package/dist/kv/replication/contractConstants.js.map +1 -0
- package/dist/kv/replication/grantPolicy.cjs +82 -0
- package/dist/kv/replication/grantPolicy.cjs.map +1 -0
- package/dist/kv/replication/grantPolicy.d.cts +19 -0
- package/dist/kv/replication/grantPolicy.d.ts +19 -0
- package/dist/kv/replication/grantPolicy.js +50 -0
- package/dist/kv/replication/grantPolicy.js.map +1 -0
- package/dist/kv/replication/grantPolicyConstants.cjs +42 -0
- package/dist/kv/replication/grantPolicyConstants.cjs.map +1 -0
- package/dist/kv/replication/grantPolicyConstants.d.cts +10 -0
- package/dist/kv/replication/grantPolicyConstants.d.ts +10 -0
- package/dist/kv/replication/grantPolicyConstants.js +14 -0
- package/dist/kv/replication/grantPolicyConstants.js.map +1 -0
- package/dist/kv/replication/grantPolicyTypes.cjs +19 -0
- package/dist/kv/replication/grantPolicyTypes.cjs.map +1 -0
- package/dist/kv/replication/grantPolicyTypes.d.cts +87 -0
- package/dist/kv/replication/grantPolicyTypes.d.ts +87 -0
- package/dist/kv/replication/grantPolicyTypes.js +1 -0
- package/dist/kv/replication/grantPolicyTypes.js.map +1 -0
- package/dist/kv/replication/index.cjs +575 -131
- package/dist/kv/replication/index.cjs.map +1 -1
- package/dist/kv/replication/index.d.cts +6 -3
- package/dist/kv/replication/index.d.ts +6 -3
- package/dist/kv/replication/index.js +575 -131
- package/dist/kv/replication/index.js.map +1 -1
- package/dist/scope-B-m6cKH-.d.ts +1962 -0
- package/dist/scope-B7oeKdLM.d.cts +1962 -0
- package/dist/sql/index.cjs +72 -36
- package/dist/sql/index.cjs.map +1 -1
- package/dist/sql/index.d.cts +2 -2
- package/dist/sql/index.d.ts +2 -2
- package/dist/sql/index.js +72 -36
- package/dist/sql/index.js.map +1 -1
- package/dist/{types-D-9A3Sut.d.cts → types-BjH0ZtGy.d.cts} +8 -0
- package/dist/{types-D-9A3Sut.d.ts → types-BjH0ZtGy.d.ts} +8 -0
- package/package.json +55 -2
- package/dist/scope-CoKKmMiI.d.ts +0 -957
- package/dist/scope-D7wmjbMH.d.cts +0 -957
package/dist/index.cjs
CHANGED
|
@@ -84,8 +84,8 @@ __export(src_exports, {
|
|
|
84
84
|
authRequiredError: () => authRequiredError,
|
|
85
85
|
authUnauthorizedError: () => authUnauthorizedError,
|
|
86
86
|
authorizationVerdictOf: () => authorizationVerdictOf,
|
|
87
|
-
base64Decode: () =>
|
|
88
|
-
base64Encode: () =>
|
|
87
|
+
base64Decode: () => base64Decode,
|
|
88
|
+
base64Encode: () => base64Encode,
|
|
89
89
|
buildCanonicalDecryptRequest: () => buildCanonicalDecryptRequest,
|
|
90
90
|
buildDecryptAttenuation: () => buildDecryptAttenuation,
|
|
91
91
|
buildDecryptFacts: () => buildDecryptFacts,
|
|
@@ -115,13 +115,14 @@ __export(src_exports, {
|
|
|
115
115
|
generateRandomReceiverKey: () => generateRandomReceiverKey,
|
|
116
116
|
getTinyCloudDebugLogs: () => getTinyCloudDebugLogs,
|
|
117
117
|
hexDecode: () => hexDecode,
|
|
118
|
-
hexEncode: () =>
|
|
118
|
+
hexEncode: () => hexEncode,
|
|
119
119
|
installTinyCloudDebugGlobals: () => installTinyCloudDebugGlobals,
|
|
120
120
|
isNetworkId: () => isNetworkId,
|
|
121
121
|
isStorageFullError: () => isStorageFullError,
|
|
122
122
|
kvPrefixCovers: () => kvPrefixCovers,
|
|
123
123
|
networkDiscoveryKey: () => networkDiscoveryKey,
|
|
124
124
|
networkError: () => networkError,
|
|
125
|
+
nodeRetryError: () => nodeRetryError,
|
|
125
126
|
notFoundError: () => notFoundError,
|
|
126
127
|
ok: () => ok,
|
|
127
128
|
openWrappedKey: () => openWrappedKey,
|
|
@@ -179,6 +180,14 @@ var ErrorCodes = {
|
|
|
179
180
|
ABORTED: "ABORTED",
|
|
180
181
|
INVALID_INPUT: "INVALID_INPUT",
|
|
181
182
|
PERMISSION_DENIED: "PERMISSION_DENIED",
|
|
183
|
+
// Node retry signals (tinycloud-node #347). `meta.retryable` says whether a
|
|
184
|
+
// retry is safe; `meta.retryAfterMs` carries the node's Retry-After.
|
|
185
|
+
/** HTTP 503 `sql_busy:<stage>` / `unavailable_retryable`: nothing was applied; retry after `meta.retryAfterMs`. */
|
|
186
|
+
NODE_BUSY: "NODE_BUSY",
|
|
187
|
+
/** HTTP 503 `request_timeout`: the node's deadline passed before any write began; nothing was applied. */
|
|
188
|
+
REQUEST_TIMEOUT: "REQUEST_TIMEOUT",
|
|
189
|
+
/** HTTP 500 `outcome_unknown`: a write may have been applied; read back before retrying a non-idempotent operation. */
|
|
190
|
+
OUTCOME_UNKNOWN: "OUTCOME_UNKNOWN",
|
|
182
191
|
// KV-specific errors
|
|
183
192
|
KV_NOT_FOUND: "KV_NOT_FOUND",
|
|
184
193
|
KV_WRITE_FAILED: "KV_WRITE_FAILED",
|
|
@@ -233,6 +242,8 @@ var TelemetryEvents = {
|
|
|
233
242
|
REPLICATION_SYNC: "replication.sync",
|
|
234
243
|
REPLICATION_STATE: "replication.state",
|
|
235
244
|
REPLICATION_DIVERGENCE: "replication.divergence",
|
|
245
|
+
REPLICATION_GRANT: "replication.grant",
|
|
246
|
+
REPLICATION_LIFECYCLE: "replication.lifecycle",
|
|
236
247
|
/** Emitted once, on the first write rejected because storage is full. */
|
|
237
248
|
STORAGE_FULL: "storage.full"
|
|
238
249
|
};
|
|
@@ -269,21 +280,27 @@ var SAFE_NUMBER_FIELDS = /* @__PURE__ */ new Set([
|
|
|
269
280
|
"fetched",
|
|
270
281
|
"pages",
|
|
271
282
|
"lagMs",
|
|
272
|
-
"pendingCleared"
|
|
283
|
+
"pendingCleared",
|
|
284
|
+
"partitions",
|
|
285
|
+
"remaining",
|
|
286
|
+
"expiryMs"
|
|
273
287
|
]);
|
|
274
|
-
var SAFE_BOOLEAN_FIELDS = /* @__PURE__ */ new Set(["authenticated", "ok", "persisted"]);
|
|
288
|
+
var SAFE_BOOLEAN_FIELDS = /* @__PURE__ */ new Set(["authenticated", "ok", "persisted", "purgePending"]);
|
|
275
289
|
var SAFE_REPLICATION_FIELDS = {
|
|
276
290
|
source: ["replica", "network", "none"],
|
|
277
|
-
reason: ["hit", "absent", "deleted", "content_missing", "coverage_incomplete", "not_covered", "pending_write", "grant_missing", "grant_expired", "grant_revoked", "grant_not_yet_valid", "stale", "replica_error", "runtime_unsupported", "replica_unavailable", "unsupported_option", "network_cursor", "cursor_restart", "aborted", "REPLICA_BEHIND_OWN_WRITES", "REPLICA_UNPROVEN_SINCE_START", "NETWORK_REQUESTED"],
|
|
291
|
+
reason: ["hit", "absent", "deleted", "content_missing", "coverage_incomplete", "not_covered", "pending_write", "grant_missing", "grant_expired", "grant_revoked", "grant_not_yet_valid", "stale", "replica_error", "runtime_unsupported", "replica_unavailable", "unsupported_option", "network_cursor", "cursor_restart", "aborted", "REPLICA_BEHIND_OWN_WRITES", "REPLICA_UNPROVEN_SINCE_START", "NETWORK_REQUESTED", "replica_gated", "sign_out", "account_switch", "consent_expired", "startup"],
|
|
278
292
|
op: ["get", "list", "put", "delete", "batchPut"],
|
|
279
|
-
trigger: ["start", "interval", "stale_read", "manual"],
|
|
293
|
+
trigger: ["start", "interval", "stale_read", "manual", "peer"],
|
|
294
|
+
origin: ["peer"],
|
|
280
295
|
outcome: ["found", "not_found", "error", "committed", "failed", "ambiguous", "ok", "busy", "aborted"],
|
|
281
|
-
class: ["offline", "authority", "storage", "node"],
|
|
282
|
-
state: ["opened", "grant_installed", "grant_missing", "runtime_unsupported", "unavailable", "revoked", "recreated", "purged", "purge_failed", "pending_store_error", "pinned", "pending_cleared", "closed", "idle", "ready"],
|
|
296
|
+
class: ["offline", "authority", "storage", "node", "lifecycle"],
|
|
297
|
+
state: ["opened", "grant_installed", "grant_missing", "runtime_unsupported", "unavailable", "revoked", "recreated", "purged", "purge_failed", "pending_store_error", "pinned", "pending_cleared", "closed", "idle", "ready", "gated"],
|
|
298
|
+
action: ["minted", "renewed", "renewal_refused", "renewal_deferred", "capped", "expired", "consent_deadline_reached", "denied", "deny_stale", "deny_failed", "database_deleted", "marker_set", "marker_cleared", "unguarded", "drained", "erased", "erase_failed", "activation_blocked", "activated", "recovered"],
|
|
299
|
+
gate: ["not_established", "stamp_stale", "principal_mismatch", "purge_pending", "logout_pending", "gate_unavailable"],
|
|
283
300
|
kind: ["value", "missing_local", "extra_local", "keys", "order"],
|
|
284
301
|
coverage: ["empty", "bootstrapping", "complete"],
|
|
285
302
|
authority: ["valid", "expired", "revoked", "not-yet-valid"],
|
|
286
|
-
code: ["NETWORK_ERROR", "TIMEOUT", "ABORTED", "REPLICA_BUSY", "REPLICA_BEHIND_OWN_WRITES", "REPLICA_UNPROVEN_SINCE_START", "NETWORK_REQUESTED", "INTEGRITY_ERROR", "STORAGE_ERROR", "REPLICA_CLOSED", "RUNTIME_UNSUPPORTED", "GRANT_EXPIRED", "GRANT_REVOKED", "GRANT_NOT_YET_VALID", "GRANT_MISSING", "GRANT_INVALID", "SOURCE_CHANGED", "PROTOCOL_ERROR", "SCOPE_VIOLATION", "NODE_ERROR", "CONTENT_MISMATCH", "STORAGE_FULL"],
|
|
303
|
+
code: ["NETWORK_ERROR", "TIMEOUT", "ABORTED", "REPLICA_BUSY", "REPLICA_BEHIND_OWN_WRITES", "REPLICA_UNPROVEN_SINCE_START", "NETWORK_REQUESTED", "INTEGRITY_ERROR", "STORAGE_ERROR", "REPLICA_CLOSED", "RUNTIME_UNSUPPORTED", "GRANT_EXPIRED", "GRANT_REVOKED", "GRANT_NOT_YET_VALID", "GRANT_MISSING", "GRANT_INVALID", "SOURCE_CHANGED", "PROTOCOL_ERROR", "SCOPE_VIOLATION", "NODE_ERROR", "CONTENT_MISMATCH", "STORAGE_FULL", "REPLICA_GATED", "RESET_REQUIRED", "STORAGE_BUDGET", "CONSENT_EXPIRED"],
|
|
287
304
|
pendingState: ["in_flight", "committed", "ambiguous"],
|
|
288
305
|
strategy: ["session", "minted", "installed"],
|
|
289
306
|
verify: ["match", "diverged", "error", "aborted"]
|
|
@@ -1115,6 +1132,42 @@ var ServiceContext = class {
|
|
|
1115
1132
|
}
|
|
1116
1133
|
};
|
|
1117
1134
|
|
|
1135
|
+
// src/responseErrors.ts
|
|
1136
|
+
function parseServiceErrorBody(errorText) {
|
|
1137
|
+
try {
|
|
1138
|
+
return JSON.parse(errorText);
|
|
1139
|
+
} catch {
|
|
1140
|
+
return {};
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
function formatServiceResponseError(serviceLabel, operation, status, errorText, parsed) {
|
|
1144
|
+
if (parsed.message) {
|
|
1145
|
+
return compactMessage(parsed.message);
|
|
1146
|
+
}
|
|
1147
|
+
if (looksLikeHtml(errorText)) {
|
|
1148
|
+
if (status === 524 || /524\s*[:-]/i.test(errorText) || /a timeout occurred/i.test(errorText)) {
|
|
1149
|
+
return `${serviceLabel} ${operation} failed: upstream request timed out. Please retry.`;
|
|
1150
|
+
}
|
|
1151
|
+
return `${serviceLabel} ${operation} failed: upstream service returned an HTML error page (${status}).`;
|
|
1152
|
+
}
|
|
1153
|
+
return `${serviceLabel} ${operation} failed: ${status} - ${compactMessage(errorText)}`;
|
|
1154
|
+
}
|
|
1155
|
+
function responseErrorMeta(status, statusText, errorText) {
|
|
1156
|
+
const meta = { status, statusText };
|
|
1157
|
+
const snippet = compactMessage(errorText);
|
|
1158
|
+
if (snippet) {
|
|
1159
|
+
meta.responseSnippet = snippet.slice(0, 300);
|
|
1160
|
+
}
|
|
1161
|
+
return meta;
|
|
1162
|
+
}
|
|
1163
|
+
function looksLikeHtml(text) {
|
|
1164
|
+
return /<!doctype html/i.test(text) || /<html[\s>]/i.test(text);
|
|
1165
|
+
}
|
|
1166
|
+
function compactMessage(message) {
|
|
1167
|
+
const compact = message.replace(/\s+/g, " ").trim();
|
|
1168
|
+
return compact.length > 500 ? `${compact.slice(0, 500)}...` : compact;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1118
1171
|
// src/errors.ts
|
|
1119
1172
|
var import_bootstrap = require("@tinycloud/bootstrap");
|
|
1120
1173
|
|
|
@@ -1274,6 +1327,30 @@ function networkError(service, message, cause) {
|
|
|
1274
1327
|
cause
|
|
1275
1328
|
};
|
|
1276
1329
|
}
|
|
1330
|
+
function nodeRetryError(service, message, response, errorText) {
|
|
1331
|
+
const body = errorText.trim();
|
|
1332
|
+
const [, token, stage] = /^([a-z_]+)(?::\s*([a-z_]+):)?/.exec(body) ?? [];
|
|
1333
|
+
let code;
|
|
1334
|
+
if (response.status === 503 && (token === "sql_busy" || token === "unavailable_retryable")) {
|
|
1335
|
+
code = ErrorCodes.NODE_BUSY;
|
|
1336
|
+
} else if (response.status === 503 && token === "request_timeout") {
|
|
1337
|
+
code = ErrorCodes.REQUEST_TIMEOUT;
|
|
1338
|
+
} else if (response.status === 500 && token === "outcome_unknown") {
|
|
1339
|
+
code = ErrorCodes.OUTCOME_UNKNOWN;
|
|
1340
|
+
} else {
|
|
1341
|
+
return void 0;
|
|
1342
|
+
}
|
|
1343
|
+
const retryable = code !== ErrorCodes.OUTCOME_UNKNOWN;
|
|
1344
|
+
const retryAfter = response.headers.get("retry-after")?.trim();
|
|
1345
|
+
return serviceError(code, message, service, {
|
|
1346
|
+
meta: {
|
|
1347
|
+
...responseErrorMeta(response.status, response.statusText, errorText),
|
|
1348
|
+
retryable,
|
|
1349
|
+
...token === "sql_busy" && stage ? { stage } : {},
|
|
1350
|
+
...retryable && retryAfter && /^\d{1,9}$/.test(retryAfter) ? { retryAfterMs: Number(retryAfter) * 1e3 } : {}
|
|
1351
|
+
}
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1277
1354
|
var RequestTimeoutError = class extends Error {
|
|
1278
1355
|
constructor(timeoutMs) {
|
|
1279
1356
|
super(`Request timed out after ${timeoutMs}ms.`);
|
|
@@ -2612,9 +2689,12 @@ var KVService = class extends BaseService {
|
|
|
2612
2689
|
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2613
2690
|
));
|
|
2614
2691
|
}
|
|
2692
|
+
const message = `Failed to get key ${JSON.stringify(key)}: ${response.status} - ${errorText}`;
|
|
2693
|
+
const retryError = nodeRetryError("kv", message, response, errorText);
|
|
2694
|
+
if (retryError) return err(retryError);
|
|
2615
2695
|
return err(serviceError(
|
|
2616
2696
|
ErrorCodes.NETWORK_ERROR,
|
|
2617
|
-
|
|
2697
|
+
message,
|
|
2618
2698
|
"kv",
|
|
2619
2699
|
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2620
2700
|
));
|
|
@@ -2696,19 +2776,14 @@ var KVService = class extends BaseService {
|
|
|
2696
2776
|
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2697
2777
|
));
|
|
2698
2778
|
}
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
`Concurrent KV update conflicted for key ${JSON.stringify(key)}`,
|
|
2703
|
-
"kv",
|
|
2704
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2705
|
-
));
|
|
2706
|
-
}
|
|
2779
|
+
const message = `Failed to put key ${JSON.stringify(key)}: ${response.status} - ${errorText}`;
|
|
2780
|
+
const retryError = nodeRetryError("kv", message, response, errorText);
|
|
2781
|
+
if (retryError) return err(retryError);
|
|
2707
2782
|
const quotaError = this.handleQuotaErrorResponse(response, errorText, key);
|
|
2708
2783
|
if (quotaError) return quotaError;
|
|
2709
2784
|
return err(serviceError(
|
|
2710
2785
|
ErrorCodes.KV_WRITE_FAILED,
|
|
2711
|
-
|
|
2786
|
+
message,
|
|
2712
2787
|
"kv",
|
|
2713
2788
|
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2714
2789
|
));
|
|
@@ -3008,9 +3083,12 @@ var KVService = class extends BaseService {
|
|
|
3008
3083
|
);
|
|
3009
3084
|
}
|
|
3010
3085
|
const errorText = await response.text();
|
|
3086
|
+
const message = `Failed to list keys: ${response.status} - ${errorText}`;
|
|
3087
|
+
const retryError = nodeRetryError("kv", message, response, errorText);
|
|
3088
|
+
if (retryError) return err(retryError);
|
|
3011
3089
|
return err(serviceError(
|
|
3012
3090
|
ErrorCodes.NETWORK_ERROR,
|
|
3013
|
-
|
|
3091
|
+
message,
|
|
3014
3092
|
"kv",
|
|
3015
3093
|
{ meta: { status: response.status, statusText: response.statusText } }
|
|
3016
3094
|
));
|
|
@@ -3261,17 +3339,12 @@ var KVService = class extends BaseService {
|
|
|
3261
3339
|
{ meta: { status: response.status, statusText: response.statusText } }
|
|
3262
3340
|
));
|
|
3263
3341
|
}
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
`Concurrent KV delete conflicted for key ${JSON.stringify(key)}`,
|
|
3268
|
-
"kv",
|
|
3269
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
3270
|
-
));
|
|
3271
|
-
}
|
|
3342
|
+
const message = `Failed to delete key ${JSON.stringify(key)}: ${response.status} - ${errorText}`;
|
|
3343
|
+
const retryError = nodeRetryError("kv", message, response, errorText);
|
|
3344
|
+
if (retryError) return err(retryError);
|
|
3272
3345
|
return err(serviceError(
|
|
3273
3346
|
ErrorCodes.NETWORK_ERROR,
|
|
3274
|
-
|
|
3347
|
+
message,
|
|
3275
3348
|
"kv",
|
|
3276
3349
|
{ meta: { status: response.status, statusText: response.statusText } }
|
|
3277
3350
|
));
|
|
@@ -3312,10 +3385,13 @@ var KVService = class extends BaseService {
|
|
|
3312
3385
|
return await this.classifyNotFound(response, key);
|
|
3313
3386
|
}
|
|
3314
3387
|
const errorText = await response.text();
|
|
3388
|
+
const message = `Failed to get metadata for key ${JSON.stringify(key)}: ${response.status} - ${errorText}`;
|
|
3389
|
+
const retryError = nodeRetryError("kv", message, response, errorText);
|
|
3390
|
+
if (retryError) return err(retryError);
|
|
3315
3391
|
return err(
|
|
3316
3392
|
serviceError(
|
|
3317
3393
|
ErrorCodes.NETWORK_ERROR,
|
|
3318
|
-
|
|
3394
|
+
message,
|
|
3319
3395
|
"kv",
|
|
3320
3396
|
{ meta: { status: response.status, statusText: response.statusText } }
|
|
3321
3397
|
)
|
|
@@ -3507,42 +3583,6 @@ function requiresSecretsOptIn(space, prefix) {
|
|
|
3507
3583
|
return prefix.split("/", 1)[0] === "vault";
|
|
3508
3584
|
}
|
|
3509
3585
|
|
|
3510
|
-
// src/responseErrors.ts
|
|
3511
|
-
function parseServiceErrorBody(errorText) {
|
|
3512
|
-
try {
|
|
3513
|
-
return JSON.parse(errorText);
|
|
3514
|
-
} catch {
|
|
3515
|
-
return {};
|
|
3516
|
-
}
|
|
3517
|
-
}
|
|
3518
|
-
function formatServiceResponseError(serviceLabel, operation, status, errorText, parsed) {
|
|
3519
|
-
if (parsed.message) {
|
|
3520
|
-
return compactMessage(parsed.message);
|
|
3521
|
-
}
|
|
3522
|
-
if (looksLikeHtml(errorText)) {
|
|
3523
|
-
if (status === 524 || /524\s*[:-]/i.test(errorText) || /a timeout occurred/i.test(errorText)) {
|
|
3524
|
-
return `${serviceLabel} ${operation} failed: upstream request timed out. Please retry.`;
|
|
3525
|
-
}
|
|
3526
|
-
return `${serviceLabel} ${operation} failed: upstream service returned an HTML error page (${status}).`;
|
|
3527
|
-
}
|
|
3528
|
-
return `${serviceLabel} ${operation} failed: ${status} - ${compactMessage(errorText)}`;
|
|
3529
|
-
}
|
|
3530
|
-
function responseErrorMeta(status, statusText, errorText) {
|
|
3531
|
-
const meta = { status, statusText };
|
|
3532
|
-
const snippet = compactMessage(errorText);
|
|
3533
|
-
if (snippet) {
|
|
3534
|
-
meta.responseSnippet = snippet.slice(0, 300);
|
|
3535
|
-
}
|
|
3536
|
-
return meta;
|
|
3537
|
-
}
|
|
3538
|
-
function looksLikeHtml(text) {
|
|
3539
|
-
return /<!doctype html/i.test(text) || /<html[\s>]/i.test(text);
|
|
3540
|
-
}
|
|
3541
|
-
function compactMessage(message) {
|
|
3542
|
-
const compact = message.replace(/\s+/g, " ").trim();
|
|
3543
|
-
return compact.length > 500 ? `${compact.slice(0, 500)}...` : compact;
|
|
3544
|
-
}
|
|
3545
|
-
|
|
3546
3586
|
// src/sql/SQLMigrations.ts
|
|
3547
3587
|
var SQLMigrations = class {
|
|
3548
3588
|
constructor(service, dbName) {
|
|
@@ -3958,6 +3998,8 @@ var SQLService = class extends BaseService {
|
|
|
3958
3998
|
if (response.status === 413 && parseStorageRejection(errorText)) {
|
|
3959
3999
|
return err(storageRejectionError("sql", 413, meta, errorText));
|
|
3960
4000
|
}
|
|
4001
|
+
const retryError = nodeRetryError("sql", `SQL ${operation} failed: ${response.status} - ${errorText}`, response, errorText);
|
|
4002
|
+
if (retryError) return err(retryError);
|
|
3961
4003
|
const errorBody = parseServiceErrorBody(errorText);
|
|
3962
4004
|
const errorCode = this.mapHttpStatusToErrorCode(
|
|
3963
4005
|
response.status,
|
|
@@ -4427,6 +4469,8 @@ var DuckDbService = class extends BaseService {
|
|
|
4427
4469
|
if (response.status === 413 && parseStorageRejection(errorText)) {
|
|
4428
4470
|
return err(storageRejectionError("duckdb", 413, meta, errorText));
|
|
4429
4471
|
}
|
|
4472
|
+
const retryError = nodeRetryError("duckdb", `DuckDB ${operation} failed: ${response.status} - ${errorText}`, response, errorText);
|
|
4473
|
+
if (retryError) return err(retryError);
|
|
4430
4474
|
const errorBody = parseServiceErrorBody(errorText);
|
|
4431
4475
|
const errorCode = this.mapHttpStatusToErrorCode(
|
|
4432
4476
|
response.status,
|
|
@@ -5634,6 +5678,116 @@ var TinyCloudQuota = class {
|
|
|
5634
5678
|
}
|
|
5635
5679
|
};
|
|
5636
5680
|
|
|
5681
|
+
// src/encryption/canonical.ts
|
|
5682
|
+
function canonicalize(value) {
|
|
5683
|
+
if (value === void 0) {
|
|
5684
|
+
return "";
|
|
5685
|
+
}
|
|
5686
|
+
return stringify(value);
|
|
5687
|
+
}
|
|
5688
|
+
function stringify(value) {
|
|
5689
|
+
if (value === null) return "null";
|
|
5690
|
+
switch (typeof value) {
|
|
5691
|
+
case "boolean":
|
|
5692
|
+
case "number":
|
|
5693
|
+
return JSON.stringify(value);
|
|
5694
|
+
case "string":
|
|
5695
|
+
return JSON.stringify(value);
|
|
5696
|
+
case "object": {
|
|
5697
|
+
if (Array.isArray(value)) {
|
|
5698
|
+
return `[${value.map(stringify).join(",")}]`;
|
|
5699
|
+
}
|
|
5700
|
+
const keys = Object.keys(value).sort();
|
|
5701
|
+
const parts = [];
|
|
5702
|
+
for (const k of keys) {
|
|
5703
|
+
const v = value[k];
|
|
5704
|
+
if (v === void 0) continue;
|
|
5705
|
+
parts.push(`${JSON.stringify(k)}:${stringify(v)}`);
|
|
5706
|
+
}
|
|
5707
|
+
return `{${parts.join(",")}}`;
|
|
5708
|
+
}
|
|
5709
|
+
default:
|
|
5710
|
+
throw new TypeError(
|
|
5711
|
+
`canonicalize: unsupported value type ${typeof value}`
|
|
5712
|
+
);
|
|
5713
|
+
}
|
|
5714
|
+
}
|
|
5715
|
+
var HEX = "0123456789abcdef";
|
|
5716
|
+
function hexEncode(bytes) {
|
|
5717
|
+
let out = "";
|
|
5718
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
5719
|
+
const b = bytes[i];
|
|
5720
|
+
out += HEX[b >> 4 & 15] + HEX[b & 15];
|
|
5721
|
+
}
|
|
5722
|
+
return out;
|
|
5723
|
+
}
|
|
5724
|
+
function hexDecode(hex) {
|
|
5725
|
+
if (hex.length % 2 !== 0) {
|
|
5726
|
+
throw new Error("hex string must have even length");
|
|
5727
|
+
}
|
|
5728
|
+
const out = new Uint8Array(hex.length / 2);
|
|
5729
|
+
for (let i = 0; i < out.length; i++) {
|
|
5730
|
+
const hi = parseInt(hex[i * 2], 16);
|
|
5731
|
+
const lo = parseInt(hex[i * 2 + 1], 16);
|
|
5732
|
+
if (Number.isNaN(hi) || Number.isNaN(lo)) {
|
|
5733
|
+
throw new Error("invalid hex character");
|
|
5734
|
+
}
|
|
5735
|
+
out[i] = hi << 4 | lo;
|
|
5736
|
+
}
|
|
5737
|
+
return out;
|
|
5738
|
+
}
|
|
5739
|
+
function base64Encode(bytes) {
|
|
5740
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
5741
|
+
let out = "";
|
|
5742
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
5743
|
+
const b0 = bytes[i];
|
|
5744
|
+
const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
|
|
5745
|
+
const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
|
|
5746
|
+
out += chars[b0 >> 2 & 63];
|
|
5747
|
+
out += chars[(b0 << 4 | b1 >> 4) & 63];
|
|
5748
|
+
out += i + 1 < bytes.length ? chars[(b1 << 2 | b2 >> 6) & 63] : "=";
|
|
5749
|
+
out += i + 2 < bytes.length ? chars[b2 & 63] : "=";
|
|
5750
|
+
}
|
|
5751
|
+
return out;
|
|
5752
|
+
}
|
|
5753
|
+
function base64Decode(s) {
|
|
5754
|
+
const clean = s.replace(/[^A-Za-z0-9+/=]/g, "");
|
|
5755
|
+
const len = clean.length;
|
|
5756
|
+
if (len % 4 !== 0) {
|
|
5757
|
+
throw new Error("invalid base64 input");
|
|
5758
|
+
}
|
|
5759
|
+
const padding = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
|
|
5760
|
+
const outLen = len / 4 * 3 - padding;
|
|
5761
|
+
const out = new Uint8Array(outLen);
|
|
5762
|
+
const lookup = {};
|
|
5763
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
5764
|
+
for (let i = 0; i < chars.length; i++) lookup[chars[i]] = i;
|
|
5765
|
+
let outIdx = 0;
|
|
5766
|
+
for (let i = 0; i < len; i += 4) {
|
|
5767
|
+
const v0 = lookup[clean[i]] ?? 0;
|
|
5768
|
+
const v1 = lookup[clean[i + 1]] ?? 0;
|
|
5769
|
+
const v2 = clean[i + 2] === "=" ? 0 : lookup[clean[i + 2]] ?? 0;
|
|
5770
|
+
const v3 = clean[i + 3] === "=" ? 0 : lookup[clean[i + 3]] ?? 0;
|
|
5771
|
+
const b0 = v0 << 2 | v1 >> 4;
|
|
5772
|
+
const b1 = (v1 & 15) << 4 | v2 >> 2;
|
|
5773
|
+
const b2 = (v2 & 3) << 6 | v3;
|
|
5774
|
+
if (outIdx < outLen) out[outIdx++] = b0;
|
|
5775
|
+
if (outIdx < outLen) out[outIdx++] = b1;
|
|
5776
|
+
if (outIdx < outLen) out[outIdx++] = b2;
|
|
5777
|
+
}
|
|
5778
|
+
return out;
|
|
5779
|
+
}
|
|
5780
|
+
function utf8Encode(s) {
|
|
5781
|
+
return new TextEncoder().encode(s);
|
|
5782
|
+
}
|
|
5783
|
+
function utf8Decode(b) {
|
|
5784
|
+
return new TextDecoder().decode(b);
|
|
5785
|
+
}
|
|
5786
|
+
function canonicalHashHex(sha256, value) {
|
|
5787
|
+
const canonical = canonicalize(value);
|
|
5788
|
+
return hexEncode(sha256(utf8Encode(canonical)));
|
|
5789
|
+
}
|
|
5790
|
+
|
|
5637
5791
|
// src/vault/types.ts
|
|
5638
5792
|
var VaultPublicSpaceKVActions = [
|
|
5639
5793
|
"tinycloud.kv/get",
|
|
@@ -5803,7 +5957,7 @@ function toBytes(str) {
|
|
|
5803
5957
|
function fromBytes(bytes) {
|
|
5804
5958
|
return new TextDecoder().decode(bytes);
|
|
5805
5959
|
}
|
|
5806
|
-
function
|
|
5960
|
+
function hexEncode2(bytes) {
|
|
5807
5961
|
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
5808
5962
|
}
|
|
5809
5963
|
function concatBytes(...arrays) {
|
|
@@ -5816,14 +5970,14 @@ function concatBytes(...arrays) {
|
|
|
5816
5970
|
}
|
|
5817
5971
|
return result;
|
|
5818
5972
|
}
|
|
5819
|
-
function
|
|
5973
|
+
function base64Encode2(bytes) {
|
|
5820
5974
|
let binary = "";
|
|
5821
5975
|
for (let i = 0; i < bytes.length; i++) {
|
|
5822
5976
|
binary += String.fromCharCode(bytes[i]);
|
|
5823
5977
|
}
|
|
5824
5978
|
return btoa(binary);
|
|
5825
5979
|
}
|
|
5826
|
-
function
|
|
5980
|
+
function base64Decode2(str) {
|
|
5827
5981
|
const binary = atob(str);
|
|
5828
5982
|
const bytes = new Uint8Array(binary.length);
|
|
5829
5983
|
for (let i = 0; i < binary.length; i++) {
|
|
@@ -5865,6 +6019,11 @@ function defaultVaultMessage(input) {
|
|
|
5865
6019
|
return input.message;
|
|
5866
6020
|
case "PERMISSION_DENIED":
|
|
5867
6021
|
return input.message;
|
|
6022
|
+
case "AAD_MISMATCH":
|
|
6023
|
+
case "ETAG_MISMATCH":
|
|
6024
|
+
case "INVALID_ETAG":
|
|
6025
|
+
case "OUTCOME_UNKNOWN":
|
|
6026
|
+
case "NODE_BUSY":
|
|
5868
6027
|
case "STORAGE_QUOTA_EXCEEDED":
|
|
5869
6028
|
case "STORAGE_LIMIT_REACHED":
|
|
5870
6029
|
return input.message;
|
|
@@ -5887,6 +6046,37 @@ function typedKvCause(error) {
|
|
|
5887
6046
|
...error.cause !== void 0 ? { cause: error.cause } : {}
|
|
5888
6047
|
});
|
|
5889
6048
|
}
|
|
6049
|
+
function canonicalAadSpaceId(spaceId) {
|
|
6050
|
+
return spaceId.replace(/0x[0-9a-fA-F]{40}/g, (address) => address.toLowerCase());
|
|
6051
|
+
}
|
|
6052
|
+
function checkEnvelopeAad(value, spaceId, key) {
|
|
6053
|
+
const e = value;
|
|
6054
|
+
if (typeof e !== "object" || e === null || Array.isArray(e) || !["networkId", "alg", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext"].every(
|
|
6055
|
+
(field) => typeof e[field] === "string" && e[field] !== ""
|
|
6056
|
+
) || !Number.isInteger(e.keyVersion) || e.aad !== void 0 && typeof e.aad !== "string") {
|
|
6057
|
+
return "corrupt";
|
|
6058
|
+
}
|
|
6059
|
+
let aad = "";
|
|
6060
|
+
try {
|
|
6061
|
+
if (typeof e.aad === "string") aad = fromBytes(base64Decode(e.aad));
|
|
6062
|
+
} catch {
|
|
6063
|
+
}
|
|
6064
|
+
const suffix = `:${key}`;
|
|
6065
|
+
return aad.endsWith(suffix) && canonicalAadSpaceId(aad.slice(0, -suffix.length)) === canonicalAadSpaceId(`tinycloud.vault:${spaceId}`) ? "ok" : "mismatch";
|
|
6066
|
+
}
|
|
6067
|
+
var AMBIGUOUS_WRITE_STATUSES = [500, 502, 503, 504, 520, 522, 524];
|
|
6068
|
+
function ifMatchWriteError(key, error) {
|
|
6069
|
+
const status = error.meta?.status;
|
|
6070
|
+
const code = error.code === ErrorCodes.KV_PRECONDITION_FAILED ? "ETAG_MISMATCH" : error.code === ErrorCodes.KV_WRITE_FAILED && status === 400 ? "INVALID_ETAG" : error.code === ErrorCodes.OUTCOME_UNKNOWN || error.code === ErrorCodes.KV_WRITE_FAILED && typeof status === "number" && AMBIGUOUS_WRITE_STATUSES.includes(status) || (error.code === ErrorCodes.NETWORK_ERROR || error.code === ErrorCodes.TIMEOUT) && status === void 0 && error.meta?.requestMayHaveDispatched !== false ? "OUTCOME_UNKNOWN" : error.code === ErrorCodes.NODE_BUSY || error.code === ErrorCodes.REQUEST_TIMEOUT ? "NODE_BUSY" : void 0;
|
|
6071
|
+
if (code === void 0) return void 0;
|
|
6072
|
+
return vaultError({
|
|
6073
|
+
code,
|
|
6074
|
+
key,
|
|
6075
|
+
message: error.message,
|
|
6076
|
+
...error.meta !== void 0 ? { meta: error.meta } : {},
|
|
6077
|
+
cause: typedKvCause(error)
|
|
6078
|
+
});
|
|
6079
|
+
}
|
|
5890
6080
|
function kvWriteError(context, error) {
|
|
5891
6081
|
if (error.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error.code === ErrorCodes.STORAGE_LIMIT_REACHED) {
|
|
5892
6082
|
return vaultError({ code: error.code, message: error.message, meta: error.meta });
|
|
@@ -6073,7 +6263,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6073
6263
|
}
|
|
6074
6264
|
if (existingPubKey) {
|
|
6075
6265
|
this.encryptionIdentity = {
|
|
6076
|
-
publicKey:
|
|
6266
|
+
publicKey: base64Decode2(existingPubKey),
|
|
6077
6267
|
privateKey: new Uint8Array(0)
|
|
6078
6268
|
// private key not available without signing
|
|
6079
6269
|
};
|
|
@@ -6098,7 +6288,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6098
6288
|
);
|
|
6099
6289
|
this.encryptionIdentity = this.crypto.x25519FromSeed(seed);
|
|
6100
6290
|
try {
|
|
6101
|
-
const pubKeyB64 =
|
|
6291
|
+
const pubKeyB64 = base64Encode2(this.encryptionIdentity.publicKey);
|
|
6102
6292
|
await this.tc.ensurePublicSpace();
|
|
6103
6293
|
await this.tc.publicKV.put(".well-known/vault-pubkey", pubKeyB64);
|
|
6104
6294
|
await this.tc.publicKV.put(".well-known/vault-version", CURRENT_VAULT_VERSION);
|
|
@@ -6164,6 +6354,21 @@ var DataVaultService = class extends BaseService {
|
|
|
6164
6354
|
message: "Authentication required"
|
|
6165
6355
|
});
|
|
6166
6356
|
}
|
|
6357
|
+
if (options?.ifMatch !== void 0) {
|
|
6358
|
+
if (options.ifNoneMatch !== void 0) {
|
|
6359
|
+
return vaultError({
|
|
6360
|
+
code: "STORAGE_ERROR",
|
|
6361
|
+
cause: new Error("ifMatch cannot be combined with ifNoneMatch")
|
|
6362
|
+
});
|
|
6363
|
+
}
|
|
6364
|
+
if (!/^"[^"\s]+"$/.test(options.ifMatch)) {
|
|
6365
|
+
return vaultError({
|
|
6366
|
+
code: "INVALID_ETAG",
|
|
6367
|
+
key,
|
|
6368
|
+
message: options.ifMatch.startsWith("W/") ? 'ifMatch is a weak etag (W/"..."): the node only issues strong etags, so the etag was weakened in transit (a proxy or CDN rewrote it). Read it from a connection that preserves strong ETags' : "ifMatch must be the strong, quoted ETag returned by a network read (VaultEntry.etag), passed exactly as returned"
|
|
6369
|
+
});
|
|
6370
|
+
}
|
|
6371
|
+
}
|
|
6167
6372
|
try {
|
|
6168
6373
|
const { plaintext, contentType } = this.serializeValue(value, options);
|
|
6169
6374
|
const metadata = {
|
|
@@ -6186,7 +6391,8 @@ var DataVaultService = class extends BaseService {
|
|
|
6186
6391
|
}
|
|
6187
6392
|
const valuePath = `vault/${key}`;
|
|
6188
6393
|
const valueBody = JSON.stringify(envelopeResult.data);
|
|
6189
|
-
const
|
|
6394
|
+
const putOptions = options?.ifMatch !== void 0 ? { ifMatch: options.ifMatch } : options?.ifNoneMatch !== void 0 ? { ifNoneMatch: options.ifNoneMatch } : void 0;
|
|
6395
|
+
const valuePutResult = putOptions !== void 0 ? await this.tc.kv.put(valuePath, valueBody, putOptions) : await this.tc.kv.put(valuePath, valueBody);
|
|
6190
6396
|
if (!valuePutResult.ok) {
|
|
6191
6397
|
if (options?.ifNoneMatch !== void 0 && valuePutResult.error.code === ErrorCodes.KV_PRECONDITION_FAILED) {
|
|
6192
6398
|
return vaultError({
|
|
@@ -6196,6 +6402,10 @@ var DataVaultService = class extends BaseService {
|
|
|
6196
6402
|
cause: typedKvCause(valuePutResult.error)
|
|
6197
6403
|
});
|
|
6198
6404
|
}
|
|
6405
|
+
if (options?.ifMatch !== void 0) {
|
|
6406
|
+
const classified = ifMatchWriteError(key, valuePutResult.error);
|
|
6407
|
+
if (classified !== void 0) return classified;
|
|
6408
|
+
}
|
|
6199
6409
|
return kvWriteError("Failed to store encrypted value", valuePutResult.error);
|
|
6200
6410
|
}
|
|
6201
6411
|
return { ok: true, data: void 0 };
|
|
@@ -6230,6 +6440,19 @@ var DataVaultService = class extends BaseService {
|
|
|
6230
6440
|
}
|
|
6231
6441
|
const rawEnvelope = unwrapKVData(valueResult.data);
|
|
6232
6442
|
const envelope = typeof rawEnvelope === "string" ? JSON.parse(rawEnvelope) : rawEnvelope;
|
|
6443
|
+
if (options?.expectAad === true) {
|
|
6444
|
+
const verdict = checkEnvelopeAad(envelope, this.vaultConfig.spaceId, key);
|
|
6445
|
+
if (verdict === "corrupt") {
|
|
6446
|
+
return vaultError({ code: "DECRYPTION_FAILED", message: "envelope is malformed" });
|
|
6447
|
+
}
|
|
6448
|
+
if (verdict === "mismatch") {
|
|
6449
|
+
return vaultError({
|
|
6450
|
+
code: "AAD_MISMATCH",
|
|
6451
|
+
key,
|
|
6452
|
+
message: `Envelope at ${key} is not bound to this space and key`
|
|
6453
|
+
});
|
|
6454
|
+
}
|
|
6455
|
+
}
|
|
6233
6456
|
const proof = await this.decryptCapabilityProof();
|
|
6234
6457
|
const plaintextResult = await config.service.decryptEnvelope(envelope, proof);
|
|
6235
6458
|
if (!plaintextResult.ok) {
|
|
@@ -6246,7 +6469,11 @@ var DataVaultService = class extends BaseService {
|
|
|
6246
6469
|
contentType,
|
|
6247
6470
|
options
|
|
6248
6471
|
);
|
|
6249
|
-
|
|
6472
|
+
const etag = options?.source === "network" ? valueResult.data.headers?.etag : void 0;
|
|
6473
|
+
return {
|
|
6474
|
+
ok: true,
|
|
6475
|
+
data: { value, metadata, keyId, ...etag === void 0 ? {} : { etag } }
|
|
6476
|
+
};
|
|
6250
6477
|
} catch (error) {
|
|
6251
6478
|
return vaultError({
|
|
6252
6479
|
code: "STORAGE_ERROR",
|
|
@@ -6292,6 +6519,12 @@ var DataVaultService = class extends BaseService {
|
|
|
6292
6519
|
} catch {
|
|
6293
6520
|
return { status: "corrupt_envelope" };
|
|
6294
6521
|
}
|
|
6522
|
+
if (options?.expectAad === true) {
|
|
6523
|
+
const verdict = checkEnvelopeAad(envelope, this.vaultConfig.spaceId, key);
|
|
6524
|
+
if (verdict !== "ok") {
|
|
6525
|
+
return { status: verdict === "corrupt" ? "corrupt_envelope" : "aad_mismatch" };
|
|
6526
|
+
}
|
|
6527
|
+
}
|
|
6295
6528
|
let plaintextResult;
|
|
6296
6529
|
try {
|
|
6297
6530
|
const proof = await this.decryptCapabilityProof();
|
|
@@ -6323,7 +6556,11 @@ var DataVaultService = class extends BaseService {
|
|
|
6323
6556
|
contentType,
|
|
6324
6557
|
options
|
|
6325
6558
|
);
|
|
6326
|
-
|
|
6559
|
+
const etag = valueResult.data.headers?.etag;
|
|
6560
|
+
return {
|
|
6561
|
+
status: "ok",
|
|
6562
|
+
entry: { value, metadata, keyId, ...etag === void 0 ? {} : { etag } }
|
|
6563
|
+
};
|
|
6327
6564
|
} catch {
|
|
6328
6565
|
return { status: "invalid_payload" };
|
|
6329
6566
|
}
|
|
@@ -6373,6 +6610,14 @@ var DataVaultService = class extends BaseService {
|
|
|
6373
6610
|
)
|
|
6374
6611
|
});
|
|
6375
6612
|
}
|
|
6613
|
+
if (options?.ifMatch !== void 0) {
|
|
6614
|
+
return vaultError({
|
|
6615
|
+
code: "STORAGE_ERROR",
|
|
6616
|
+
cause: new Error(
|
|
6617
|
+
"ifMatch is only supported by network-encrypted vaults"
|
|
6618
|
+
)
|
|
6619
|
+
});
|
|
6620
|
+
}
|
|
6376
6621
|
if (!this._isUnlocked || !this.masterKey) {
|
|
6377
6622
|
return vaultError({
|
|
6378
6623
|
code: "VAULT_LOCKED",
|
|
@@ -6398,7 +6643,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6398
6643
|
}
|
|
6399
6644
|
const contentType = options?.contentType ?? (value instanceof Uint8Array ? "application/octet-stream" : "application/json");
|
|
6400
6645
|
const entryKey = this.crypto.randomBytes(32);
|
|
6401
|
-
const keyId =
|
|
6646
|
+
const keyId = hexEncode2(this.crypto.sha256(entryKey)).slice(0, 16);
|
|
6402
6647
|
const encrypted = this.crypto.encrypt(entryKey, plaintext);
|
|
6403
6648
|
const keyBlob = this.crypto.encrypt(this.masterKey, entryKey);
|
|
6404
6649
|
const metadata = {
|
|
@@ -6416,7 +6661,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6416
6661
|
...metadata
|
|
6417
6662
|
});
|
|
6418
6663
|
const keyPayload = JSON.stringify({
|
|
6419
|
-
key:
|
|
6664
|
+
key: base64Encode2(keyBlob),
|
|
6420
6665
|
metadata: keyMetadata
|
|
6421
6666
|
});
|
|
6422
6667
|
const keyPutResult = await this.tc.kv.put(
|
|
@@ -6427,7 +6672,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6427
6672
|
return kvWriteError("Failed to store key blob", keyPutResult.error);
|
|
6428
6673
|
}
|
|
6429
6674
|
const valuePayload = JSON.stringify({
|
|
6430
|
-
data:
|
|
6675
|
+
data: base64Encode2(encrypted),
|
|
6431
6676
|
metadata
|
|
6432
6677
|
});
|
|
6433
6678
|
const valuePutResult = await this.tc.kv.put(
|
|
@@ -6458,6 +6703,12 @@ var DataVaultService = class extends BaseService {
|
|
|
6458
6703
|
if (this.usesNetworkEncryption) {
|
|
6459
6704
|
return this.getNetworkEncrypted(key, options);
|
|
6460
6705
|
}
|
|
6706
|
+
if (options?.expectAad === true) {
|
|
6707
|
+
return vaultError({
|
|
6708
|
+
code: "STORAGE_ERROR",
|
|
6709
|
+
cause: new Error("expectAad is only supported by network-encrypted vaults")
|
|
6710
|
+
});
|
|
6711
|
+
}
|
|
6461
6712
|
if (!this._isUnlocked || !this.masterKey) {
|
|
6462
6713
|
return vaultError({
|
|
6463
6714
|
code: "VAULT_LOCKED",
|
|
@@ -6479,7 +6730,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6479
6730
|
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
6480
6731
|
}
|
|
6481
6732
|
const keyEnvelope = JSON.parse(keyResult.data.data);
|
|
6482
|
-
const keyBlobBytes =
|
|
6733
|
+
const keyBlobBytes = base64Decode2(keyEnvelope.key);
|
|
6483
6734
|
const entryKey = this.crypto.decrypt(this.masterKey, keyBlobBytes);
|
|
6484
6735
|
const valueResult = await this.tc.kv.get(`vault/${key}`, {
|
|
6485
6736
|
raw: true,
|
|
@@ -6489,7 +6740,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6489
6740
|
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
6490
6741
|
}
|
|
6491
6742
|
const valueEnvelope = JSON.parse(valueResult.data.data);
|
|
6492
|
-
const encryptedBytes =
|
|
6743
|
+
const encryptedBytes = base64Decode2(valueEnvelope.data);
|
|
6493
6744
|
const plaintext = this.crypto.decrypt(entryKey, encryptedBytes);
|
|
6494
6745
|
const metadata = valueEnvelope.metadata ?? {};
|
|
6495
6746
|
const contentType = metadata[VaultHeaders.CONTENT_TYPE] ?? "application/json";
|
|
@@ -6737,7 +6988,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6737
6988
|
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
6738
6989
|
}
|
|
6739
6990
|
const keyEnvelope = JSON.parse(keyResult.data.data);
|
|
6740
|
-
const keyBlobBytes =
|
|
6991
|
+
const keyBlobBytes = base64Decode2(keyEnvelope.key);
|
|
6741
6992
|
const entryKey = this.crypto.decrypt(this.masterKey, keyBlobBytes);
|
|
6742
6993
|
const ephemeralSeed = this.crypto.randomBytes(32);
|
|
6743
6994
|
const ephemeralKeyPair = this.crypto.x25519FromSeed(ephemeralSeed);
|
|
@@ -6756,7 +7007,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6756
7007
|
encryptedGrant
|
|
6757
7008
|
);
|
|
6758
7009
|
const grantPayload = JSON.stringify({
|
|
6759
|
-
grant:
|
|
7010
|
+
grant: base64Encode2(grantBlob),
|
|
6760
7011
|
spaceId: this.vaultConfig.spaceId,
|
|
6761
7012
|
metadata: {
|
|
6762
7013
|
[VaultHeaders.GRANT_VERSION]: "1",
|
|
@@ -6796,6 +7047,14 @@ var DataVaultService = class extends BaseService {
|
|
|
6796
7047
|
*/
|
|
6797
7048
|
async getShared(grantorDID, key, options) {
|
|
6798
7049
|
return this.withTelemetry("getShared", key, async () => {
|
|
7050
|
+
if (options?.expectAad === true) {
|
|
7051
|
+
return vaultError({
|
|
7052
|
+
code: "STORAGE_ERROR",
|
|
7053
|
+
cause: new Error(
|
|
7054
|
+
"expectAad is not supported by getShared: the AAD binds the grantor's space, which this vault does not know"
|
|
7055
|
+
)
|
|
7056
|
+
});
|
|
7057
|
+
}
|
|
6799
7058
|
if (this.usesNetworkEncryption) {
|
|
6800
7059
|
const grantorKV = options?.kv;
|
|
6801
7060
|
if (!grantorKV) {
|
|
@@ -6890,7 +7149,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6890
7149
|
});
|
|
6891
7150
|
}
|
|
6892
7151
|
const grantEnvelope = typeof grantResult.data?.data === "string" ? JSON.parse(grantResult.data.data) : grantResult.data?.data;
|
|
6893
|
-
const grantBlobBytes =
|
|
7152
|
+
const grantBlobBytes = base64Decode2(grantEnvelope.grant);
|
|
6894
7153
|
const ephemeralPubKey = grantBlobBytes.slice(0, 32);
|
|
6895
7154
|
const encryptedGrant = grantBlobBytes.slice(32);
|
|
6896
7155
|
const sharedSecret = this.crypto.x25519Dh(
|
|
@@ -6913,7 +7172,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6913
7172
|
});
|
|
6914
7173
|
}
|
|
6915
7174
|
const valueEnvelope = typeof valueResult.data?.data === "string" ? JSON.parse(valueResult.data.data) : valueResult.data?.data;
|
|
6916
|
-
const encryptedBytes =
|
|
7175
|
+
const encryptedBytes = base64Decode2(valueEnvelope.data);
|
|
6917
7176
|
const plaintext = this.crypto.decrypt(entryKey, encryptedBytes);
|
|
6918
7177
|
const metadata = valueEnvelope.metadata ?? {};
|
|
6919
7178
|
const contentType = metadata[VaultHeaders.CONTENT_TYPE] ?? "application/json";
|
|
@@ -6967,7 +7226,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6967
7226
|
if (!result.ok) {
|
|
6968
7227
|
return vaultError({ code: "PUBLIC_KEY_NOT_FOUND", did });
|
|
6969
7228
|
}
|
|
6970
|
-
const pubKeyBytes =
|
|
7229
|
+
const pubKeyBytes = base64Decode2(result.data);
|
|
6971
7230
|
return { ok: true, data: pubKeyBytes };
|
|
6972
7231
|
} catch (error) {
|
|
6973
7232
|
return vaultError({ code: "PUBLIC_KEY_NOT_FOUND", did });
|
|
@@ -7086,7 +7345,7 @@ var DataVaultService = class extends BaseService {
|
|
|
7086
7345
|
}
|
|
7087
7346
|
const currentEntry = getResult.data;
|
|
7088
7347
|
const newEntryKey = this.crypto.randomBytes(32);
|
|
7089
|
-
const newKeyId =
|
|
7348
|
+
const newKeyId = hexEncode2(this.crypto.sha256(newEntryKey)).slice(
|
|
7090
7349
|
0,
|
|
7091
7350
|
16
|
|
7092
7351
|
);
|
|
@@ -7103,7 +7362,7 @@ var DataVaultService = class extends BaseService {
|
|
|
7103
7362
|
[VaultHeaders.KEY_ID]: newKeyId
|
|
7104
7363
|
};
|
|
7105
7364
|
const keyPayload = JSON.stringify({
|
|
7106
|
-
key:
|
|
7365
|
+
key: base64Encode2(newKeyBlob),
|
|
7107
7366
|
metadata: JSON.stringify({
|
|
7108
7367
|
keyId: newKeyId,
|
|
7109
7368
|
...metadata
|
|
@@ -7117,7 +7376,7 @@ var DataVaultService = class extends BaseService {
|
|
|
7117
7376
|
return kvWriteError("Failed to store rotated key blob", keyPutResult.error);
|
|
7118
7377
|
}
|
|
7119
7378
|
const valuePayload = JSON.stringify({
|
|
7120
|
-
data:
|
|
7379
|
+
data: base64Encode2(encrypted),
|
|
7121
7380
|
metadata
|
|
7122
7381
|
});
|
|
7123
7382
|
const valuePutResult = await this.tc.kv.put(
|
|
@@ -7409,116 +7668,6 @@ var SecretsService = class {
|
|
|
7409
7668
|
}
|
|
7410
7669
|
};
|
|
7411
7670
|
|
|
7412
|
-
// src/encryption/canonical.ts
|
|
7413
|
-
function canonicalize(value) {
|
|
7414
|
-
if (value === void 0) {
|
|
7415
|
-
return "";
|
|
7416
|
-
}
|
|
7417
|
-
return stringify(value);
|
|
7418
|
-
}
|
|
7419
|
-
function stringify(value) {
|
|
7420
|
-
if (value === null) return "null";
|
|
7421
|
-
switch (typeof value) {
|
|
7422
|
-
case "boolean":
|
|
7423
|
-
case "number":
|
|
7424
|
-
return JSON.stringify(value);
|
|
7425
|
-
case "string":
|
|
7426
|
-
return JSON.stringify(value);
|
|
7427
|
-
case "object": {
|
|
7428
|
-
if (Array.isArray(value)) {
|
|
7429
|
-
return `[${value.map(stringify).join(",")}]`;
|
|
7430
|
-
}
|
|
7431
|
-
const keys = Object.keys(value).sort();
|
|
7432
|
-
const parts = [];
|
|
7433
|
-
for (const k of keys) {
|
|
7434
|
-
const v = value[k];
|
|
7435
|
-
if (v === void 0) continue;
|
|
7436
|
-
parts.push(`${JSON.stringify(k)}:${stringify(v)}`);
|
|
7437
|
-
}
|
|
7438
|
-
return `{${parts.join(",")}}`;
|
|
7439
|
-
}
|
|
7440
|
-
default:
|
|
7441
|
-
throw new TypeError(
|
|
7442
|
-
`canonicalize: unsupported value type ${typeof value}`
|
|
7443
|
-
);
|
|
7444
|
-
}
|
|
7445
|
-
}
|
|
7446
|
-
var HEX = "0123456789abcdef";
|
|
7447
|
-
function hexEncode2(bytes) {
|
|
7448
|
-
let out = "";
|
|
7449
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
7450
|
-
const b = bytes[i];
|
|
7451
|
-
out += HEX[b >> 4 & 15] + HEX[b & 15];
|
|
7452
|
-
}
|
|
7453
|
-
return out;
|
|
7454
|
-
}
|
|
7455
|
-
function hexDecode(hex) {
|
|
7456
|
-
if (hex.length % 2 !== 0) {
|
|
7457
|
-
throw new Error("hex string must have even length");
|
|
7458
|
-
}
|
|
7459
|
-
const out = new Uint8Array(hex.length / 2);
|
|
7460
|
-
for (let i = 0; i < out.length; i++) {
|
|
7461
|
-
const hi = parseInt(hex[i * 2], 16);
|
|
7462
|
-
const lo = parseInt(hex[i * 2 + 1], 16);
|
|
7463
|
-
if (Number.isNaN(hi) || Number.isNaN(lo)) {
|
|
7464
|
-
throw new Error("invalid hex character");
|
|
7465
|
-
}
|
|
7466
|
-
out[i] = hi << 4 | lo;
|
|
7467
|
-
}
|
|
7468
|
-
return out;
|
|
7469
|
-
}
|
|
7470
|
-
function base64Encode2(bytes) {
|
|
7471
|
-
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
7472
|
-
let out = "";
|
|
7473
|
-
for (let i = 0; i < bytes.length; i += 3) {
|
|
7474
|
-
const b0 = bytes[i];
|
|
7475
|
-
const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
|
|
7476
|
-
const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
|
|
7477
|
-
out += chars[b0 >> 2 & 63];
|
|
7478
|
-
out += chars[(b0 << 4 | b1 >> 4) & 63];
|
|
7479
|
-
out += i + 1 < bytes.length ? chars[(b1 << 2 | b2 >> 6) & 63] : "=";
|
|
7480
|
-
out += i + 2 < bytes.length ? chars[b2 & 63] : "=";
|
|
7481
|
-
}
|
|
7482
|
-
return out;
|
|
7483
|
-
}
|
|
7484
|
-
function base64Decode2(s) {
|
|
7485
|
-
const clean = s.replace(/[^A-Za-z0-9+/=]/g, "");
|
|
7486
|
-
const len = clean.length;
|
|
7487
|
-
if (len % 4 !== 0) {
|
|
7488
|
-
throw new Error("invalid base64 input");
|
|
7489
|
-
}
|
|
7490
|
-
const padding = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
|
|
7491
|
-
const outLen = len / 4 * 3 - padding;
|
|
7492
|
-
const out = new Uint8Array(outLen);
|
|
7493
|
-
const lookup = {};
|
|
7494
|
-
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
7495
|
-
for (let i = 0; i < chars.length; i++) lookup[chars[i]] = i;
|
|
7496
|
-
let outIdx = 0;
|
|
7497
|
-
for (let i = 0; i < len; i += 4) {
|
|
7498
|
-
const v0 = lookup[clean[i]] ?? 0;
|
|
7499
|
-
const v1 = lookup[clean[i + 1]] ?? 0;
|
|
7500
|
-
const v2 = clean[i + 2] === "=" ? 0 : lookup[clean[i + 2]] ?? 0;
|
|
7501
|
-
const v3 = clean[i + 3] === "=" ? 0 : lookup[clean[i + 3]] ?? 0;
|
|
7502
|
-
const b0 = v0 << 2 | v1 >> 4;
|
|
7503
|
-
const b1 = (v1 & 15) << 4 | v2 >> 2;
|
|
7504
|
-
const b2 = (v2 & 3) << 6 | v3;
|
|
7505
|
-
if (outIdx < outLen) out[outIdx++] = b0;
|
|
7506
|
-
if (outIdx < outLen) out[outIdx++] = b1;
|
|
7507
|
-
if (outIdx < outLen) out[outIdx++] = b2;
|
|
7508
|
-
}
|
|
7509
|
-
return out;
|
|
7510
|
-
}
|
|
7511
|
-
function utf8Encode(s) {
|
|
7512
|
-
return new TextEncoder().encode(s);
|
|
7513
|
-
}
|
|
7514
|
-
function utf8Decode(b) {
|
|
7515
|
-
return new TextDecoder().decode(b);
|
|
7516
|
-
}
|
|
7517
|
-
function canonicalHashHex(sha256, value) {
|
|
7518
|
-
const canonical = canonicalize(value);
|
|
7519
|
-
return hexEncode2(sha256(utf8Encode(canonical)));
|
|
7520
|
-
}
|
|
7521
|
-
|
|
7522
7671
|
// src/encryption/types.ts
|
|
7523
7672
|
var DEFAULT_ENCRYPTION_ALG = "x25519-aes256gcm/v1";
|
|
7524
7673
|
var ENVELOPE_VERSION = 1;
|
|
@@ -7728,7 +7877,7 @@ function encryptToNetwork(crypto2, input) {
|
|
|
7728
7877
|
const symmetricKey = crypto2.randomBytes(32);
|
|
7729
7878
|
const ciphertext = crypto2.authEncrypt(symmetricKey, input.plaintext, input.aad);
|
|
7730
7879
|
const wrapped = crypto2.sealToNetworkKey(input.networkPublicKey, symmetricKey);
|
|
7731
|
-
const encryptedSymmetricKey =
|
|
7880
|
+
const encryptedSymmetricKey = base64Encode(wrapped);
|
|
7732
7881
|
const encryptedSymmetricKeyHash = canonicalHashHex(
|
|
7733
7882
|
crypto2.sha256,
|
|
7734
7883
|
encryptedSymmetricKey
|
|
@@ -7740,8 +7889,8 @@ function encryptToNetwork(crypto2, input) {
|
|
|
7740
7889
|
keyVersion,
|
|
7741
7890
|
encryptedSymmetricKey,
|
|
7742
7891
|
encryptedSymmetricKeyHash,
|
|
7743
|
-
ciphertext:
|
|
7744
|
-
...input.aad !== void 0 ? { aad:
|
|
7892
|
+
ciphertext: base64Encode(ciphertext),
|
|
7893
|
+
...input.aad !== void 0 ? { aad: base64Encode(input.aad) } : {},
|
|
7745
7894
|
...input.metadata !== void 0 ? { metadata: input.metadata } : {}
|
|
7746
7895
|
};
|
|
7747
7896
|
return { envelope, symmetricKey };
|
|
@@ -7815,8 +7964,8 @@ function validateEnvelope(crypto2, envelope) {
|
|
|
7815
7964
|
return { ok: true, data: e };
|
|
7816
7965
|
}
|
|
7817
7966
|
function decryptEnvelopeWithKey(crypto2, envelope, symmetricKey) {
|
|
7818
|
-
const ciphertext =
|
|
7819
|
-
const aad = envelope.aad !== void 0 ?
|
|
7967
|
+
const ciphertext = base64Decode(envelope.ciphertext);
|
|
7968
|
+
const aad = envelope.aad !== void 0 ? base64Decode(envelope.aad) : void 0;
|
|
7820
7969
|
return crypto2.authDecrypt(symmetricKey, ciphertext, aad);
|
|
7821
7970
|
}
|
|
7822
7971
|
|
|
@@ -7834,7 +7983,7 @@ function buildCanonicalDecryptRequest(input) {
|
|
|
7834
7983
|
return { canonicalBody, bodyHash, receiverPublicKeyHash };
|
|
7835
7984
|
}
|
|
7836
7985
|
function buildDecryptFacts(input) {
|
|
7837
|
-
const bodyHash = input.canonicalBody !== void 0 ?
|
|
7986
|
+
const bodyHash = input.canonicalBody !== void 0 ? hexEncode(input.crypto.sha256(utf8Encode(input.canonicalBody))) : canonicalHashHex(
|
|
7838
7987
|
input.crypto.sha256,
|
|
7839
7988
|
input.body
|
|
7840
7989
|
);
|
|
@@ -8117,7 +8266,7 @@ function verifyDecryptResponse(input) {
|
|
|
8117
8266
|
})
|
|
8118
8267
|
};
|
|
8119
8268
|
}
|
|
8120
|
-
const expectedRequestHash =
|
|
8269
|
+
const expectedRequestHash = hexEncode(
|
|
8121
8270
|
crypto2.sha256(utf8Encode(`${invocationCid}${requestBodyHash}`))
|
|
8122
8271
|
);
|
|
8123
8272
|
if (response.requestHash !== expectedRequestHash) {
|
|
@@ -8141,7 +8290,7 @@ function verifyDecryptResponse(input) {
|
|
|
8141
8290
|
const signedBytes = new TextEncoder().encode(
|
|
8142
8291
|
canonicalSignedResponse(response)
|
|
8143
8292
|
);
|
|
8144
|
-
const signatureBytes =
|
|
8293
|
+
const signatureBytes = base64Decode(response.nodeSignature);
|
|
8145
8294
|
if (!crypto2.verifyNodeSignature(response.nodeId, signedBytes, signatureBytes)) {
|
|
8146
8295
|
return {
|
|
8147
8296
|
ok: false,
|
|
@@ -8153,7 +8302,7 @@ function verifyDecryptResponse(input) {
|
|
|
8153
8302
|
return { ok: true, data: response };
|
|
8154
8303
|
}
|
|
8155
8304
|
function openWrappedKey(crypto2, receiverPrivateKey, response) {
|
|
8156
|
-
const wrapped =
|
|
8305
|
+
const wrapped = base64Decode(response.wrappedKey);
|
|
8157
8306
|
return crypto2.openWithReceiverKey(receiverPrivateKey, wrapped);
|
|
8158
8307
|
}
|
|
8159
8308
|
|
|
@@ -8197,7 +8346,7 @@ var EncryptionService = class extends BaseService {
|
|
|
8197
8346
|
const usable = ensureNetworkUsableForDecrypt(discovered.data);
|
|
8198
8347
|
if (!usable.ok) return usable;
|
|
8199
8348
|
const descriptor = usable.data;
|
|
8200
|
-
const networkPublicKey =
|
|
8349
|
+
const networkPublicKey = base64Decode(descriptor.publicEncryptionKey);
|
|
8201
8350
|
const result = encryptToNetwork(this.crypto, {
|
|
8202
8351
|
// Discovery returns the node's canonical network identifier. Persist
|
|
8203
8352
|
// that spelling in the envelope so a later response can be compared
|
|
@@ -8225,7 +8374,7 @@ var EncryptionService = class extends BaseService {
|
|
|
8225
8374
|
this.assertActive();
|
|
8226
8375
|
const validated = validateEnvelope(this.crypto, envelope);
|
|
8227
8376
|
if (!validated.ok) return validated;
|
|
8228
|
-
if (options?.aad !== void 0 && validated.data.aad !==
|
|
8377
|
+
if (options?.aad !== void 0 && validated.data.aad !== base64Encode(options.aad)) {
|
|
8229
8378
|
return encErr(
|
|
8230
8379
|
encryptionError({
|
|
8231
8380
|
code: "INVALID_INPUT",
|
|
@@ -8253,7 +8402,7 @@ var EncryptionService = class extends BaseService {
|
|
|
8253
8402
|
);
|
|
8254
8403
|
}
|
|
8255
8404
|
const receiverKey = generateRandomReceiverKey({ crypto: this.crypto });
|
|
8256
|
-
const receiverPublicKey =
|
|
8405
|
+
const receiverPublicKey = base64Encode(receiverKey.publicKey);
|
|
8257
8406
|
const receiverPublicKeyHash = canonicalHashHex(
|
|
8258
8407
|
this.crypto.sha256,
|
|
8259
8408
|
receiverPublicKey
|
|
@@ -8468,6 +8617,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
|
|
|
8468
8617
|
kvPrefixCovers,
|
|
8469
8618
|
networkDiscoveryKey,
|
|
8470
8619
|
networkError,
|
|
8620
|
+
nodeRetryError,
|
|
8471
8621
|
notFoundError,
|
|
8472
8622
|
ok,
|
|
8473
8623
|
openWrappedKey,
|