@tinycloud/sdk-services 3.1.0 → 3.2.0-beta.3
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-DWbPbov7.d.cts → BaseService-BDQXjWMx.d.cts} +1 -1
- package/dist/{BaseService-B_UKhVht.d.ts → BaseService-Di_hVfUV.d.ts} +1 -1
- package/dist/encryption/index.cjs +7 -1
- 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 +7 -1
- package/dist/encryption/index.js.map +1 -1
- package/dist/index.cjs +683 -362
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +81 -29
- package/dist/index.d.ts +81 -29
- package/dist/index.js +674 -362
- 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 +532 -336
- package/dist/kv/index.cjs.map +1 -1
- package/dist/kv/index.d.cts +17 -468
- package/dist/kv/index.d.ts +17 -468
- package/dist/kv/index.js +526 -335
- package/dist/kv/index.js.map +1 -1
- package/dist/kv/replication/index.cjs +1375 -0
- package/dist/kv/replication/index.cjs.map +1 -0
- package/dist/kv/replication/index.d.cts +9 -0
- package/dist/kv/replication/index.d.ts +9 -0
- package/dist/kv/replication/index.js +1342 -0
- package/dist/kv/replication/index.js.map +1 -0
- package/dist/scope-BmLxwyl-.d.ts +957 -0
- package/dist/scope-D855kRDx.d.cts +957 -0
- package/dist/sql/index.cjs +12 -6
- 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 +12 -6
- package/dist/sql/index.js.map +1 -1
- package/dist/{types-CVwwxqXF.d.cts → types-7A5rHWRY.d.cts} +10 -1
- package/dist/{types-CVwwxqXF.d.ts → types-7A5rHWRY.d.ts} +10 -1
- package/package.json +18 -1
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
23
|
BaseService: () => BaseService,
|
|
24
|
+
CLEAR_PENDING_WARNING: () => CLEAR_PENDING_WARNING,
|
|
24
25
|
DECRYPT_ACTION: () => DECRYPT_ACTION,
|
|
25
26
|
DECRYPT_FACT_TYPE: () => DECRYPT_FACT_TYPE,
|
|
26
27
|
DECRYPT_RESULT_TYPE: () => DECRYPT_RESULT_TYPE,
|
|
@@ -47,11 +48,13 @@ __export(src_exports, {
|
|
|
47
48
|
KVListResultSchema: () => KVListResultSchema,
|
|
48
49
|
KVResponseHeadersSchema: () => KVResponseHeadersSchema,
|
|
49
50
|
KVService: () => KVService,
|
|
51
|
+
MAX_SECRET_SCOPE_LENGTH: () => MAX_SECRET_SCOPE_LENGTH,
|
|
50
52
|
NETWORK_NAME_PATTERN: () => NETWORK_NAME_PATTERN,
|
|
51
53
|
NetworkIdError: () => NetworkIdError,
|
|
52
54
|
PrefixedKVService: () => PrefixedKVService,
|
|
53
55
|
RetryPolicySchema: () => RetryPolicySchema,
|
|
54
56
|
SECRET_NAME_RE: () => SECRET_NAME_RE,
|
|
57
|
+
SECRET_SCOPE_RE: () => SECRET_SCOPE_RE,
|
|
55
58
|
SQLAction: () => SQLAction,
|
|
56
59
|
SQLMigrations: () => SQLMigrations,
|
|
57
60
|
SQLService: () => SQLService,
|
|
@@ -65,6 +68,7 @@ __export(src_exports, {
|
|
|
65
68
|
ServiceResponseEventSchema: () => ServiceResponseEventSchema,
|
|
66
69
|
ServiceRetryEventSchema: () => ServiceRetryEventSchema,
|
|
67
70
|
ServiceSessionSchema: () => ServiceSessionSchema,
|
|
71
|
+
TC832_RESERVED_SCOPES: () => TC832_RESERVED_SCOPES,
|
|
68
72
|
TelemetryEvents: () => TelemetryEvents,
|
|
69
73
|
TelemetrySpanEventSchema: () => TelemetrySpanEventSchema,
|
|
70
74
|
TinyCloudDebugLogger: () => TinyCloudDebugLogger,
|
|
@@ -72,6 +76,7 @@ __export(src_exports, {
|
|
|
72
76
|
VaultHeaders: () => VaultHeaders,
|
|
73
77
|
VaultPublicSpaceKVActions: () => VaultPublicSpaceKVActions,
|
|
74
78
|
abortedError: () => abortedError,
|
|
79
|
+
appSecretScope: () => appSecretScope,
|
|
75
80
|
authExpiredError: () => authExpiredError,
|
|
76
81
|
authRequiredError: () => authRequiredError,
|
|
77
82
|
authUnauthorizedError: () => authUnauthorizedError,
|
|
@@ -84,6 +89,7 @@ __export(src_exports, {
|
|
|
84
89
|
buildDecryptInvocation: () => buildDecryptInvocation,
|
|
85
90
|
buildNetworkId: () => buildNetworkId,
|
|
86
91
|
canonicalHashHex: () => canonicalHashHex,
|
|
92
|
+
canonicalReplicationIdentity: () => canonicalReplicationIdentity,
|
|
87
93
|
canonicalSignedResponse: () => canonicalSignedResponse,
|
|
88
94
|
canonicalizeEncryptionJson: () => canonicalize,
|
|
89
95
|
canonicalizeSecretScope: () => canonicalizeSecretScope,
|
|
@@ -110,6 +116,7 @@ __export(src_exports, {
|
|
|
110
116
|
installTinyCloudDebugGlobals: () => installTinyCloudDebugGlobals,
|
|
111
117
|
isNetworkId: () => isNetworkId,
|
|
112
118
|
isStorageFullError: () => isStorageFullError,
|
|
119
|
+
kvPrefixCovers: () => kvPrefixCovers,
|
|
113
120
|
networkDiscoveryKey: () => networkDiscoveryKey,
|
|
114
121
|
networkError: () => networkError,
|
|
115
122
|
notFoundError: () => notFoundError,
|
|
@@ -123,6 +130,8 @@ __export(src_exports, {
|
|
|
123
130
|
parseSecretCatalogKey: () => parseSecretCatalogKey,
|
|
124
131
|
parseStorageQuotaBytes: () => parseStorageQuotaBytes,
|
|
125
132
|
permissionDeniedError: () => permissionDeniedError,
|
|
133
|
+
replicationIdentityKey: () => replicationIdentityKey,
|
|
134
|
+
requiresSecretsOptIn: () => requiresSecretsOptIn,
|
|
126
135
|
resolveSecretListPrefix: () => resolveSecretListPrefix,
|
|
127
136
|
resolveSecretPath: () => resolveSecretPath,
|
|
128
137
|
serviceError: () => serviceError,
|
|
@@ -210,7 +219,12 @@ var TelemetryEvents = {
|
|
|
210
219
|
SERVICE_ERROR: "service.error",
|
|
211
220
|
SERVICE_RETRY: "service.retry",
|
|
212
221
|
SESSION_CHANGED: "session.changed",
|
|
213
|
-
SESSION_EXPIRED: "session.expired"
|
|
222
|
+
SESSION_EXPIRED: "session.expired",
|
|
223
|
+
REPLICATION_READ: "replication.read",
|
|
224
|
+
REPLICATION_WRITE: "replication.write",
|
|
225
|
+
REPLICATION_SYNC: "replication.sync",
|
|
226
|
+
REPLICATION_STATE: "replication.state",
|
|
227
|
+
REPLICATION_DIVERGENCE: "replication.divergence"
|
|
214
228
|
};
|
|
215
229
|
function ok(data) {
|
|
216
230
|
return { ok: true, data };
|
|
@@ -236,9 +250,34 @@ var SAFE_NUMBER_FIELDS = /* @__PURE__ */ new Set([
|
|
|
236
250
|
"endedAt",
|
|
237
251
|
"startedAt",
|
|
238
252
|
"status",
|
|
239
|
-
"timestamp"
|
|
253
|
+
"timestamp",
|
|
254
|
+
"latencyMs",
|
|
255
|
+
"stalenessMs",
|
|
256
|
+
"count",
|
|
257
|
+
"changes",
|
|
258
|
+
"deleted",
|
|
259
|
+
"fetched",
|
|
260
|
+
"pages",
|
|
261
|
+
"lagMs",
|
|
262
|
+
"pendingCleared"
|
|
240
263
|
]);
|
|
241
264
|
var SAFE_BOOLEAN_FIELDS = /* @__PURE__ */ new Set(["authenticated", "ok", "persisted"]);
|
|
265
|
+
var SAFE_REPLICATION_FIELDS = {
|
|
266
|
+
source: ["replica", "network", "none"],
|
|
267
|
+
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"],
|
|
268
|
+
op: ["get", "list", "put", "delete", "batchPut"],
|
|
269
|
+
trigger: ["start", "interval", "stale_read", "manual"],
|
|
270
|
+
outcome: ["found", "not_found", "error", "committed", "failed", "ambiguous", "ok", "busy", "aborted"],
|
|
271
|
+
class: ["offline", "authority", "storage", "node"],
|
|
272
|
+
state: ["opened", "grant_installed", "grant_missing", "runtime_unsupported", "unavailable", "revoked", "recreated", "purged", "purge_failed", "pending_store_error", "pinned", "pending_cleared", "closed", "idle", "ready"],
|
|
273
|
+
kind: ["value", "missing_local", "extra_local", "keys", "order"],
|
|
274
|
+
coverage: ["empty", "bootstrapping", "complete"],
|
|
275
|
+
authority: ["valid", "expired", "revoked", "not-yet-valid"],
|
|
276
|
+
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"],
|
|
277
|
+
pendingState: ["in_flight", "committed", "ambiguous"],
|
|
278
|
+
strategy: ["session", "minted", "installed"],
|
|
279
|
+
verify: ["match", "diverged", "error", "aborted"]
|
|
280
|
+
};
|
|
242
281
|
function finiteNumber(value) {
|
|
243
282
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
244
283
|
}
|
|
@@ -275,6 +314,12 @@ function projectDiagnosticData(value) {
|
|
|
275
314
|
const boolean = read(value, key);
|
|
276
315
|
if (typeof boolean === "boolean") projected[key] = boolean;
|
|
277
316
|
}
|
|
317
|
+
for (const [key, allowed] of Object.entries(SAFE_REPLICATION_FIELDS)) {
|
|
318
|
+
const candidate = read(value, key);
|
|
319
|
+
if (typeof candidate === "string" && allowed.includes(candidate)) {
|
|
320
|
+
projected[key] = candidate;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
278
323
|
const url = read(value, "url");
|
|
279
324
|
if (url !== void 0) projected.url = REDACTED;
|
|
280
325
|
const error = read(value, "error");
|
|
@@ -899,6 +944,12 @@ var ServiceContext = class {
|
|
|
899
944
|
get abortSignal() {
|
|
900
945
|
return this._abortController.signal;
|
|
901
946
|
}
|
|
947
|
+
get operationAbortSignal() {
|
|
948
|
+
return this._operationAbortSignal;
|
|
949
|
+
}
|
|
950
|
+
setOperationAbortSignal(signal) {
|
|
951
|
+
this._operationAbortSignal = signal;
|
|
952
|
+
}
|
|
902
953
|
/**
|
|
903
954
|
* Permanently retire this graph after its owner installs a replacement.
|
|
904
955
|
* Unlike `abort()`, retirement never creates a fresh controller, so captured
|
|
@@ -1650,6 +1701,7 @@ var BaseService = class {
|
|
|
1650
1701
|
const parents = [
|
|
1651
1702
|
this.abortController.signal,
|
|
1652
1703
|
this.context?.abortSignal,
|
|
1704
|
+
this.context?.operationAbortSignal,
|
|
1653
1705
|
signal
|
|
1654
1706
|
].filter((parent) => parent !== void 0);
|
|
1655
1707
|
for (const parent of parents) {
|
|
@@ -1895,14 +1947,28 @@ var KVService = class extends BaseService {
|
|
|
1895
1947
|
*/
|
|
1896
1948
|
constructor(config = {}) {
|
|
1897
1949
|
super();
|
|
1950
|
+
this.readThrough = null;
|
|
1951
|
+
this.readThroughReady = null;
|
|
1898
1952
|
this._config = config;
|
|
1899
1953
|
}
|
|
1954
|
+
/** Delay KV routing until an asynchronously loaded read-through is attached. */
|
|
1955
|
+
setReadThroughReady(ready) {
|
|
1956
|
+
const settled = ready.then(() => void 0, () => void 0);
|
|
1957
|
+
this.readThroughReady = settled;
|
|
1958
|
+
void settled.then(() => {
|
|
1959
|
+
if (this.readThroughReady === settled) this.readThroughReady = null;
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1900
1962
|
/**
|
|
1901
1963
|
* Get the service configuration.
|
|
1902
1964
|
*/
|
|
1903
1965
|
get config() {
|
|
1904
1966
|
return this._config;
|
|
1905
1967
|
}
|
|
1968
|
+
/** Install or remove the optional replication read-through policy. */
|
|
1969
|
+
setReadThrough(readThrough) {
|
|
1970
|
+
this.readThrough = readThrough;
|
|
1971
|
+
}
|
|
1906
1972
|
handleQuotaErrorResponse(response, errorText, key) {
|
|
1907
1973
|
if (response.status !== 402 && response.status !== 413) {
|
|
1908
1974
|
return void 0;
|
|
@@ -2156,6 +2222,7 @@ var KVService = class extends BaseService {
|
|
|
2156
2222
|
return aligned;
|
|
2157
2223
|
}
|
|
2158
2224
|
async batchRead(keys, action, options) {
|
|
2225
|
+
if (this.readThroughReady !== null) await this.readThroughReady;
|
|
2159
2226
|
if (!this.requireAuth()) return err(authRequiredError("kv"));
|
|
2160
2227
|
if (keys.length === 0) return ok({ results: [], count: 0 });
|
|
2161
2228
|
if (keys.length > MAX_KV_BATCH_READ_ITEMS) {
|
|
@@ -2406,6 +2473,7 @@ var KVService = class extends BaseService {
|
|
|
2406
2473
|
*/
|
|
2407
2474
|
async get(key, options) {
|
|
2408
2475
|
return this.withTelemetry("get", key, async () => {
|
|
2476
|
+
if (this.readThroughReady !== null) await this.readThroughReady;
|
|
2409
2477
|
if (!this.requireAuth()) {
|
|
2410
2478
|
return err(authRequiredError("kv"));
|
|
2411
2479
|
}
|
|
@@ -2419,64 +2487,38 @@ var KVService = class extends BaseService {
|
|
|
2419
2487
|
const path = this.getFullPath(key, options?.prefix);
|
|
2420
2488
|
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
2421
2489
|
try {
|
|
2422
|
-
const
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
response,
|
|
2445
|
-
errorText2,
|
|
2446
|
-
[path],
|
|
2447
|
-
KVAction.GET,
|
|
2448
|
-
structuredCapability ? { permissionHint, ...structuredCapability } : {}
|
|
2449
|
-
);
|
|
2450
|
-
}
|
|
2451
|
-
if (response.status === 404) {
|
|
2452
|
-
return this.classifyNotFound(response, key);
|
|
2453
|
-
}
|
|
2454
|
-
const errorText = await response.text();
|
|
2455
|
-
if (response.status === 413) {
|
|
2456
|
-
return err(serviceError(
|
|
2457
|
-
ErrorCodes.KV_RESPONSE_TOO_LARGE,
|
|
2458
|
-
`KV value at key ${JSON.stringify(key)} exceeds the requested response limit`,
|
|
2459
|
-
"kv",
|
|
2460
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2461
|
-
));
|
|
2490
|
+
const network = () => this.getFromNetwork(key, path, options, request.signal);
|
|
2491
|
+
if (options?.source === "network") {
|
|
2492
|
+
const observer = this.readThrough;
|
|
2493
|
+
if (observer === null) return await network();
|
|
2494
|
+
let outcome = "error";
|
|
2495
|
+
const startedAt = Date.now();
|
|
2496
|
+
try {
|
|
2497
|
+
const result = await network();
|
|
2498
|
+
outcome = result.ok ? "found" : result.error.code === ErrorCodes.KV_NOT_FOUND ? "not_found" : "error";
|
|
2499
|
+
return result;
|
|
2500
|
+
} finally {
|
|
2501
|
+
try {
|
|
2502
|
+
observer.observeNetworkRequested({
|
|
2503
|
+
op: "get",
|
|
2504
|
+
space: this.context.session.spaceId,
|
|
2505
|
+
path,
|
|
2506
|
+
reason: "NETWORK_REQUESTED",
|
|
2507
|
+
outcome,
|
|
2508
|
+
latencyMs: Date.now() - startedAt
|
|
2509
|
+
});
|
|
2510
|
+
} catch {
|
|
2511
|
+
}
|
|
2462
2512
|
}
|
|
2463
|
-
return err(
|
|
2464
|
-
serviceError(
|
|
2465
|
-
ErrorCodes.NETWORK_ERROR,
|
|
2466
|
-
`Failed to get key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
|
|
2467
|
-
"kv",
|
|
2468
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2469
|
-
)
|
|
2470
|
-
);
|
|
2471
2513
|
}
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2514
|
+
if (this.readThrough === null) return await network();
|
|
2515
|
+
return await this.readThrough.get({
|
|
2516
|
+
space: this.context.session.spaceId,
|
|
2517
|
+
key,
|
|
2518
|
+
path,
|
|
2519
|
+
options,
|
|
2520
|
+
signal: request.signal,
|
|
2521
|
+
network
|
|
2480
2522
|
});
|
|
2481
2523
|
} catch (error) {
|
|
2482
2524
|
return err(wrapError("kv", error));
|
|
@@ -2485,6 +2527,56 @@ var KVService = class extends BaseService {
|
|
|
2485
2527
|
}
|
|
2486
2528
|
});
|
|
2487
2529
|
}
|
|
2530
|
+
async getFromNetwork(key, path, options, signal) {
|
|
2531
|
+
const response = await this.invokeOperation(
|
|
2532
|
+
path,
|
|
2533
|
+
KVAction.GET,
|
|
2534
|
+
void 0,
|
|
2535
|
+
signal,
|
|
2536
|
+
options?.maxResponseBytes === void 0 ? void 0 : { "x-tinycloud-max-response-bytes": String(options.maxResponseBytes) }
|
|
2537
|
+
);
|
|
2538
|
+
if (!response.ok) {
|
|
2539
|
+
if (response.status === 401 || response.status === 403) {
|
|
2540
|
+
const errorText2 = await this.readAuthorizationText(response);
|
|
2541
|
+
const permissionHint = parsePermissionHintFromErrorText(errorText2);
|
|
2542
|
+
const structuredCapability = permissionHint?.service === "tinycloud.kv" && permissionHint.space === this.context.session.spaceId && permissionHint.path === path ? validatedCapabilityOf({
|
|
2543
|
+
service: "kv",
|
|
2544
|
+
code: ErrorCodes.AUTH_UNAUTHORIZED,
|
|
2545
|
+
meta: {
|
|
2546
|
+
status: response.status,
|
|
2547
|
+
resource: `${this.context.session.spaceId}/kv/${path}`,
|
|
2548
|
+
requiredAction: KVAction.GET
|
|
2549
|
+
}
|
|
2550
|
+
}) : void 0;
|
|
2551
|
+
return this.authorizationFailure(
|
|
2552
|
+
`Failed to get key ${JSON.stringify(key)}`,
|
|
2553
|
+
response,
|
|
2554
|
+
errorText2,
|
|
2555
|
+
[path],
|
|
2556
|
+
KVAction.GET,
|
|
2557
|
+
structuredCapability ? { permissionHint, ...structuredCapability } : {}
|
|
2558
|
+
);
|
|
2559
|
+
}
|
|
2560
|
+
if (response.status === 404) return this.classifyNotFound(response, key);
|
|
2561
|
+
const errorText = await response.text();
|
|
2562
|
+
if (response.status === 413) {
|
|
2563
|
+
return err(serviceError(
|
|
2564
|
+
ErrorCodes.KV_RESPONSE_TOO_LARGE,
|
|
2565
|
+
`KV value at key ${JSON.stringify(key)} exceeds the requested response limit`,
|
|
2566
|
+
"kv",
|
|
2567
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2568
|
+
));
|
|
2569
|
+
}
|
|
2570
|
+
return err(serviceError(
|
|
2571
|
+
ErrorCodes.NETWORK_ERROR,
|
|
2572
|
+
`Failed to get key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
|
|
2573
|
+
"kv",
|
|
2574
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2575
|
+
));
|
|
2576
|
+
}
|
|
2577
|
+
const data = await this.parseResponse(response, options?.raw, options?.binary);
|
|
2578
|
+
return ok({ data, headers: this.createResponseHeaders(response.headers) });
|
|
2579
|
+
}
|
|
2488
2580
|
async batchGet(keys, options) {
|
|
2489
2581
|
return this.withTelemetry(
|
|
2490
2582
|
"batchGet",
|
|
@@ -2497,6 +2589,7 @@ var KVService = class extends BaseService {
|
|
|
2497
2589
|
*/
|
|
2498
2590
|
async put(key, value, options) {
|
|
2499
2591
|
return this.withTelemetry("put", key, async () => {
|
|
2592
|
+
if (this.readThroughReady !== null) await this.readThroughReady;
|
|
2500
2593
|
if (!this.requireAuth()) {
|
|
2501
2594
|
return err(authRequiredError("kv"));
|
|
2502
2595
|
}
|
|
@@ -2511,63 +2604,15 @@ var KVService = class extends BaseService {
|
|
|
2511
2604
|
const body = this.serializePutValue(value, options?.contentType);
|
|
2512
2605
|
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
2513
2606
|
try {
|
|
2514
|
-
const
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
{
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
);
|
|
2524
|
-
if (response.status === 401 || response.status === 403) {
|
|
2525
|
-
return this.authorizationFailure(
|
|
2526
|
-
`Failed to put key ${JSON.stringify(key)}`,
|
|
2527
|
-
response,
|
|
2528
|
-
await this.readAuthorizationText(response),
|
|
2529
|
-
[path],
|
|
2530
|
-
KVAction.PUT
|
|
2531
|
-
);
|
|
2532
|
-
}
|
|
2533
|
-
if (!response.ok) {
|
|
2534
|
-
const errorText = await response.text();
|
|
2535
|
-
if (response.status === 412) {
|
|
2536
|
-
return err(serviceError(
|
|
2537
|
-
ErrorCodes.KV_PRECONDITION_FAILED,
|
|
2538
|
-
`KV precondition failed for key ${JSON.stringify(key)}`,
|
|
2539
|
-
"kv",
|
|
2540
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2541
|
-
));
|
|
2542
|
-
}
|
|
2543
|
-
if (response.status === 503 && (options?.ifMatch !== void 0 || options?.ifNoneMatch !== void 0)) {
|
|
2544
|
-
return err(serviceError(
|
|
2545
|
-
ErrorCodes.KV_CONFLICT,
|
|
2546
|
-
`Concurrent KV update conflicted for key ${JSON.stringify(key)}`,
|
|
2547
|
-
"kv",
|
|
2548
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2549
|
-
));
|
|
2550
|
-
}
|
|
2551
|
-
const quotaError = this.handleQuotaErrorResponse(
|
|
2552
|
-
response,
|
|
2553
|
-
errorText,
|
|
2554
|
-
key
|
|
2555
|
-
);
|
|
2556
|
-
if (quotaError) {
|
|
2557
|
-
return quotaError;
|
|
2558
|
-
}
|
|
2559
|
-
return err(
|
|
2560
|
-
serviceError(
|
|
2561
|
-
ErrorCodes.KV_WRITE_FAILED,
|
|
2562
|
-
`Failed to put key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
|
|
2563
|
-
"kv",
|
|
2564
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2565
|
-
)
|
|
2566
|
-
);
|
|
2567
|
-
}
|
|
2568
|
-
return ok({
|
|
2569
|
-
data: void 0,
|
|
2570
|
-
headers: this.createResponseHeaders(response.headers)
|
|
2607
|
+
const network = () => this.putToNetwork(key, path, body, options, request.signal);
|
|
2608
|
+
if (this.readThrough === null) return await network();
|
|
2609
|
+
return await this.readThrough.write({
|
|
2610
|
+
op: "put",
|
|
2611
|
+
space: this.context.session.spaceId,
|
|
2612
|
+
entries: [{ path, body }],
|
|
2613
|
+
signal: request.signal,
|
|
2614
|
+
conditional: options?.ifMatch !== void 0 || options?.ifNoneMatch !== void 0,
|
|
2615
|
+
network
|
|
2571
2616
|
});
|
|
2572
2617
|
} catch (error) {
|
|
2573
2618
|
return err(wrapError("kv", error));
|
|
@@ -2576,11 +2621,64 @@ var KVService = class extends BaseService {
|
|
|
2576
2621
|
}
|
|
2577
2622
|
});
|
|
2578
2623
|
}
|
|
2624
|
+
async putToNetwork(key, path, body, options, signal) {
|
|
2625
|
+
const response = await this.invokeOperation(
|
|
2626
|
+
path,
|
|
2627
|
+
KVAction.PUT,
|
|
2628
|
+
body,
|
|
2629
|
+
signal,
|
|
2630
|
+
{
|
|
2631
|
+
...options?.ifMatch === void 0 ? {} : { "if-match": options.ifMatch },
|
|
2632
|
+
...options?.ifNoneMatch === void 0 ? {} : { "if-none-match": options.ifNoneMatch }
|
|
2633
|
+
}
|
|
2634
|
+
);
|
|
2635
|
+
if (response.status === 401 || response.status === 403) {
|
|
2636
|
+
return this.authorizationFailure(
|
|
2637
|
+
`Failed to put key ${JSON.stringify(key)}`,
|
|
2638
|
+
response,
|
|
2639
|
+
await this.readAuthorizationText(response),
|
|
2640
|
+
[path],
|
|
2641
|
+
KVAction.PUT
|
|
2642
|
+
);
|
|
2643
|
+
}
|
|
2644
|
+
if (!response.ok) {
|
|
2645
|
+
const errorText = await response.text();
|
|
2646
|
+
if (response.status === 412) {
|
|
2647
|
+
return err(serviceError(
|
|
2648
|
+
ErrorCodes.KV_PRECONDITION_FAILED,
|
|
2649
|
+
`KV precondition failed for key ${JSON.stringify(key)}`,
|
|
2650
|
+
"kv",
|
|
2651
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2652
|
+
));
|
|
2653
|
+
}
|
|
2654
|
+
if (response.status === 503 && (options?.ifMatch !== void 0 || options?.ifNoneMatch !== void 0)) {
|
|
2655
|
+
return err(serviceError(
|
|
2656
|
+
ErrorCodes.KV_CONFLICT,
|
|
2657
|
+
`Concurrent KV update conflicted for key ${JSON.stringify(key)}`,
|
|
2658
|
+
"kv",
|
|
2659
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2660
|
+
));
|
|
2661
|
+
}
|
|
2662
|
+
const quotaError = this.handleQuotaErrorResponse(response, errorText, key);
|
|
2663
|
+
if (quotaError) return quotaError;
|
|
2664
|
+
return err(serviceError(
|
|
2665
|
+
ErrorCodes.KV_WRITE_FAILED,
|
|
2666
|
+
`Failed to put key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
|
|
2667
|
+
"kv",
|
|
2668
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2669
|
+
));
|
|
2670
|
+
}
|
|
2671
|
+
return ok({
|
|
2672
|
+
data: void 0,
|
|
2673
|
+
headers: this.createResponseHeaders(response.headers)
|
|
2674
|
+
});
|
|
2675
|
+
}
|
|
2579
2676
|
/**
|
|
2580
2677
|
* Store multiple values in one TinyCloud KV invocation.
|
|
2581
2678
|
*/
|
|
2582
2679
|
async batchPut(items, options) {
|
|
2583
2680
|
return this.withTelemetry("batchPut", String(items.length), async () => {
|
|
2681
|
+
if (this.readThroughReady !== null) await this.readThroughReady;
|
|
2584
2682
|
if (!this.requireAuth()) {
|
|
2585
2683
|
return err(authRequiredError("kv"));
|
|
2586
2684
|
}
|
|
@@ -2611,155 +2709,177 @@ var KVService = class extends BaseService {
|
|
|
2611
2709
|
}
|
|
2612
2710
|
seen.add(path);
|
|
2613
2711
|
}
|
|
2614
|
-
let requestMayHaveDispatched = false;
|
|
2615
2712
|
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
2616
2713
|
try {
|
|
2617
|
-
const
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
session,
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2714
|
+
const network = () => this.batchPutToNetwork(items, paths, request.signal);
|
|
2715
|
+
if (this.readThrough === null) return await network();
|
|
2716
|
+
const entries = items.map((item, index) => ({
|
|
2717
|
+
path: paths[index],
|
|
2718
|
+
body: this.serializeBatchPutValue(item)
|
|
2719
|
+
}));
|
|
2720
|
+
return await this.readThrough.write({
|
|
2721
|
+
op: "batchPut",
|
|
2722
|
+
space: session.spaceId,
|
|
2723
|
+
entries,
|
|
2724
|
+
signal: request.signal,
|
|
2725
|
+
network: () => this.batchPutToNetwork(items, paths, request.signal, entries)
|
|
2726
|
+
});
|
|
2727
|
+
} catch (error) {
|
|
2728
|
+
const wrapped = wrapError("kv", error);
|
|
2729
|
+
return err({ ...wrapped, meta: { ...wrapped.meta, requestMayHaveDispatched: false } });
|
|
2730
|
+
} finally {
|
|
2731
|
+
request.dispose();
|
|
2732
|
+
}
|
|
2733
|
+
});
|
|
2734
|
+
}
|
|
2735
|
+
async batchPutToNetwork(items, paths, signal, entries) {
|
|
2736
|
+
const session = this.context.session;
|
|
2737
|
+
let requestMayHaveDispatched = false;
|
|
2738
|
+
try {
|
|
2739
|
+
const body = new FormData();
|
|
2740
|
+
for (let index = 0; index < items.length; index++) {
|
|
2741
|
+
body.append(
|
|
2742
|
+
encodeKvBatchPartName(paths[index]),
|
|
2743
|
+
entries?.[index]?.body ?? this.serializeBatchPutValue(items[index])
|
|
2632
2744
|
);
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
return this.authorizationFailure(
|
|
2660
|
-
`Failed to batch put ${items.length} key(s)`,
|
|
2661
|
-
response,
|
|
2662
|
-
errorText,
|
|
2663
|
-
paths,
|
|
2664
|
-
KVAction.PUT
|
|
2665
|
-
);
|
|
2666
|
-
}
|
|
2667
|
-
const quotaError = this.handleQuotaErrorResponse(
|
|
2668
|
-
response,
|
|
2669
|
-
errorText,
|
|
2670
|
-
"batch"
|
|
2671
|
-
);
|
|
2672
|
-
if (quotaError) {
|
|
2673
|
-
return quotaError;
|
|
2674
|
-
}
|
|
2745
|
+
}
|
|
2746
|
+
const headers = this.context.invokeAny(
|
|
2747
|
+
session,
|
|
2748
|
+
paths.map((path) => ({
|
|
2749
|
+
spaceId: session.spaceId,
|
|
2750
|
+
service: "kv",
|
|
2751
|
+
path,
|
|
2752
|
+
action: KVAction.PUT
|
|
2753
|
+
}))
|
|
2754
|
+
);
|
|
2755
|
+
const url = `${this.host}/invoke`;
|
|
2756
|
+
const init = {
|
|
2757
|
+
method: "POST",
|
|
2758
|
+
headers,
|
|
2759
|
+
body,
|
|
2760
|
+
signal
|
|
2761
|
+
};
|
|
2762
|
+
const fetchFn = this.context.fetch;
|
|
2763
|
+
requestMayHaveDispatched = true;
|
|
2764
|
+
const response = await fetchFn(url, init);
|
|
2765
|
+
if (!response.ok) {
|
|
2766
|
+
let errorText;
|
|
2767
|
+
try {
|
|
2768
|
+
errorText = await response.text();
|
|
2769
|
+
} catch (textError) {
|
|
2770
|
+
const cause = textError instanceof Error ? textError : new Error(String(textError));
|
|
2675
2771
|
return err(
|
|
2676
2772
|
serviceError(
|
|
2677
2773
|
ErrorCodes.KV_WRITE_FAILED,
|
|
2678
|
-
`Failed to batch put ${items.length} key(s): ${response.status} - ${
|
|
2774
|
+
`Failed to batch put ${items.length} key(s): ${response.status} - <response body could not be read: ${cause.message}>`,
|
|
2679
2775
|
"kv",
|
|
2680
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2776
|
+
{ cause, meta: { status: response.status, statusText: response.statusText } }
|
|
2681
2777
|
)
|
|
2682
2778
|
);
|
|
2683
2779
|
}
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
serviceError(
|
|
2692
|
-
timedOut ? ErrorCodes.TIMEOUT : ErrorCodes.NETWORK_ERROR,
|
|
2693
|
-
timedOut ? `KV batchPut response body did not arrive in time: ${cause.message}` : `KV batchPut response was not valid JSON: ${cause.message}`,
|
|
2694
|
-
"kv",
|
|
2695
|
-
{
|
|
2696
|
-
cause,
|
|
2697
|
-
meta: {
|
|
2698
|
-
...timedOut ? { timeoutMs: cause.timeoutMs } : {},
|
|
2699
|
-
requestMayHaveDispatched: true,
|
|
2700
|
-
responseReceived: true,
|
|
2701
|
-
status: response.status,
|
|
2702
|
-
outcome: "batch-unconfirmed"
|
|
2703
|
-
}
|
|
2704
|
-
}
|
|
2705
|
-
)
|
|
2780
|
+
if (response.status === 401 || response.status === 403) {
|
|
2781
|
+
return this.authorizationFailure(
|
|
2782
|
+
`Failed to batch put ${items.length} key(s)`,
|
|
2783
|
+
response,
|
|
2784
|
+
errorText,
|
|
2785
|
+
paths,
|
|
2786
|
+
KVAction.PUT
|
|
2706
2787
|
);
|
|
2707
2788
|
}
|
|
2708
|
-
const
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
{
|
|
2716
|
-
meta: {
|
|
2717
|
-
requestMayHaveDispatched: true,
|
|
2718
|
-
responseReceived: true,
|
|
2719
|
-
status: response.status,
|
|
2720
|
-
outcome: "batch-unconfirmed"
|
|
2721
|
-
}
|
|
2722
|
-
}
|
|
2723
|
-
)
|
|
2724
|
-
);
|
|
2789
|
+
const quotaError = this.handleQuotaErrorResponse(
|
|
2790
|
+
response,
|
|
2791
|
+
errorText,
|
|
2792
|
+
"batch"
|
|
2793
|
+
);
|
|
2794
|
+
if (quotaError) {
|
|
2795
|
+
return quotaError;
|
|
2725
2796
|
}
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2797
|
+
return err(
|
|
2798
|
+
serviceError(
|
|
2799
|
+
ErrorCodes.KV_WRITE_FAILED,
|
|
2800
|
+
`Failed to batch put ${items.length} key(s): ${response.status} - ${errorText}`,
|
|
2801
|
+
"kv",
|
|
2802
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2803
|
+
)
|
|
2804
|
+
);
|
|
2805
|
+
}
|
|
2806
|
+
let rawBody;
|
|
2807
|
+
try {
|
|
2808
|
+
rawBody = await response.json();
|
|
2809
|
+
} catch (jsonError) {
|
|
2810
|
+
const cause = jsonError instanceof Error ? jsonError : new Error(String(jsonError));
|
|
2811
|
+
const timedOut = isRequestTimeoutError(cause);
|
|
2812
|
+
return err(
|
|
2813
|
+
serviceError(
|
|
2814
|
+
timedOut ? ErrorCodes.TIMEOUT : ErrorCodes.NETWORK_ERROR,
|
|
2815
|
+
timedOut ? `KV batchPut response body did not arrive in time: ${cause.message}` : `KV batchPut response was not valid JSON: ${cause.message}`,
|
|
2816
|
+
"kv",
|
|
2817
|
+
{
|
|
2818
|
+
cause,
|
|
2819
|
+
meta: {
|
|
2820
|
+
...timedOut ? { timeoutMs: cause.timeoutMs } : {},
|
|
2821
|
+
requestMayHaveDispatched: true,
|
|
2822
|
+
responseReceived: true,
|
|
2823
|
+
status: response.status,
|
|
2824
|
+
outcome: "batch-unconfirmed"
|
|
2742
2825
|
}
|
|
2743
|
-
|
|
2744
|
-
)
|
|
2745
|
-
|
|
2746
|
-
return ok(batchResponse);
|
|
2747
|
-
} catch (error) {
|
|
2748
|
-
const wrapped = wrapError("kv", error);
|
|
2749
|
-
return err({
|
|
2750
|
-
...wrapped,
|
|
2751
|
-
meta: { ...wrapped.meta, requestMayHaveDispatched }
|
|
2752
|
-
});
|
|
2753
|
-
} finally {
|
|
2754
|
-
request.dispose();
|
|
2826
|
+
}
|
|
2827
|
+
)
|
|
2828
|
+
);
|
|
2755
2829
|
}
|
|
2756
|
-
|
|
2830
|
+
const batchResponse = this.normalizeBatchPutResponse(rawBody);
|
|
2831
|
+
if (!batchResponse) {
|
|
2832
|
+
return err(
|
|
2833
|
+
serviceError(
|
|
2834
|
+
ErrorCodes.NETWORK_ERROR,
|
|
2835
|
+
"KV batchPut response did not include matching written keys and count",
|
|
2836
|
+
"kv",
|
|
2837
|
+
{
|
|
2838
|
+
meta: {
|
|
2839
|
+
requestMayHaveDispatched: true,
|
|
2840
|
+
responseReceived: true,
|
|
2841
|
+
status: response.status,
|
|
2842
|
+
outcome: "batch-unconfirmed"
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
)
|
|
2846
|
+
);
|
|
2847
|
+
}
|
|
2848
|
+
const requestedPaths = new Set(paths);
|
|
2849
|
+
const writtenPaths = new Set(batchResponse.written);
|
|
2850
|
+
const matchesRequest = batchResponse.count === paths.length && batchResponse.written.length === paths.length && writtenPaths.size === paths.length && batchResponse.written.every((key) => requestedPaths.has(key));
|
|
2851
|
+
if (!matchesRequest) {
|
|
2852
|
+
return err(
|
|
2853
|
+
serviceError(
|
|
2854
|
+
ErrorCodes.NETWORK_ERROR,
|
|
2855
|
+
`KV batchPut response did not confirm all ${paths.length} requested key(s) were written`,
|
|
2856
|
+
"kv",
|
|
2857
|
+
{
|
|
2858
|
+
meta: {
|
|
2859
|
+
requestMayHaveDispatched: true,
|
|
2860
|
+
responseReceived: true,
|
|
2861
|
+
status: response.status,
|
|
2862
|
+
outcome: "batch-unconfirmed"
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
)
|
|
2866
|
+
);
|
|
2867
|
+
}
|
|
2868
|
+
return ok(batchResponse);
|
|
2869
|
+
} catch (error) {
|
|
2870
|
+
const wrapped = wrapError("kv", error);
|
|
2871
|
+
return err({
|
|
2872
|
+
...wrapped,
|
|
2873
|
+
meta: { ...wrapped.meta, requestMayHaveDispatched }
|
|
2874
|
+
});
|
|
2875
|
+
}
|
|
2757
2876
|
}
|
|
2758
2877
|
/**
|
|
2759
2878
|
* List keys with optional prefix filtering.
|
|
2760
2879
|
*/
|
|
2761
2880
|
async list(options) {
|
|
2762
2881
|
return this.withTelemetry("list", options?.prefix, async () => {
|
|
2882
|
+
if (this.readThroughReady !== null) await this.readThroughReady;
|
|
2763
2883
|
if (!this.requireAuth()) {
|
|
2764
2884
|
return err(authRequiredError("kv"));
|
|
2765
2885
|
}
|
|
@@ -2776,49 +2896,44 @@ var KVService = class extends BaseService {
|
|
|
2776
2896
|
}
|
|
2777
2897
|
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
2778
2898
|
try {
|
|
2779
|
-
const
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2899
|
+
const network = () => this.listFromNetwork(listPath, options, request.signal);
|
|
2900
|
+
let page;
|
|
2901
|
+
if (options?.source === "network") {
|
|
2902
|
+
const observer = this.readThrough;
|
|
2903
|
+
if (observer === null) {
|
|
2904
|
+
page = await network();
|
|
2905
|
+
} else {
|
|
2906
|
+
let outcome = "error";
|
|
2907
|
+
const startedAt = Date.now();
|
|
2908
|
+
try {
|
|
2909
|
+
page = await network();
|
|
2910
|
+
outcome = page.ok ? "found" : "error";
|
|
2911
|
+
} finally {
|
|
2912
|
+
try {
|
|
2913
|
+
observer.observeNetworkRequested({
|
|
2914
|
+
op: "list",
|
|
2915
|
+
space: this.context.session.spaceId,
|
|
2916
|
+
path: listPath,
|
|
2917
|
+
reason: "NETWORK_REQUESTED",
|
|
2918
|
+
outcome,
|
|
2919
|
+
latencyMs: Date.now() - startedAt
|
|
2920
|
+
});
|
|
2921
|
+
} catch {
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2798
2924
|
}
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
let keys = await this.parseResponse(response, options?.raw);
|
|
2810
|
-
keys = keys ?? [];
|
|
2811
|
-
if (options?.removePrefix && listPath) {
|
|
2812
|
-
const prefixWithSlash = listPath.endsWith("/") ? listPath : `${listPath}/`;
|
|
2813
|
-
keys = keys.map(
|
|
2814
|
-
(key) => key.startsWith(prefixWithSlash) ? key.slice(prefixWithSlash.length) : key
|
|
2815
|
-
);
|
|
2925
|
+
} else if (this.readThrough === null) {
|
|
2926
|
+
page = await network();
|
|
2927
|
+
} else {
|
|
2928
|
+
page = await this.readThrough.list({
|
|
2929
|
+
space: this.context.session.spaceId,
|
|
2930
|
+
listPath,
|
|
2931
|
+
options,
|
|
2932
|
+
signal: request.signal,
|
|
2933
|
+
network
|
|
2934
|
+
});
|
|
2816
2935
|
}
|
|
2817
|
-
return ok(
|
|
2818
|
-
keys,
|
|
2819
|
-
...response.headers.get("x-tinycloud-truncated") === null ? {} : { truncated: response.headers.get("x-tinycloud-truncated") === "true" },
|
|
2820
|
-
...response.headers.get("x-tinycloud-next-cursor") === null ? {} : { nextCursor: response.headers.get("x-tinycloud-next-cursor") ?? void 0 }
|
|
2821
|
-
});
|
|
2936
|
+
return page.ok ? this.finishList(page.data, listPath, options?.removePrefix) : page;
|
|
2822
2937
|
} catch (error) {
|
|
2823
2938
|
return err(wrapError("kv", error));
|
|
2824
2939
|
} finally {
|
|
@@ -2826,6 +2941,56 @@ var KVService = class extends BaseService {
|
|
|
2826
2941
|
}
|
|
2827
2942
|
});
|
|
2828
2943
|
}
|
|
2944
|
+
async listFromNetwork(listPath, options, signal) {
|
|
2945
|
+
const response = await this.invokeOperation(
|
|
2946
|
+
listPath,
|
|
2947
|
+
KVAction.LIST,
|
|
2948
|
+
void 0,
|
|
2949
|
+
signal,
|
|
2950
|
+
options?.limit === void 0 && options?.cursor === void 0 ? void 0 : {
|
|
2951
|
+
...options?.limit === void 0 ? {} : { "x-tinycloud-limit": String(options.limit) },
|
|
2952
|
+
...options?.cursor === void 0 ? {} : { "x-tinycloud-cursor": options.cursor }
|
|
2953
|
+
}
|
|
2954
|
+
);
|
|
2955
|
+
if (!response.ok) {
|
|
2956
|
+
if (response.status === 401 || response.status === 403) {
|
|
2957
|
+
return this.authorizationFailure(
|
|
2958
|
+
"Failed to list keys",
|
|
2959
|
+
response,
|
|
2960
|
+
await this.readAuthorizationText(response),
|
|
2961
|
+
[listPath],
|
|
2962
|
+
KVAction.LIST
|
|
2963
|
+
);
|
|
2964
|
+
}
|
|
2965
|
+
const errorText = await response.text();
|
|
2966
|
+
return err(serviceError(
|
|
2967
|
+
ErrorCodes.NETWORK_ERROR,
|
|
2968
|
+
`Failed to list keys: ${response.status} - ${errorText}`,
|
|
2969
|
+
"kv",
|
|
2970
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2971
|
+
));
|
|
2972
|
+
}
|
|
2973
|
+
const keys = await this.parseResponse(response, options?.raw) ?? [];
|
|
2974
|
+
return ok({
|
|
2975
|
+
keys,
|
|
2976
|
+
...response.headers.get("x-tinycloud-truncated") === null ? {} : { truncated: response.headers.get("x-tinycloud-truncated") === "true" },
|
|
2977
|
+
...response.headers.get("x-tinycloud-next-cursor") === null ? {} : { nextCursor: response.headers.get("x-tinycloud-next-cursor") ?? void 0 }
|
|
2978
|
+
});
|
|
2979
|
+
}
|
|
2980
|
+
finishList(page, listPath, removePrefix) {
|
|
2981
|
+
let keys = page.keys;
|
|
2982
|
+
if (removePrefix && listPath) {
|
|
2983
|
+
const prefixWithSlash = listPath.endsWith("/") ? listPath : `${listPath}/`;
|
|
2984
|
+
keys = keys.map(
|
|
2985
|
+
(key) => key.startsWith(prefixWithSlash) ? key.slice(prefixWithSlash.length) : key
|
|
2986
|
+
);
|
|
2987
|
+
}
|
|
2988
|
+
return ok({
|
|
2989
|
+
keys,
|
|
2990
|
+
...page.truncated === void 0 ? {} : { truncated: page.truncated },
|
|
2991
|
+
...page.nextCursor === void 0 ? {} : { nextCursor: page.nextCursor }
|
|
2992
|
+
});
|
|
2993
|
+
}
|
|
2829
2994
|
/**
|
|
2830
2995
|
* Read one page of the `tinycloud.kv/sync` change feed for a prefix.
|
|
2831
2996
|
*/
|
|
@@ -2994,61 +3159,22 @@ var KVService = class extends BaseService {
|
|
|
2994
3159
|
*/
|
|
2995
3160
|
async delete(key, options) {
|
|
2996
3161
|
return this.withTelemetry("delete", key, async () => {
|
|
3162
|
+
if (this.readThroughReady !== null) await this.readThroughReady;
|
|
2997
3163
|
if (!this.requireAuth()) {
|
|
2998
3164
|
return err(authRequiredError("kv"));
|
|
2999
3165
|
}
|
|
3000
3166
|
const path = this.getFullPath(key, options?.prefix);
|
|
3001
3167
|
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
3002
3168
|
try {
|
|
3003
|
-
const
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
return this.authorizationFailure(
|
|
3013
|
-
`Failed to delete key ${JSON.stringify(key)}`,
|
|
3014
|
-
response,
|
|
3015
|
-
await this.readAuthorizationText(response),
|
|
3016
|
-
[path],
|
|
3017
|
-
KVAction.DELETE
|
|
3018
|
-
);
|
|
3019
|
-
}
|
|
3020
|
-
if (response.status === 404) {
|
|
3021
|
-
return this.classifyNotFound(response, key);
|
|
3022
|
-
}
|
|
3023
|
-
const errorText = await response.text();
|
|
3024
|
-
if (response.status === 412) {
|
|
3025
|
-
return err(serviceError(
|
|
3026
|
-
ErrorCodes.KV_PRECONDITION_FAILED,
|
|
3027
|
-
`KV precondition failed for key ${JSON.stringify(key)}`,
|
|
3028
|
-
"kv",
|
|
3029
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
3030
|
-
));
|
|
3031
|
-
}
|
|
3032
|
-
if (response.status === 503 && options?.ifMatch !== void 0) {
|
|
3033
|
-
return err(serviceError(
|
|
3034
|
-
ErrorCodes.KV_CONFLICT,
|
|
3035
|
-
`Concurrent KV delete conflicted for key ${JSON.stringify(key)}`,
|
|
3036
|
-
"kv",
|
|
3037
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
3038
|
-
));
|
|
3039
|
-
}
|
|
3040
|
-
return err(
|
|
3041
|
-
serviceError(
|
|
3042
|
-
ErrorCodes.NETWORK_ERROR,
|
|
3043
|
-
`Failed to delete key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
|
|
3044
|
-
"kv",
|
|
3045
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
3046
|
-
)
|
|
3047
|
-
);
|
|
3048
|
-
}
|
|
3049
|
-
return ok({
|
|
3050
|
-
data: void 0,
|
|
3051
|
-
headers: this.createResponseHeaders(response.headers)
|
|
3169
|
+
const network = () => this.deleteFromNetwork(key, path, options, request.signal);
|
|
3170
|
+
if (this.readThrough === null) return await network();
|
|
3171
|
+
return await this.readThrough.write({
|
|
3172
|
+
op: "delete",
|
|
3173
|
+
space: this.context.session.spaceId,
|
|
3174
|
+
entries: [{ path }],
|
|
3175
|
+
signal: request.signal,
|
|
3176
|
+
conditional: options?.ifMatch !== void 0,
|
|
3177
|
+
network
|
|
3052
3178
|
});
|
|
3053
3179
|
} catch (error) {
|
|
3054
3180
|
return err(wrapError("kv", error));
|
|
@@ -3057,6 +3183,54 @@ var KVService = class extends BaseService {
|
|
|
3057
3183
|
}
|
|
3058
3184
|
});
|
|
3059
3185
|
}
|
|
3186
|
+
async deleteFromNetwork(key, path, options, signal) {
|
|
3187
|
+
const response = await this.invokeOperation(
|
|
3188
|
+
path,
|
|
3189
|
+
KVAction.DELETE,
|
|
3190
|
+
void 0,
|
|
3191
|
+
signal,
|
|
3192
|
+
options?.ifMatch === void 0 ? void 0 : { "if-match": options.ifMatch }
|
|
3193
|
+
);
|
|
3194
|
+
if (!response.ok) {
|
|
3195
|
+
if (response.status === 401 || response.status === 403) {
|
|
3196
|
+
return this.authorizationFailure(
|
|
3197
|
+
`Failed to delete key ${JSON.stringify(key)}`,
|
|
3198
|
+
response,
|
|
3199
|
+
await this.readAuthorizationText(response),
|
|
3200
|
+
[path],
|
|
3201
|
+
KVAction.DELETE
|
|
3202
|
+
);
|
|
3203
|
+
}
|
|
3204
|
+
if (response.status === 404) return this.classifyNotFound(response, key);
|
|
3205
|
+
const errorText = await response.text();
|
|
3206
|
+
if (response.status === 412) {
|
|
3207
|
+
return err(serviceError(
|
|
3208
|
+
ErrorCodes.KV_PRECONDITION_FAILED,
|
|
3209
|
+
`KV precondition failed for key ${JSON.stringify(key)}`,
|
|
3210
|
+
"kv",
|
|
3211
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
3212
|
+
));
|
|
3213
|
+
}
|
|
3214
|
+
if (response.status === 503 && options?.ifMatch !== void 0) {
|
|
3215
|
+
return err(serviceError(
|
|
3216
|
+
ErrorCodes.KV_CONFLICT,
|
|
3217
|
+
`Concurrent KV delete conflicted for key ${JSON.stringify(key)}`,
|
|
3218
|
+
"kv",
|
|
3219
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
3220
|
+
));
|
|
3221
|
+
}
|
|
3222
|
+
return err(serviceError(
|
|
3223
|
+
ErrorCodes.NETWORK_ERROR,
|
|
3224
|
+
`Failed to delete key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
|
|
3225
|
+
"kv",
|
|
3226
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
3227
|
+
));
|
|
3228
|
+
}
|
|
3229
|
+
return ok({
|
|
3230
|
+
data: void 0,
|
|
3231
|
+
headers: this.createResponseHeaders(response.headers)
|
|
3232
|
+
});
|
|
3233
|
+
}
|
|
3060
3234
|
/**
|
|
3061
3235
|
* Get metadata for a key without retrieving the value.
|
|
3062
3236
|
*/
|
|
@@ -3085,7 +3259,7 @@ var KVService = class extends BaseService {
|
|
|
3085
3259
|
);
|
|
3086
3260
|
}
|
|
3087
3261
|
if (response.status === 404) {
|
|
3088
|
-
return this.classifyNotFound(response, key);
|
|
3262
|
+
return await this.classifyNotFound(response, key);
|
|
3089
3263
|
}
|
|
3090
3264
|
const errorText = await response.text();
|
|
3091
3265
|
return err(
|
|
@@ -3151,7 +3325,7 @@ var KVService = class extends BaseService {
|
|
|
3151
3325
|
signal: request.signal
|
|
3152
3326
|
});
|
|
3153
3327
|
if (!response.ok) {
|
|
3154
|
-
return this.createSignedReadUrlError(response, key, path);
|
|
3328
|
+
return await this.createSignedReadUrlError(response, key, path);
|
|
3155
3329
|
}
|
|
3156
3330
|
const signedUrl = this.normalizeSignedReadUrlResponse(
|
|
3157
3331
|
await response.json()
|
|
@@ -3224,6 +3398,64 @@ var KVService = class extends BaseService {
|
|
|
3224
3398
|
*/
|
|
3225
3399
|
KVService.serviceName = "kv";
|
|
3226
3400
|
|
|
3401
|
+
// src/kv/replication/constants.ts
|
|
3402
|
+
var CLEAR_PENDING_WARNING = "Clearing pending writes surrenders read-your-writes protection for the cleared keys. If a write whose record was cleared had an unknown outcome or never finished, it may commit now or later; this device can serve an older value until the next sync after that commit, as it would for a write from another device.";
|
|
3403
|
+
|
|
3404
|
+
// src/kv/replication/identity.ts
|
|
3405
|
+
var SPACE_ID = /^(tinycloud:pkh:eip155:\d+:)([^:]+)(:.+)$/;
|
|
3406
|
+
var PRINCIPAL_DID = /^(did:pkh:eip155:\d+:)([^:]+)$/;
|
|
3407
|
+
function canonicalReplicationIdentity(raw) {
|
|
3408
|
+
if (!raw || typeof raw.host !== "string" || typeof raw.space !== "string" || typeof raw.principal !== "string") {
|
|
3409
|
+
throw new TypeError(
|
|
3410
|
+
"Replication identity host, space, and principal must be strings"
|
|
3411
|
+
);
|
|
3412
|
+
}
|
|
3413
|
+
const authority = /^https?:\/\/([^/?#]*)/i.exec(raw.host)?.[1];
|
|
3414
|
+
let url;
|
|
3415
|
+
try {
|
|
3416
|
+
url = new URL(raw.host);
|
|
3417
|
+
} catch {
|
|
3418
|
+
throw new TypeError(
|
|
3419
|
+
"Replication identity host must be an absolute HTTP(S) URL"
|
|
3420
|
+
);
|
|
3421
|
+
}
|
|
3422
|
+
if (!authority || authority.includes("@") || raw.host.includes("?") || raw.host.includes("#") || url.username || url.password) {
|
|
3423
|
+
throw new TypeError(
|
|
3424
|
+
"Replication identity host must be HTTP(S) without credentials, query, or fragment"
|
|
3425
|
+
);
|
|
3426
|
+
}
|
|
3427
|
+
const space = SPACE_ID.exec(raw.space);
|
|
3428
|
+
const principal = PRINCIPAL_DID.exec(raw.principal);
|
|
3429
|
+
if (!space || !principal) {
|
|
3430
|
+
throw new TypeError(
|
|
3431
|
+
"Replication identity requires a full TinyCloud space id and a pkh principal DID"
|
|
3432
|
+
);
|
|
3433
|
+
}
|
|
3434
|
+
return {
|
|
3435
|
+
host: `${url.origin}${url.pathname.replace(/\/+$/, "")}`,
|
|
3436
|
+
space: `${space[1]}${space[2].toLowerCase()}${space[3]}`,
|
|
3437
|
+
principal: `${principal[1]}${principal[2].toLowerCase()}`
|
|
3438
|
+
};
|
|
3439
|
+
}
|
|
3440
|
+
function replicationIdentityKey(id) {
|
|
3441
|
+
const canonical = canonicalReplicationIdentity(id);
|
|
3442
|
+
return `tc-replication/v1
|
|
3443
|
+
${canonical.host}
|
|
3444
|
+
${canonical.space}
|
|
3445
|
+
${canonical.principal}`;
|
|
3446
|
+
}
|
|
3447
|
+
|
|
3448
|
+
// src/kv/replication/scope.ts
|
|
3449
|
+
function kvPrefixCovers(prefix, key) {
|
|
3450
|
+
if (prefix === "") return true;
|
|
3451
|
+
if (prefix.endsWith("/")) return key.startsWith(prefix);
|
|
3452
|
+
return key === prefix || key.startsWith(`${prefix}/`);
|
|
3453
|
+
}
|
|
3454
|
+
function requiresSecretsOptIn(space, prefix) {
|
|
3455
|
+
if (space === "secrets" || space.endsWith(":secrets")) return true;
|
|
3456
|
+
return prefix.split("/", 1)[0] === "vault";
|
|
3457
|
+
}
|
|
3458
|
+
|
|
3227
3459
|
// src/responseErrors.ts
|
|
3228
3460
|
function parseServiceErrorBody(errorText) {
|
|
3229
3461
|
try {
|
|
@@ -3400,7 +3632,7 @@ var SQLService = class extends BaseService {
|
|
|
3400
3632
|
request.signal
|
|
3401
3633
|
);
|
|
3402
3634
|
if (!response.ok) {
|
|
3403
|
-
return this.handleErrorResponse(response, "query", dbName, [action]);
|
|
3635
|
+
return await this.handleErrorResponse(response, "query", dbName, [action]);
|
|
3404
3636
|
}
|
|
3405
3637
|
const data = await response.json();
|
|
3406
3638
|
return ok(data);
|
|
@@ -3440,7 +3672,7 @@ var SQLService = class extends BaseService {
|
|
|
3440
3672
|
request.signal
|
|
3441
3673
|
);
|
|
3442
3674
|
if (!response.ok) {
|
|
3443
|
-
return this.handleErrorResponse(response, "execute", dbName, requestedActions);
|
|
3675
|
+
return await this.handleErrorResponse(response, "execute", dbName, requestedActions);
|
|
3444
3676
|
}
|
|
3445
3677
|
const data = await response.json();
|
|
3446
3678
|
return ok(data);
|
|
@@ -3466,7 +3698,7 @@ var SQLService = class extends BaseService {
|
|
|
3466
3698
|
request.signal
|
|
3467
3699
|
);
|
|
3468
3700
|
if (!response.ok) {
|
|
3469
|
-
return this.handleErrorResponse(response, "batch", dbName, requestedActions);
|
|
3701
|
+
return await this.handleErrorResponse(response, "batch", dbName, requestedActions);
|
|
3470
3702
|
}
|
|
3471
3703
|
const data = await response.json();
|
|
3472
3704
|
return ok(data);
|
|
@@ -3491,7 +3723,7 @@ var SQLService = class extends BaseService {
|
|
|
3491
3723
|
request.signal
|
|
3492
3724
|
);
|
|
3493
3725
|
if (!response.ok) {
|
|
3494
|
-
return this.handleErrorResponse(response, "executeStatement", dbName, [SQLAction.WRITE]);
|
|
3726
|
+
return await this.handleErrorResponse(response, "executeStatement", dbName, [SQLAction.WRITE]);
|
|
3495
3727
|
}
|
|
3496
3728
|
const data = await response.json();
|
|
3497
3729
|
return ok(data);
|
|
@@ -3516,7 +3748,7 @@ var SQLService = class extends BaseService {
|
|
|
3516
3748
|
request.signal
|
|
3517
3749
|
);
|
|
3518
3750
|
if (!response.ok) {
|
|
3519
|
-
return this.handleErrorResponse(response, "export", dbName, [SQLAction.READ]);
|
|
3751
|
+
return await this.handleErrorResponse(response, "export", dbName, [SQLAction.READ]);
|
|
3520
3752
|
}
|
|
3521
3753
|
const resp = response;
|
|
3522
3754
|
if (typeof resp.blob === "function") {
|
|
@@ -3892,7 +4124,7 @@ var DuckDbService = class extends BaseService {
|
|
|
3892
4124
|
request.signal
|
|
3893
4125
|
);
|
|
3894
4126
|
if (!response.ok) {
|
|
3895
|
-
return this.handleErrorResponse(response, "query");
|
|
4127
|
+
return await this.handleErrorResponse(response, "query");
|
|
3896
4128
|
}
|
|
3897
4129
|
const data = await response.json();
|
|
3898
4130
|
return ok(data);
|
|
@@ -3918,7 +4150,7 @@ var DuckDbService = class extends BaseService {
|
|
|
3918
4150
|
{ Accept: "application/vnd.apache.arrow.stream" }
|
|
3919
4151
|
);
|
|
3920
4152
|
if (!response.ok) {
|
|
3921
|
-
return this.handleErrorResponse(response, "queryArrow");
|
|
4153
|
+
return await this.handleErrorResponse(response, "queryArrow");
|
|
3922
4154
|
}
|
|
3923
4155
|
const buffer = await response.arrayBuffer();
|
|
3924
4156
|
return ok(buffer);
|
|
@@ -3951,7 +4183,7 @@ var DuckDbService = class extends BaseService {
|
|
|
3951
4183
|
request.signal
|
|
3952
4184
|
);
|
|
3953
4185
|
if (!response.ok) {
|
|
3954
|
-
return this.handleErrorResponse(response, "execute");
|
|
4186
|
+
return await this.handleErrorResponse(response, "execute");
|
|
3955
4187
|
}
|
|
3956
4188
|
const data = await response.json();
|
|
3957
4189
|
return ok(data);
|
|
@@ -3983,7 +4215,7 @@ var DuckDbService = class extends BaseService {
|
|
|
3983
4215
|
request.signal
|
|
3984
4216
|
);
|
|
3985
4217
|
if (!response.ok) {
|
|
3986
|
-
return this.handleErrorResponse(response, "batch");
|
|
4218
|
+
return await this.handleErrorResponse(response, "batch");
|
|
3987
4219
|
}
|
|
3988
4220
|
const data = await response.json();
|
|
3989
4221
|
return ok(data);
|
|
@@ -4008,7 +4240,7 @@ var DuckDbService = class extends BaseService {
|
|
|
4008
4240
|
request.signal
|
|
4009
4241
|
);
|
|
4010
4242
|
if (!response.ok) {
|
|
4011
|
-
return this.handleErrorResponse(response, "executeStatement");
|
|
4243
|
+
return await this.handleErrorResponse(response, "executeStatement");
|
|
4012
4244
|
}
|
|
4013
4245
|
const data = await response.json();
|
|
4014
4246
|
return ok(data);
|
|
@@ -4033,7 +4265,7 @@ var DuckDbService = class extends BaseService {
|
|
|
4033
4265
|
request.signal
|
|
4034
4266
|
);
|
|
4035
4267
|
if (!response.ok) {
|
|
4036
|
-
return this.handleErrorResponse(response, "describe");
|
|
4268
|
+
return await this.handleErrorResponse(response, "describe");
|
|
4037
4269
|
}
|
|
4038
4270
|
const data = await response.json();
|
|
4039
4271
|
return ok(data);
|
|
@@ -4058,7 +4290,7 @@ var DuckDbService = class extends BaseService {
|
|
|
4058
4290
|
request.signal
|
|
4059
4291
|
);
|
|
4060
4292
|
if (!response.ok) {
|
|
4061
|
-
return this.handleErrorResponse(response, "export");
|
|
4293
|
+
return await this.handleErrorResponse(response, "export");
|
|
4062
4294
|
}
|
|
4063
4295
|
const blob = await response.blob();
|
|
4064
4296
|
return ok(blob);
|
|
@@ -4093,7 +4325,7 @@ var DuckDbService = class extends BaseService {
|
|
|
4093
4325
|
signal: request.signal
|
|
4094
4326
|
});
|
|
4095
4327
|
if (!response.ok) {
|
|
4096
|
-
return this.handleErrorResponse(response, "import");
|
|
4328
|
+
return await this.handleErrorResponse(response, "import");
|
|
4097
4329
|
}
|
|
4098
4330
|
return ok(void 0);
|
|
4099
4331
|
} catch (error) {
|
|
@@ -5442,6 +5674,12 @@ function defaultVaultMessage(input) {
|
|
|
5442
5674
|
return input.message ?? `Public key not found for ${input.did}`;
|
|
5443
5675
|
case "STORAGE_ERROR":
|
|
5444
5676
|
return input.message ?? input.cause.message;
|
|
5677
|
+
case "ALREADY_EXISTS":
|
|
5678
|
+
return input.message ?? `A value already exists at ${input.key}`;
|
|
5679
|
+
case "AUTH_UNAUTHORIZED":
|
|
5680
|
+
return input.message;
|
|
5681
|
+
case "PERMISSION_DENIED":
|
|
5682
|
+
return input.message;
|
|
5445
5683
|
case "STORAGE_QUOTA_EXCEEDED":
|
|
5446
5684
|
case "STORAGE_LIMIT_REACHED":
|
|
5447
5685
|
return input.message;
|
|
@@ -5455,13 +5693,33 @@ function vaultError(input) {
|
|
|
5455
5693
|
};
|
|
5456
5694
|
return { ok: false, error };
|
|
5457
5695
|
}
|
|
5696
|
+
function typedKvCause(error) {
|
|
5697
|
+
return Object.assign(new Error(error.message), {
|
|
5698
|
+
name: "ServiceError",
|
|
5699
|
+
code: error.code,
|
|
5700
|
+
service: error.service,
|
|
5701
|
+
...error.meta !== void 0 ? { meta: error.meta } : {},
|
|
5702
|
+
...error.cause !== void 0 ? { cause: error.cause } : {}
|
|
5703
|
+
});
|
|
5704
|
+
}
|
|
5458
5705
|
function kvWriteError(context, error) {
|
|
5459
5706
|
if (error.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error.code === ErrorCodes.STORAGE_LIMIT_REACHED) {
|
|
5460
5707
|
return vaultError({ code: error.code, message: error.message, meta: error.meta });
|
|
5461
5708
|
}
|
|
5709
|
+
const message = `${context}: ${error.message}`;
|
|
5710
|
+
if (error.code === ErrorCodes.AUTH_UNAUTHORIZED) {
|
|
5711
|
+
const code = authorizationVerdictOf(error) === "unauthenticated" ? "AUTH_UNAUTHORIZED" : "PERMISSION_DENIED";
|
|
5712
|
+
return vaultError({
|
|
5713
|
+
code,
|
|
5714
|
+
message,
|
|
5715
|
+
...error.meta !== void 0 ? { meta: error.meta } : {},
|
|
5716
|
+
cause: typedKvCause(error)
|
|
5717
|
+
});
|
|
5718
|
+
}
|
|
5462
5719
|
return vaultError({
|
|
5463
5720
|
code: "STORAGE_ERROR",
|
|
5464
|
-
cause: new Error(
|
|
5721
|
+
cause: new Error(message, { cause: typedKvCause(error) }),
|
|
5722
|
+
...error.meta !== void 0 ? { meta: error.meta } : {}
|
|
5465
5723
|
});
|
|
5466
5724
|
}
|
|
5467
5725
|
var DataVaultService = class extends BaseService {
|
|
@@ -5741,11 +5999,18 @@ var DataVaultService = class extends BaseService {
|
|
|
5741
5999
|
cause: new Error(envelopeResult.error.message)
|
|
5742
6000
|
});
|
|
5743
6001
|
}
|
|
5744
|
-
const
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
);
|
|
6002
|
+
const valuePath = `vault/${key}`;
|
|
6003
|
+
const valueBody = JSON.stringify(envelopeResult.data);
|
|
6004
|
+
const valuePutResult = options?.ifNoneMatch !== void 0 ? await this.tc.kv.put(valuePath, valueBody, { ifNoneMatch: options.ifNoneMatch }) : await this.tc.kv.put(valuePath, valueBody);
|
|
5748
6005
|
if (!valuePutResult.ok) {
|
|
6006
|
+
if (options?.ifNoneMatch !== void 0 && valuePutResult.error.code === ErrorCodes.KV_PRECONDITION_FAILED) {
|
|
6007
|
+
return vaultError({
|
|
6008
|
+
code: "ALREADY_EXISTS",
|
|
6009
|
+
key,
|
|
6010
|
+
...valuePutResult.error.meta !== void 0 ? { meta: valuePutResult.error.meta } : {},
|
|
6011
|
+
cause: typedKvCause(valuePutResult.error)
|
|
6012
|
+
});
|
|
6013
|
+
}
|
|
5749
6014
|
return kvWriteError("Failed to store encrypted value", valuePutResult.error);
|
|
5750
6015
|
}
|
|
5751
6016
|
return { ok: true, data: void 0 };
|
|
@@ -5772,7 +6037,8 @@ var DataVaultService = class extends BaseService {
|
|
|
5772
6037
|
}
|
|
5773
6038
|
try {
|
|
5774
6039
|
const valueResult = await this.tc.kv.get(`vault/${key}`, {
|
|
5775
|
-
raw: true
|
|
6040
|
+
raw: true,
|
|
6041
|
+
...options?.source === void 0 ? {} : { source: options.source }
|
|
5776
6042
|
});
|
|
5777
6043
|
if (!valueResult.ok) {
|
|
5778
6044
|
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
@@ -5817,7 +6083,7 @@ var DataVaultService = class extends BaseService {
|
|
|
5817
6083
|
}
|
|
5818
6084
|
let valueResult;
|
|
5819
6085
|
try {
|
|
5820
|
-
valueResult = await this.tc.kv.get(`vault/${key}`, { raw: true });
|
|
6086
|
+
valueResult = await this.tc.kv.get(`vault/${key}`, { raw: true, source: "network" });
|
|
5821
6087
|
} catch {
|
|
5822
6088
|
return { status: "node_unreachable" };
|
|
5823
6089
|
}
|
|
@@ -5886,7 +6152,8 @@ var DataVaultService = class extends BaseService {
|
|
|
5886
6152
|
}
|
|
5887
6153
|
try {
|
|
5888
6154
|
const valueResult = await this.tc.kv.get(`vault/${key}`, {
|
|
5889
|
-
raw: true
|
|
6155
|
+
raw: true,
|
|
6156
|
+
source: "network"
|
|
5890
6157
|
});
|
|
5891
6158
|
if (!valueResult.ok) {
|
|
5892
6159
|
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
@@ -5913,6 +6180,14 @@ var DataVaultService = class extends BaseService {
|
|
|
5913
6180
|
if (this.usesNetworkEncryption) {
|
|
5914
6181
|
return this.putNetworkEncrypted(key, value, options);
|
|
5915
6182
|
}
|
|
6183
|
+
if (options?.ifNoneMatch !== void 0) {
|
|
6184
|
+
return vaultError({
|
|
6185
|
+
code: "STORAGE_ERROR",
|
|
6186
|
+
cause: new Error(
|
|
6187
|
+
"ifNoneMatch is only supported by network-encrypted vaults"
|
|
6188
|
+
)
|
|
6189
|
+
});
|
|
6190
|
+
}
|
|
5916
6191
|
if (!this._isUnlocked || !this.masterKey) {
|
|
5917
6192
|
return vaultError({
|
|
5918
6193
|
code: "VAULT_LOCKED",
|
|
@@ -6012,7 +6287,8 @@ var DataVaultService = class extends BaseService {
|
|
|
6012
6287
|
}
|
|
6013
6288
|
try {
|
|
6014
6289
|
const keyResult = await this.tc.kv.get(`keys/${key}`, {
|
|
6015
|
-
raw: true
|
|
6290
|
+
raw: true,
|
|
6291
|
+
...options?.source === void 0 ? {} : { source: options.source }
|
|
6016
6292
|
});
|
|
6017
6293
|
if (!keyResult.ok) {
|
|
6018
6294
|
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
@@ -6021,7 +6297,8 @@ var DataVaultService = class extends BaseService {
|
|
|
6021
6297
|
const keyBlobBytes = base64Decode(keyEnvelope.key);
|
|
6022
6298
|
const entryKey = this.crypto.decrypt(this.masterKey, keyBlobBytes);
|
|
6023
6299
|
const valueResult = await this.tc.kv.get(`vault/${key}`, {
|
|
6024
|
-
raw: true
|
|
6300
|
+
raw: true,
|
|
6301
|
+
...options?.source === void 0 ? {} : { source: options.source }
|
|
6025
6302
|
});
|
|
6026
6303
|
if (!valueResult.ok) {
|
|
6027
6304
|
return vaultError({ code: "KEY_NOT_FOUND", key });
|
|
@@ -6131,6 +6408,7 @@ var DataVaultService = class extends BaseService {
|
|
|
6131
6408
|
prefix: listPrefix,
|
|
6132
6409
|
removePrefix: true,
|
|
6133
6410
|
...options?.limit === void 0 ? {} : { limit: options.limit },
|
|
6411
|
+
...options?.source === void 0 ? {} : { source: options.source },
|
|
6134
6412
|
...options?.cursor === void 0 ? {} : { cursor: options.cursor }
|
|
6135
6413
|
});
|
|
6136
6414
|
if (!listResult.ok) {
|
|
@@ -6724,6 +7002,35 @@ var SECRET_NAME_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
|
6724
7002
|
var SECRET_PREFIX = "secrets/";
|
|
6725
7003
|
var SCOPED_SECRET_PREFIX = "secrets/scoped/";
|
|
6726
7004
|
var RESERVED_SECRET_SCOPES = /* @__PURE__ */ new Set(["default", "global"]);
|
|
7005
|
+
var SECRET_SCOPE_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
7006
|
+
var MAX_SECRET_SCOPE_LENGTH = 64;
|
|
7007
|
+
var TC832_RESERVED_SCOPES = Object.freeze([
|
|
7008
|
+
"default",
|
|
7009
|
+
"global",
|
|
7010
|
+
"scoped"
|
|
7011
|
+
]);
|
|
7012
|
+
function appSecretScope(appId) {
|
|
7013
|
+
if (appId.trim() === "") {
|
|
7014
|
+
throw new Error("App id must be non-empty to derive a secret scope.");
|
|
7015
|
+
}
|
|
7016
|
+
const scope = appId.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
7017
|
+
if (scope === "") {
|
|
7018
|
+
throw new Error(
|
|
7019
|
+
`App id ${JSON.stringify(appId)} must contain at least one letter or number.`
|
|
7020
|
+
);
|
|
7021
|
+
}
|
|
7022
|
+
if (scope.length > MAX_SECRET_SCOPE_LENGTH) {
|
|
7023
|
+
throw new Error(
|
|
7024
|
+
`App id ${JSON.stringify(appId)} yields a secret scope longer than ${MAX_SECRET_SCOPE_LENGTH} characters.`
|
|
7025
|
+
);
|
|
7026
|
+
}
|
|
7027
|
+
if (TC832_RESERVED_SCOPES.includes(scope)) {
|
|
7028
|
+
throw new Error(
|
|
7029
|
+
`App id ${JSON.stringify(appId)} yields the reserved secret scope ${JSON.stringify(scope)}.`
|
|
7030
|
+
);
|
|
7031
|
+
}
|
|
7032
|
+
return scope;
|
|
7033
|
+
}
|
|
6727
7034
|
function canonicalizeSecretScope(scope) {
|
|
6728
7035
|
if (scope === void 0) {
|
|
6729
7036
|
return void 0;
|
|
@@ -6826,7 +7133,9 @@ var SecretsService = class {
|
|
|
6826
7133
|
async get(name, options) {
|
|
6827
7134
|
const secretPath = resolveSecretPathResult(name, options);
|
|
6828
7135
|
if ("ok" in secretPath) return secretPath;
|
|
6829
|
-
const result = await this.vault.get(secretPath.vaultKey)
|
|
7136
|
+
const result = options?.source === void 0 ? await this.vault.get(secretPath.vaultKey) : await this.vault.get(secretPath.vaultKey, {
|
|
7137
|
+
source: options.source
|
|
7138
|
+
});
|
|
6830
7139
|
if (!result.ok) {
|
|
6831
7140
|
return result;
|
|
6832
7141
|
}
|
|
@@ -6836,11 +7145,12 @@ var SecretsService = class {
|
|
|
6836
7145
|
const secretPath = resolveSecretPathResult(name, options);
|
|
6837
7146
|
if ("ok" in secretPath) return secretPath;
|
|
6838
7147
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6839
|
-
|
|
7148
|
+
const payload = {
|
|
6840
7149
|
value,
|
|
6841
7150
|
createdAt: now,
|
|
6842
7151
|
updatedAt: now
|
|
6843
|
-
}
|
|
7152
|
+
};
|
|
7153
|
+
return options?.ifAbsent === true ? this.vault.put(secretPath.vaultKey, payload, { ifNoneMatch: "*" }) : this.vault.put(secretPath.vaultKey, payload);
|
|
6844
7154
|
}
|
|
6845
7155
|
async delete(name, options) {
|
|
6846
7156
|
const secretPath = resolveSecretPathResult(name, options);
|
|
@@ -6859,7 +7169,8 @@ var SecretsService = class {
|
|
|
6859
7169
|
}
|
|
6860
7170
|
const result = await this.vault.list({
|
|
6861
7171
|
prefix,
|
|
6862
|
-
removePrefix: true
|
|
7172
|
+
removePrefix: true,
|
|
7173
|
+
...options?.source === void 0 ? {} : { source: options.source }
|
|
6863
7174
|
});
|
|
6864
7175
|
if (!result.ok) {
|
|
6865
7176
|
return result;
|
|
@@ -6878,6 +7189,7 @@ var SecretsService = class {
|
|
|
6878
7189
|
prefix: "secrets/",
|
|
6879
7190
|
removePrefix: true,
|
|
6880
7191
|
limit: 1e3,
|
|
7192
|
+
source: "network",
|
|
6881
7193
|
...cursor === void 0 ? {} : { cursor }
|
|
6882
7194
|
});
|
|
6883
7195
|
if (!result.ok) {
|
|
@@ -7870,6 +8182,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
|
|
|
7870
8182
|
// Annotate the CommonJS export names for ESM import in node:
|
|
7871
8183
|
0 && (module.exports = {
|
|
7872
8184
|
BaseService,
|
|
8185
|
+
CLEAR_PENDING_WARNING,
|
|
7873
8186
|
DECRYPT_ACTION,
|
|
7874
8187
|
DECRYPT_FACT_TYPE,
|
|
7875
8188
|
DECRYPT_RESULT_TYPE,
|
|
@@ -7896,11 +8209,13 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
|
|
|
7896
8209
|
KVListResultSchema,
|
|
7897
8210
|
KVResponseHeadersSchema,
|
|
7898
8211
|
KVService,
|
|
8212
|
+
MAX_SECRET_SCOPE_LENGTH,
|
|
7899
8213
|
NETWORK_NAME_PATTERN,
|
|
7900
8214
|
NetworkIdError,
|
|
7901
8215
|
PrefixedKVService,
|
|
7902
8216
|
RetryPolicySchema,
|
|
7903
8217
|
SECRET_NAME_RE,
|
|
8218
|
+
SECRET_SCOPE_RE,
|
|
7904
8219
|
SQLAction,
|
|
7905
8220
|
SQLMigrations,
|
|
7906
8221
|
SQLService,
|
|
@@ -7914,6 +8229,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
|
|
|
7914
8229
|
ServiceResponseEventSchema,
|
|
7915
8230
|
ServiceRetryEventSchema,
|
|
7916
8231
|
ServiceSessionSchema,
|
|
8232
|
+
TC832_RESERVED_SCOPES,
|
|
7917
8233
|
TelemetryEvents,
|
|
7918
8234
|
TelemetrySpanEventSchema,
|
|
7919
8235
|
TinyCloudDebugLogger,
|
|
@@ -7921,6 +8237,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
|
|
|
7921
8237
|
VaultHeaders,
|
|
7922
8238
|
VaultPublicSpaceKVActions,
|
|
7923
8239
|
abortedError,
|
|
8240
|
+
appSecretScope,
|
|
7924
8241
|
authExpiredError,
|
|
7925
8242
|
authRequiredError,
|
|
7926
8243
|
authUnauthorizedError,
|
|
@@ -7933,6 +8250,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
|
|
|
7933
8250
|
buildDecryptInvocation,
|
|
7934
8251
|
buildNetworkId,
|
|
7935
8252
|
canonicalHashHex,
|
|
8253
|
+
canonicalReplicationIdentity,
|
|
7936
8254
|
canonicalSignedResponse,
|
|
7937
8255
|
canonicalizeEncryptionJson,
|
|
7938
8256
|
canonicalizeSecretScope,
|
|
@@ -7959,6 +8277,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
|
|
|
7959
8277
|
installTinyCloudDebugGlobals,
|
|
7960
8278
|
isNetworkId,
|
|
7961
8279
|
isStorageFullError,
|
|
8280
|
+
kvPrefixCovers,
|
|
7962
8281
|
networkDiscoveryKey,
|
|
7963
8282
|
networkError,
|
|
7964
8283
|
notFoundError,
|
|
@@ -7972,6 +8291,8 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
|
|
|
7972
8291
|
parseSecretCatalogKey,
|
|
7973
8292
|
parseStorageQuotaBytes,
|
|
7974
8293
|
permissionDeniedError,
|
|
8294
|
+
replicationIdentityKey,
|
|
8295
|
+
requiresSecretsOptIn,
|
|
7975
8296
|
resolveSecretListPrefix,
|
|
7976
8297
|
resolveSecretPath,
|
|
7977
8298
|
serviceError,
|