@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/kv/index.js
CHANGED
|
@@ -60,7 +60,12 @@ var TelemetryEvents = {
|
|
|
60
60
|
SERVICE_ERROR: "service.error",
|
|
61
61
|
SERVICE_RETRY: "service.retry",
|
|
62
62
|
SESSION_CHANGED: "session.changed",
|
|
63
|
-
SESSION_EXPIRED: "session.expired"
|
|
63
|
+
SESSION_EXPIRED: "session.expired",
|
|
64
|
+
REPLICATION_READ: "replication.read",
|
|
65
|
+
REPLICATION_WRITE: "replication.write",
|
|
66
|
+
REPLICATION_SYNC: "replication.sync",
|
|
67
|
+
REPLICATION_STATE: "replication.state",
|
|
68
|
+
REPLICATION_DIVERGENCE: "replication.divergence"
|
|
64
69
|
};
|
|
65
70
|
function ok(data) {
|
|
66
71
|
return { ok: true, data };
|
|
@@ -588,6 +593,7 @@ var BaseService = class {
|
|
|
588
593
|
const parents = [
|
|
589
594
|
this.abortController.signal,
|
|
590
595
|
this.context?.abortSignal,
|
|
596
|
+
this.context?.operationAbortSignal,
|
|
591
597
|
signal
|
|
592
598
|
].filter((parent) => parent !== void 0);
|
|
593
599
|
for (const parent of parents) {
|
|
@@ -833,14 +839,28 @@ var KVService = class extends BaseService {
|
|
|
833
839
|
*/
|
|
834
840
|
constructor(config = {}) {
|
|
835
841
|
super();
|
|
842
|
+
this.readThrough = null;
|
|
843
|
+
this.readThroughReady = null;
|
|
836
844
|
this._config = config;
|
|
837
845
|
}
|
|
846
|
+
/** Delay KV routing until an asynchronously loaded read-through is attached. */
|
|
847
|
+
setReadThroughReady(ready) {
|
|
848
|
+
const settled = ready.then(() => void 0, () => void 0);
|
|
849
|
+
this.readThroughReady = settled;
|
|
850
|
+
void settled.then(() => {
|
|
851
|
+
if (this.readThroughReady === settled) this.readThroughReady = null;
|
|
852
|
+
});
|
|
853
|
+
}
|
|
838
854
|
/**
|
|
839
855
|
* Get the service configuration.
|
|
840
856
|
*/
|
|
841
857
|
get config() {
|
|
842
858
|
return this._config;
|
|
843
859
|
}
|
|
860
|
+
/** Install or remove the optional replication read-through policy. */
|
|
861
|
+
setReadThrough(readThrough) {
|
|
862
|
+
this.readThrough = readThrough;
|
|
863
|
+
}
|
|
844
864
|
handleQuotaErrorResponse(response, errorText, key) {
|
|
845
865
|
if (response.status !== 402 && response.status !== 413) {
|
|
846
866
|
return void 0;
|
|
@@ -1094,6 +1114,7 @@ var KVService = class extends BaseService {
|
|
|
1094
1114
|
return aligned;
|
|
1095
1115
|
}
|
|
1096
1116
|
async batchRead(keys, action, options) {
|
|
1117
|
+
if (this.readThroughReady !== null) await this.readThroughReady;
|
|
1097
1118
|
if (!this.requireAuth()) return err(authRequiredError("kv"));
|
|
1098
1119
|
if (keys.length === 0) return ok({ results: [], count: 0 });
|
|
1099
1120
|
if (keys.length > MAX_KV_BATCH_READ_ITEMS) {
|
|
@@ -1344,6 +1365,7 @@ var KVService = class extends BaseService {
|
|
|
1344
1365
|
*/
|
|
1345
1366
|
async get(key, options) {
|
|
1346
1367
|
return this.withTelemetry("get", key, async () => {
|
|
1368
|
+
if (this.readThroughReady !== null) await this.readThroughReady;
|
|
1347
1369
|
if (!this.requireAuth()) {
|
|
1348
1370
|
return err(authRequiredError("kv"));
|
|
1349
1371
|
}
|
|
@@ -1357,64 +1379,38 @@ var KVService = class extends BaseService {
|
|
|
1357
1379
|
const path = this.getFullPath(key, options?.prefix);
|
|
1358
1380
|
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
1359
1381
|
try {
|
|
1360
|
-
const
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
response,
|
|
1383
|
-
errorText2,
|
|
1384
|
-
[path],
|
|
1385
|
-
KVAction.GET,
|
|
1386
|
-
structuredCapability ? { permissionHint, ...structuredCapability } : {}
|
|
1387
|
-
);
|
|
1388
|
-
}
|
|
1389
|
-
if (response.status === 404) {
|
|
1390
|
-
return this.classifyNotFound(response, key);
|
|
1391
|
-
}
|
|
1392
|
-
const errorText = await response.text();
|
|
1393
|
-
if (response.status === 413) {
|
|
1394
|
-
return err(serviceError(
|
|
1395
|
-
ErrorCodes.KV_RESPONSE_TOO_LARGE,
|
|
1396
|
-
`KV value at key ${JSON.stringify(key)} exceeds the requested response limit`,
|
|
1397
|
-
"kv",
|
|
1398
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1399
|
-
));
|
|
1382
|
+
const network = () => this.getFromNetwork(key, path, options, request.signal);
|
|
1383
|
+
if (options?.source === "network") {
|
|
1384
|
+
const observer = this.readThrough;
|
|
1385
|
+
if (observer === null) return await network();
|
|
1386
|
+
let outcome = "error";
|
|
1387
|
+
const startedAt = Date.now();
|
|
1388
|
+
try {
|
|
1389
|
+
const result = await network();
|
|
1390
|
+
outcome = result.ok ? "found" : result.error.code === ErrorCodes.KV_NOT_FOUND ? "not_found" : "error";
|
|
1391
|
+
return result;
|
|
1392
|
+
} finally {
|
|
1393
|
+
try {
|
|
1394
|
+
observer.observeNetworkRequested({
|
|
1395
|
+
op: "get",
|
|
1396
|
+
space: this.context.session.spaceId,
|
|
1397
|
+
path,
|
|
1398
|
+
reason: "NETWORK_REQUESTED",
|
|
1399
|
+
outcome,
|
|
1400
|
+
latencyMs: Date.now() - startedAt
|
|
1401
|
+
});
|
|
1402
|
+
} catch {
|
|
1403
|
+
}
|
|
1400
1404
|
}
|
|
1401
|
-
return err(
|
|
1402
|
-
serviceError(
|
|
1403
|
-
ErrorCodes.NETWORK_ERROR,
|
|
1404
|
-
`Failed to get key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
|
|
1405
|
-
"kv",
|
|
1406
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1407
|
-
)
|
|
1408
|
-
);
|
|
1409
1405
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1406
|
+
if (this.readThrough === null) return await network();
|
|
1407
|
+
return await this.readThrough.get({
|
|
1408
|
+
space: this.context.session.spaceId,
|
|
1409
|
+
key,
|
|
1410
|
+
path,
|
|
1411
|
+
options,
|
|
1412
|
+
signal: request.signal,
|
|
1413
|
+
network
|
|
1418
1414
|
});
|
|
1419
1415
|
} catch (error) {
|
|
1420
1416
|
return err(wrapError("kv", error));
|
|
@@ -1423,6 +1419,56 @@ var KVService = class extends BaseService {
|
|
|
1423
1419
|
}
|
|
1424
1420
|
});
|
|
1425
1421
|
}
|
|
1422
|
+
async getFromNetwork(key, path, options, signal) {
|
|
1423
|
+
const response = await this.invokeOperation(
|
|
1424
|
+
path,
|
|
1425
|
+
KVAction.GET,
|
|
1426
|
+
void 0,
|
|
1427
|
+
signal,
|
|
1428
|
+
options?.maxResponseBytes === void 0 ? void 0 : { "x-tinycloud-max-response-bytes": String(options.maxResponseBytes) }
|
|
1429
|
+
);
|
|
1430
|
+
if (!response.ok) {
|
|
1431
|
+
if (response.status === 401 || response.status === 403) {
|
|
1432
|
+
const errorText2 = await this.readAuthorizationText(response);
|
|
1433
|
+
const permissionHint = parsePermissionHintFromErrorText(errorText2);
|
|
1434
|
+
const structuredCapability = permissionHint?.service === "tinycloud.kv" && permissionHint.space === this.context.session.spaceId && permissionHint.path === path ? validatedCapabilityOf({
|
|
1435
|
+
service: "kv",
|
|
1436
|
+
code: ErrorCodes.AUTH_UNAUTHORIZED,
|
|
1437
|
+
meta: {
|
|
1438
|
+
status: response.status,
|
|
1439
|
+
resource: `${this.context.session.spaceId}/kv/${path}`,
|
|
1440
|
+
requiredAction: KVAction.GET
|
|
1441
|
+
}
|
|
1442
|
+
}) : void 0;
|
|
1443
|
+
return this.authorizationFailure(
|
|
1444
|
+
`Failed to get key ${JSON.stringify(key)}`,
|
|
1445
|
+
response,
|
|
1446
|
+
errorText2,
|
|
1447
|
+
[path],
|
|
1448
|
+
KVAction.GET,
|
|
1449
|
+
structuredCapability ? { permissionHint, ...structuredCapability } : {}
|
|
1450
|
+
);
|
|
1451
|
+
}
|
|
1452
|
+
if (response.status === 404) return this.classifyNotFound(response, key);
|
|
1453
|
+
const errorText = await response.text();
|
|
1454
|
+
if (response.status === 413) {
|
|
1455
|
+
return err(serviceError(
|
|
1456
|
+
ErrorCodes.KV_RESPONSE_TOO_LARGE,
|
|
1457
|
+
`KV value at key ${JSON.stringify(key)} exceeds the requested response limit`,
|
|
1458
|
+
"kv",
|
|
1459
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1460
|
+
));
|
|
1461
|
+
}
|
|
1462
|
+
return err(serviceError(
|
|
1463
|
+
ErrorCodes.NETWORK_ERROR,
|
|
1464
|
+
`Failed to get key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
|
|
1465
|
+
"kv",
|
|
1466
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1467
|
+
));
|
|
1468
|
+
}
|
|
1469
|
+
const data = await this.parseResponse(response, options?.raw, options?.binary);
|
|
1470
|
+
return ok({ data, headers: this.createResponseHeaders(response.headers) });
|
|
1471
|
+
}
|
|
1426
1472
|
async batchGet(keys, options) {
|
|
1427
1473
|
return this.withTelemetry(
|
|
1428
1474
|
"batchGet",
|
|
@@ -1435,6 +1481,7 @@ var KVService = class extends BaseService {
|
|
|
1435
1481
|
*/
|
|
1436
1482
|
async put(key, value, options) {
|
|
1437
1483
|
return this.withTelemetry("put", key, async () => {
|
|
1484
|
+
if (this.readThroughReady !== null) await this.readThroughReady;
|
|
1438
1485
|
if (!this.requireAuth()) {
|
|
1439
1486
|
return err(authRequiredError("kv"));
|
|
1440
1487
|
}
|
|
@@ -1449,63 +1496,15 @@ var KVService = class extends BaseService {
|
|
|
1449
1496
|
const body = this.serializePutValue(value, options?.contentType);
|
|
1450
1497
|
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
1451
1498
|
try {
|
|
1452
|
-
const
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
{
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
);
|
|
1462
|
-
if (response.status === 401 || response.status === 403) {
|
|
1463
|
-
return this.authorizationFailure(
|
|
1464
|
-
`Failed to put key ${JSON.stringify(key)}`,
|
|
1465
|
-
response,
|
|
1466
|
-
await this.readAuthorizationText(response),
|
|
1467
|
-
[path],
|
|
1468
|
-
KVAction.PUT
|
|
1469
|
-
);
|
|
1470
|
-
}
|
|
1471
|
-
if (!response.ok) {
|
|
1472
|
-
const errorText = await response.text();
|
|
1473
|
-
if (response.status === 412) {
|
|
1474
|
-
return err(serviceError(
|
|
1475
|
-
ErrorCodes.KV_PRECONDITION_FAILED,
|
|
1476
|
-
`KV precondition failed for key ${JSON.stringify(key)}`,
|
|
1477
|
-
"kv",
|
|
1478
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1479
|
-
));
|
|
1480
|
-
}
|
|
1481
|
-
if (response.status === 503 && (options?.ifMatch !== void 0 || options?.ifNoneMatch !== void 0)) {
|
|
1482
|
-
return err(serviceError(
|
|
1483
|
-
ErrorCodes.KV_CONFLICT,
|
|
1484
|
-
`Concurrent KV update conflicted for key ${JSON.stringify(key)}`,
|
|
1485
|
-
"kv",
|
|
1486
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1487
|
-
));
|
|
1488
|
-
}
|
|
1489
|
-
const quotaError = this.handleQuotaErrorResponse(
|
|
1490
|
-
response,
|
|
1491
|
-
errorText,
|
|
1492
|
-
key
|
|
1493
|
-
);
|
|
1494
|
-
if (quotaError) {
|
|
1495
|
-
return quotaError;
|
|
1496
|
-
}
|
|
1497
|
-
return err(
|
|
1498
|
-
serviceError(
|
|
1499
|
-
ErrorCodes.KV_WRITE_FAILED,
|
|
1500
|
-
`Failed to put key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
|
|
1501
|
-
"kv",
|
|
1502
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1503
|
-
)
|
|
1504
|
-
);
|
|
1505
|
-
}
|
|
1506
|
-
return ok({
|
|
1507
|
-
data: void 0,
|
|
1508
|
-
headers: this.createResponseHeaders(response.headers)
|
|
1499
|
+
const network = () => this.putToNetwork(key, path, body, options, request.signal);
|
|
1500
|
+
if (this.readThrough === null) return await network();
|
|
1501
|
+
return await this.readThrough.write({
|
|
1502
|
+
op: "put",
|
|
1503
|
+
space: this.context.session.spaceId,
|
|
1504
|
+
entries: [{ path, body }],
|
|
1505
|
+
signal: request.signal,
|
|
1506
|
+
conditional: options?.ifMatch !== void 0 || options?.ifNoneMatch !== void 0,
|
|
1507
|
+
network
|
|
1509
1508
|
});
|
|
1510
1509
|
} catch (error) {
|
|
1511
1510
|
return err(wrapError("kv", error));
|
|
@@ -1514,11 +1513,64 @@ var KVService = class extends BaseService {
|
|
|
1514
1513
|
}
|
|
1515
1514
|
});
|
|
1516
1515
|
}
|
|
1516
|
+
async putToNetwork(key, path, body, options, signal) {
|
|
1517
|
+
const response = await this.invokeOperation(
|
|
1518
|
+
path,
|
|
1519
|
+
KVAction.PUT,
|
|
1520
|
+
body,
|
|
1521
|
+
signal,
|
|
1522
|
+
{
|
|
1523
|
+
...options?.ifMatch === void 0 ? {} : { "if-match": options.ifMatch },
|
|
1524
|
+
...options?.ifNoneMatch === void 0 ? {} : { "if-none-match": options.ifNoneMatch }
|
|
1525
|
+
}
|
|
1526
|
+
);
|
|
1527
|
+
if (response.status === 401 || response.status === 403) {
|
|
1528
|
+
return this.authorizationFailure(
|
|
1529
|
+
`Failed to put key ${JSON.stringify(key)}`,
|
|
1530
|
+
response,
|
|
1531
|
+
await this.readAuthorizationText(response),
|
|
1532
|
+
[path],
|
|
1533
|
+
KVAction.PUT
|
|
1534
|
+
);
|
|
1535
|
+
}
|
|
1536
|
+
if (!response.ok) {
|
|
1537
|
+
const errorText = await response.text();
|
|
1538
|
+
if (response.status === 412) {
|
|
1539
|
+
return err(serviceError(
|
|
1540
|
+
ErrorCodes.KV_PRECONDITION_FAILED,
|
|
1541
|
+
`KV precondition failed for key ${JSON.stringify(key)}`,
|
|
1542
|
+
"kv",
|
|
1543
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1544
|
+
));
|
|
1545
|
+
}
|
|
1546
|
+
if (response.status === 503 && (options?.ifMatch !== void 0 || options?.ifNoneMatch !== void 0)) {
|
|
1547
|
+
return err(serviceError(
|
|
1548
|
+
ErrorCodes.KV_CONFLICT,
|
|
1549
|
+
`Concurrent KV update conflicted for key ${JSON.stringify(key)}`,
|
|
1550
|
+
"kv",
|
|
1551
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1552
|
+
));
|
|
1553
|
+
}
|
|
1554
|
+
const quotaError = this.handleQuotaErrorResponse(response, errorText, key);
|
|
1555
|
+
if (quotaError) return quotaError;
|
|
1556
|
+
return err(serviceError(
|
|
1557
|
+
ErrorCodes.KV_WRITE_FAILED,
|
|
1558
|
+
`Failed to put key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
|
|
1559
|
+
"kv",
|
|
1560
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1561
|
+
));
|
|
1562
|
+
}
|
|
1563
|
+
return ok({
|
|
1564
|
+
data: void 0,
|
|
1565
|
+
headers: this.createResponseHeaders(response.headers)
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1517
1568
|
/**
|
|
1518
1569
|
* Store multiple values in one TinyCloud KV invocation.
|
|
1519
1570
|
*/
|
|
1520
1571
|
async batchPut(items, options) {
|
|
1521
1572
|
return this.withTelemetry("batchPut", String(items.length), async () => {
|
|
1573
|
+
if (this.readThroughReady !== null) await this.readThroughReady;
|
|
1522
1574
|
if (!this.requireAuth()) {
|
|
1523
1575
|
return err(authRequiredError("kv"));
|
|
1524
1576
|
}
|
|
@@ -1549,155 +1601,177 @@ var KVService = class extends BaseService {
|
|
|
1549
1601
|
}
|
|
1550
1602
|
seen.add(path);
|
|
1551
1603
|
}
|
|
1552
|
-
let requestMayHaveDispatched = false;
|
|
1553
1604
|
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
1554
1605
|
try {
|
|
1555
|
-
const
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
session,
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1606
|
+
const network = () => this.batchPutToNetwork(items, paths, request.signal);
|
|
1607
|
+
if (this.readThrough === null) return await network();
|
|
1608
|
+
const entries = items.map((item, index) => ({
|
|
1609
|
+
path: paths[index],
|
|
1610
|
+
body: this.serializeBatchPutValue(item)
|
|
1611
|
+
}));
|
|
1612
|
+
return await this.readThrough.write({
|
|
1613
|
+
op: "batchPut",
|
|
1614
|
+
space: session.spaceId,
|
|
1615
|
+
entries,
|
|
1616
|
+
signal: request.signal,
|
|
1617
|
+
network: () => this.batchPutToNetwork(items, paths, request.signal, entries)
|
|
1618
|
+
});
|
|
1619
|
+
} catch (error) {
|
|
1620
|
+
const wrapped = wrapError("kv", error);
|
|
1621
|
+
return err({ ...wrapped, meta: { ...wrapped.meta, requestMayHaveDispatched: false } });
|
|
1622
|
+
} finally {
|
|
1623
|
+
request.dispose();
|
|
1624
|
+
}
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
async batchPutToNetwork(items, paths, signal, entries) {
|
|
1628
|
+
const session = this.context.session;
|
|
1629
|
+
let requestMayHaveDispatched = false;
|
|
1630
|
+
try {
|
|
1631
|
+
const body = new FormData();
|
|
1632
|
+
for (let index = 0; index < items.length; index++) {
|
|
1633
|
+
body.append(
|
|
1634
|
+
encodeKvBatchPartName(paths[index]),
|
|
1635
|
+
entries?.[index]?.body ?? this.serializeBatchPutValue(items[index])
|
|
1570
1636
|
);
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
return this.authorizationFailure(
|
|
1598
|
-
`Failed to batch put ${items.length} key(s)`,
|
|
1599
|
-
response,
|
|
1600
|
-
errorText,
|
|
1601
|
-
paths,
|
|
1602
|
-
KVAction.PUT
|
|
1603
|
-
);
|
|
1604
|
-
}
|
|
1605
|
-
const quotaError = this.handleQuotaErrorResponse(
|
|
1606
|
-
response,
|
|
1607
|
-
errorText,
|
|
1608
|
-
"batch"
|
|
1609
|
-
);
|
|
1610
|
-
if (quotaError) {
|
|
1611
|
-
return quotaError;
|
|
1612
|
-
}
|
|
1637
|
+
}
|
|
1638
|
+
const headers = this.context.invokeAny(
|
|
1639
|
+
session,
|
|
1640
|
+
paths.map((path) => ({
|
|
1641
|
+
spaceId: session.spaceId,
|
|
1642
|
+
service: "kv",
|
|
1643
|
+
path,
|
|
1644
|
+
action: KVAction.PUT
|
|
1645
|
+
}))
|
|
1646
|
+
);
|
|
1647
|
+
const url = `${this.host}/invoke`;
|
|
1648
|
+
const init = {
|
|
1649
|
+
method: "POST",
|
|
1650
|
+
headers,
|
|
1651
|
+
body,
|
|
1652
|
+
signal
|
|
1653
|
+
};
|
|
1654
|
+
const fetchFn = this.context.fetch;
|
|
1655
|
+
requestMayHaveDispatched = true;
|
|
1656
|
+
const response = await fetchFn(url, init);
|
|
1657
|
+
if (!response.ok) {
|
|
1658
|
+
let errorText;
|
|
1659
|
+
try {
|
|
1660
|
+
errorText = await response.text();
|
|
1661
|
+
} catch (textError) {
|
|
1662
|
+
const cause = textError instanceof Error ? textError : new Error(String(textError));
|
|
1613
1663
|
return err(
|
|
1614
1664
|
serviceError(
|
|
1615
1665
|
ErrorCodes.KV_WRITE_FAILED,
|
|
1616
|
-
`Failed to batch put ${items.length} key(s): ${response.status} - ${
|
|
1666
|
+
`Failed to batch put ${items.length} key(s): ${response.status} - <response body could not be read: ${cause.message}>`,
|
|
1617
1667
|
"kv",
|
|
1618
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1668
|
+
{ cause, meta: { status: response.status, statusText: response.statusText } }
|
|
1619
1669
|
)
|
|
1620
1670
|
);
|
|
1621
1671
|
}
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
serviceError(
|
|
1630
|
-
timedOut ? ErrorCodes.TIMEOUT : ErrorCodes.NETWORK_ERROR,
|
|
1631
|
-
timedOut ? `KV batchPut response body did not arrive in time: ${cause.message}` : `KV batchPut response was not valid JSON: ${cause.message}`,
|
|
1632
|
-
"kv",
|
|
1633
|
-
{
|
|
1634
|
-
cause,
|
|
1635
|
-
meta: {
|
|
1636
|
-
...timedOut ? { timeoutMs: cause.timeoutMs } : {},
|
|
1637
|
-
requestMayHaveDispatched: true,
|
|
1638
|
-
responseReceived: true,
|
|
1639
|
-
status: response.status,
|
|
1640
|
-
outcome: "batch-unconfirmed"
|
|
1641
|
-
}
|
|
1642
|
-
}
|
|
1643
|
-
)
|
|
1672
|
+
if (response.status === 401 || response.status === 403) {
|
|
1673
|
+
return this.authorizationFailure(
|
|
1674
|
+
`Failed to batch put ${items.length} key(s)`,
|
|
1675
|
+
response,
|
|
1676
|
+
errorText,
|
|
1677
|
+
paths,
|
|
1678
|
+
KVAction.PUT
|
|
1644
1679
|
);
|
|
1645
1680
|
}
|
|
1646
|
-
const
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
{
|
|
1654
|
-
meta: {
|
|
1655
|
-
requestMayHaveDispatched: true,
|
|
1656
|
-
responseReceived: true,
|
|
1657
|
-
status: response.status,
|
|
1658
|
-
outcome: "batch-unconfirmed"
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1661
|
-
)
|
|
1662
|
-
);
|
|
1681
|
+
const quotaError = this.handleQuotaErrorResponse(
|
|
1682
|
+
response,
|
|
1683
|
+
errorText,
|
|
1684
|
+
"batch"
|
|
1685
|
+
);
|
|
1686
|
+
if (quotaError) {
|
|
1687
|
+
return quotaError;
|
|
1663
1688
|
}
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1689
|
+
return err(
|
|
1690
|
+
serviceError(
|
|
1691
|
+
ErrorCodes.KV_WRITE_FAILED,
|
|
1692
|
+
`Failed to batch put ${items.length} key(s): ${response.status} - ${errorText}`,
|
|
1693
|
+
"kv",
|
|
1694
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1695
|
+
)
|
|
1696
|
+
);
|
|
1697
|
+
}
|
|
1698
|
+
let rawBody;
|
|
1699
|
+
try {
|
|
1700
|
+
rawBody = await response.json();
|
|
1701
|
+
} catch (jsonError) {
|
|
1702
|
+
const cause = jsonError instanceof Error ? jsonError : new Error(String(jsonError));
|
|
1703
|
+
const timedOut = isRequestTimeoutError(cause);
|
|
1704
|
+
return err(
|
|
1705
|
+
serviceError(
|
|
1706
|
+
timedOut ? ErrorCodes.TIMEOUT : ErrorCodes.NETWORK_ERROR,
|
|
1707
|
+
timedOut ? `KV batchPut response body did not arrive in time: ${cause.message}` : `KV batchPut response was not valid JSON: ${cause.message}`,
|
|
1708
|
+
"kv",
|
|
1709
|
+
{
|
|
1710
|
+
cause,
|
|
1711
|
+
meta: {
|
|
1712
|
+
...timedOut ? { timeoutMs: cause.timeoutMs } : {},
|
|
1713
|
+
requestMayHaveDispatched: true,
|
|
1714
|
+
responseReceived: true,
|
|
1715
|
+
status: response.status,
|
|
1716
|
+
outcome: "batch-unconfirmed"
|
|
1680
1717
|
}
|
|
1681
|
-
|
|
1682
|
-
)
|
|
1683
|
-
|
|
1684
|
-
return ok(batchResponse);
|
|
1685
|
-
} catch (error) {
|
|
1686
|
-
const wrapped = wrapError("kv", error);
|
|
1687
|
-
return err({
|
|
1688
|
-
...wrapped,
|
|
1689
|
-
meta: { ...wrapped.meta, requestMayHaveDispatched }
|
|
1690
|
-
});
|
|
1691
|
-
} finally {
|
|
1692
|
-
request.dispose();
|
|
1718
|
+
}
|
|
1719
|
+
)
|
|
1720
|
+
);
|
|
1693
1721
|
}
|
|
1694
|
-
|
|
1722
|
+
const batchResponse = this.normalizeBatchPutResponse(rawBody);
|
|
1723
|
+
if (!batchResponse) {
|
|
1724
|
+
return err(
|
|
1725
|
+
serviceError(
|
|
1726
|
+
ErrorCodes.NETWORK_ERROR,
|
|
1727
|
+
"KV batchPut response did not include matching written keys and count",
|
|
1728
|
+
"kv",
|
|
1729
|
+
{
|
|
1730
|
+
meta: {
|
|
1731
|
+
requestMayHaveDispatched: true,
|
|
1732
|
+
responseReceived: true,
|
|
1733
|
+
status: response.status,
|
|
1734
|
+
outcome: "batch-unconfirmed"
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
)
|
|
1738
|
+
);
|
|
1739
|
+
}
|
|
1740
|
+
const requestedPaths = new Set(paths);
|
|
1741
|
+
const writtenPaths = new Set(batchResponse.written);
|
|
1742
|
+
const matchesRequest = batchResponse.count === paths.length && batchResponse.written.length === paths.length && writtenPaths.size === paths.length && batchResponse.written.every((key) => requestedPaths.has(key));
|
|
1743
|
+
if (!matchesRequest) {
|
|
1744
|
+
return err(
|
|
1745
|
+
serviceError(
|
|
1746
|
+
ErrorCodes.NETWORK_ERROR,
|
|
1747
|
+
`KV batchPut response did not confirm all ${paths.length} requested key(s) were written`,
|
|
1748
|
+
"kv",
|
|
1749
|
+
{
|
|
1750
|
+
meta: {
|
|
1751
|
+
requestMayHaveDispatched: true,
|
|
1752
|
+
responseReceived: true,
|
|
1753
|
+
status: response.status,
|
|
1754
|
+
outcome: "batch-unconfirmed"
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
)
|
|
1758
|
+
);
|
|
1759
|
+
}
|
|
1760
|
+
return ok(batchResponse);
|
|
1761
|
+
} catch (error) {
|
|
1762
|
+
const wrapped = wrapError("kv", error);
|
|
1763
|
+
return err({
|
|
1764
|
+
...wrapped,
|
|
1765
|
+
meta: { ...wrapped.meta, requestMayHaveDispatched }
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1695
1768
|
}
|
|
1696
1769
|
/**
|
|
1697
1770
|
* List keys with optional prefix filtering.
|
|
1698
1771
|
*/
|
|
1699
1772
|
async list(options) {
|
|
1700
1773
|
return this.withTelemetry("list", options?.prefix, async () => {
|
|
1774
|
+
if (this.readThroughReady !== null) await this.readThroughReady;
|
|
1701
1775
|
if (!this.requireAuth()) {
|
|
1702
1776
|
return err(authRequiredError("kv"));
|
|
1703
1777
|
}
|
|
@@ -1714,49 +1788,44 @@ var KVService = class extends BaseService {
|
|
|
1714
1788
|
}
|
|
1715
1789
|
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
1716
1790
|
try {
|
|
1717
|
-
const
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1791
|
+
const network = () => this.listFromNetwork(listPath, options, request.signal);
|
|
1792
|
+
let page;
|
|
1793
|
+
if (options?.source === "network") {
|
|
1794
|
+
const observer = this.readThrough;
|
|
1795
|
+
if (observer === null) {
|
|
1796
|
+
page = await network();
|
|
1797
|
+
} else {
|
|
1798
|
+
let outcome = "error";
|
|
1799
|
+
const startedAt = Date.now();
|
|
1800
|
+
try {
|
|
1801
|
+
page = await network();
|
|
1802
|
+
outcome = page.ok ? "found" : "error";
|
|
1803
|
+
} finally {
|
|
1804
|
+
try {
|
|
1805
|
+
observer.observeNetworkRequested({
|
|
1806
|
+
op: "list",
|
|
1807
|
+
space: this.context.session.spaceId,
|
|
1808
|
+
path: listPath,
|
|
1809
|
+
reason: "NETWORK_REQUESTED",
|
|
1810
|
+
outcome,
|
|
1811
|
+
latencyMs: Date.now() - startedAt
|
|
1812
|
+
});
|
|
1813
|
+
} catch {
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1736
1816
|
}
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1817
|
+
} else if (this.readThrough === null) {
|
|
1818
|
+
page = await network();
|
|
1819
|
+
} else {
|
|
1820
|
+
page = await this.readThrough.list({
|
|
1821
|
+
space: this.context.session.spaceId,
|
|
1822
|
+
listPath,
|
|
1823
|
+
options,
|
|
1824
|
+
signal: request.signal,
|
|
1825
|
+
network
|
|
1826
|
+
});
|
|
1746
1827
|
}
|
|
1747
|
-
|
|
1748
|
-
keys = keys ?? [];
|
|
1749
|
-
if (options?.removePrefix && listPath) {
|
|
1750
|
-
const prefixWithSlash = listPath.endsWith("/") ? listPath : `${listPath}/`;
|
|
1751
|
-
keys = keys.map(
|
|
1752
|
-
(key) => key.startsWith(prefixWithSlash) ? key.slice(prefixWithSlash.length) : key
|
|
1753
|
-
);
|
|
1754
|
-
}
|
|
1755
|
-
return ok({
|
|
1756
|
-
keys,
|
|
1757
|
-
...response.headers.get("x-tinycloud-truncated") === null ? {} : { truncated: response.headers.get("x-tinycloud-truncated") === "true" },
|
|
1758
|
-
...response.headers.get("x-tinycloud-next-cursor") === null ? {} : { nextCursor: response.headers.get("x-tinycloud-next-cursor") ?? void 0 }
|
|
1759
|
-
});
|
|
1828
|
+
return page.ok ? this.finishList(page.data, listPath, options?.removePrefix) : page;
|
|
1760
1829
|
} catch (error) {
|
|
1761
1830
|
return err(wrapError("kv", error));
|
|
1762
1831
|
} finally {
|
|
@@ -1764,6 +1833,56 @@ var KVService = class extends BaseService {
|
|
|
1764
1833
|
}
|
|
1765
1834
|
});
|
|
1766
1835
|
}
|
|
1836
|
+
async listFromNetwork(listPath, options, signal) {
|
|
1837
|
+
const response = await this.invokeOperation(
|
|
1838
|
+
listPath,
|
|
1839
|
+
KVAction.LIST,
|
|
1840
|
+
void 0,
|
|
1841
|
+
signal,
|
|
1842
|
+
options?.limit === void 0 && options?.cursor === void 0 ? void 0 : {
|
|
1843
|
+
...options?.limit === void 0 ? {} : { "x-tinycloud-limit": String(options.limit) },
|
|
1844
|
+
...options?.cursor === void 0 ? {} : { "x-tinycloud-cursor": options.cursor }
|
|
1845
|
+
}
|
|
1846
|
+
);
|
|
1847
|
+
if (!response.ok) {
|
|
1848
|
+
if (response.status === 401 || response.status === 403) {
|
|
1849
|
+
return this.authorizationFailure(
|
|
1850
|
+
"Failed to list keys",
|
|
1851
|
+
response,
|
|
1852
|
+
await this.readAuthorizationText(response),
|
|
1853
|
+
[listPath],
|
|
1854
|
+
KVAction.LIST
|
|
1855
|
+
);
|
|
1856
|
+
}
|
|
1857
|
+
const errorText = await response.text();
|
|
1858
|
+
return err(serviceError(
|
|
1859
|
+
ErrorCodes.NETWORK_ERROR,
|
|
1860
|
+
`Failed to list keys: ${response.status} - ${errorText}`,
|
|
1861
|
+
"kv",
|
|
1862
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1863
|
+
));
|
|
1864
|
+
}
|
|
1865
|
+
const keys = await this.parseResponse(response, options?.raw) ?? [];
|
|
1866
|
+
return ok({
|
|
1867
|
+
keys,
|
|
1868
|
+
...response.headers.get("x-tinycloud-truncated") === null ? {} : { truncated: response.headers.get("x-tinycloud-truncated") === "true" },
|
|
1869
|
+
...response.headers.get("x-tinycloud-next-cursor") === null ? {} : { nextCursor: response.headers.get("x-tinycloud-next-cursor") ?? void 0 }
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
finishList(page, listPath, removePrefix) {
|
|
1873
|
+
let keys = page.keys;
|
|
1874
|
+
if (removePrefix && listPath) {
|
|
1875
|
+
const prefixWithSlash = listPath.endsWith("/") ? listPath : `${listPath}/`;
|
|
1876
|
+
keys = keys.map(
|
|
1877
|
+
(key) => key.startsWith(prefixWithSlash) ? key.slice(prefixWithSlash.length) : key
|
|
1878
|
+
);
|
|
1879
|
+
}
|
|
1880
|
+
return ok({
|
|
1881
|
+
keys,
|
|
1882
|
+
...page.truncated === void 0 ? {} : { truncated: page.truncated },
|
|
1883
|
+
...page.nextCursor === void 0 ? {} : { nextCursor: page.nextCursor }
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1767
1886
|
/**
|
|
1768
1887
|
* Read one page of the `tinycloud.kv/sync` change feed for a prefix.
|
|
1769
1888
|
*/
|
|
@@ -1932,61 +2051,22 @@ var KVService = class extends BaseService {
|
|
|
1932
2051
|
*/
|
|
1933
2052
|
async delete(key, options) {
|
|
1934
2053
|
return this.withTelemetry("delete", key, async () => {
|
|
2054
|
+
if (this.readThroughReady !== null) await this.readThroughReady;
|
|
1935
2055
|
if (!this.requireAuth()) {
|
|
1936
2056
|
return err(authRequiredError("kv"));
|
|
1937
2057
|
}
|
|
1938
2058
|
const path = this.getFullPath(key, options?.prefix);
|
|
1939
2059
|
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
1940
2060
|
try {
|
|
1941
|
-
const
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
return this.authorizationFailure(
|
|
1951
|
-
`Failed to delete key ${JSON.stringify(key)}`,
|
|
1952
|
-
response,
|
|
1953
|
-
await this.readAuthorizationText(response),
|
|
1954
|
-
[path],
|
|
1955
|
-
KVAction.DELETE
|
|
1956
|
-
);
|
|
1957
|
-
}
|
|
1958
|
-
if (response.status === 404) {
|
|
1959
|
-
return this.classifyNotFound(response, key);
|
|
1960
|
-
}
|
|
1961
|
-
const errorText = await response.text();
|
|
1962
|
-
if (response.status === 412) {
|
|
1963
|
-
return err(serviceError(
|
|
1964
|
-
ErrorCodes.KV_PRECONDITION_FAILED,
|
|
1965
|
-
`KV precondition failed for key ${JSON.stringify(key)}`,
|
|
1966
|
-
"kv",
|
|
1967
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1968
|
-
));
|
|
1969
|
-
}
|
|
1970
|
-
if (response.status === 503 && options?.ifMatch !== void 0) {
|
|
1971
|
-
return err(serviceError(
|
|
1972
|
-
ErrorCodes.KV_CONFLICT,
|
|
1973
|
-
`Concurrent KV delete conflicted for key ${JSON.stringify(key)}`,
|
|
1974
|
-
"kv",
|
|
1975
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1976
|
-
));
|
|
1977
|
-
}
|
|
1978
|
-
return err(
|
|
1979
|
-
serviceError(
|
|
1980
|
-
ErrorCodes.NETWORK_ERROR,
|
|
1981
|
-
`Failed to delete key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
|
|
1982
|
-
"kv",
|
|
1983
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1984
|
-
)
|
|
1985
|
-
);
|
|
1986
|
-
}
|
|
1987
|
-
return ok({
|
|
1988
|
-
data: void 0,
|
|
1989
|
-
headers: this.createResponseHeaders(response.headers)
|
|
2061
|
+
const network = () => this.deleteFromNetwork(key, path, options, request.signal);
|
|
2062
|
+
if (this.readThrough === null) return await network();
|
|
2063
|
+
return await this.readThrough.write({
|
|
2064
|
+
op: "delete",
|
|
2065
|
+
space: this.context.session.spaceId,
|
|
2066
|
+
entries: [{ path }],
|
|
2067
|
+
signal: request.signal,
|
|
2068
|
+
conditional: options?.ifMatch !== void 0,
|
|
2069
|
+
network
|
|
1990
2070
|
});
|
|
1991
2071
|
} catch (error) {
|
|
1992
2072
|
return err(wrapError("kv", error));
|
|
@@ -1995,6 +2075,54 @@ var KVService = class extends BaseService {
|
|
|
1995
2075
|
}
|
|
1996
2076
|
});
|
|
1997
2077
|
}
|
|
2078
|
+
async deleteFromNetwork(key, path, options, signal) {
|
|
2079
|
+
const response = await this.invokeOperation(
|
|
2080
|
+
path,
|
|
2081
|
+
KVAction.DELETE,
|
|
2082
|
+
void 0,
|
|
2083
|
+
signal,
|
|
2084
|
+
options?.ifMatch === void 0 ? void 0 : { "if-match": options.ifMatch }
|
|
2085
|
+
);
|
|
2086
|
+
if (!response.ok) {
|
|
2087
|
+
if (response.status === 401 || response.status === 403) {
|
|
2088
|
+
return this.authorizationFailure(
|
|
2089
|
+
`Failed to delete key ${JSON.stringify(key)}`,
|
|
2090
|
+
response,
|
|
2091
|
+
await this.readAuthorizationText(response),
|
|
2092
|
+
[path],
|
|
2093
|
+
KVAction.DELETE
|
|
2094
|
+
);
|
|
2095
|
+
}
|
|
2096
|
+
if (response.status === 404) return this.classifyNotFound(response, key);
|
|
2097
|
+
const errorText = await response.text();
|
|
2098
|
+
if (response.status === 412) {
|
|
2099
|
+
return err(serviceError(
|
|
2100
|
+
ErrorCodes.KV_PRECONDITION_FAILED,
|
|
2101
|
+
`KV precondition failed for key ${JSON.stringify(key)}`,
|
|
2102
|
+
"kv",
|
|
2103
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2104
|
+
));
|
|
2105
|
+
}
|
|
2106
|
+
if (response.status === 503 && options?.ifMatch !== void 0) {
|
|
2107
|
+
return err(serviceError(
|
|
2108
|
+
ErrorCodes.KV_CONFLICT,
|
|
2109
|
+
`Concurrent KV delete conflicted for key ${JSON.stringify(key)}`,
|
|
2110
|
+
"kv",
|
|
2111
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2112
|
+
));
|
|
2113
|
+
}
|
|
2114
|
+
return err(serviceError(
|
|
2115
|
+
ErrorCodes.NETWORK_ERROR,
|
|
2116
|
+
`Failed to delete key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
|
|
2117
|
+
"kv",
|
|
2118
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2119
|
+
));
|
|
2120
|
+
}
|
|
2121
|
+
return ok({
|
|
2122
|
+
data: void 0,
|
|
2123
|
+
headers: this.createResponseHeaders(response.headers)
|
|
2124
|
+
});
|
|
2125
|
+
}
|
|
1998
2126
|
/**
|
|
1999
2127
|
* Get metadata for a key without retrieving the value.
|
|
2000
2128
|
*/
|
|
@@ -2023,7 +2151,7 @@ var KVService = class extends BaseService {
|
|
|
2023
2151
|
);
|
|
2024
2152
|
}
|
|
2025
2153
|
if (response.status === 404) {
|
|
2026
|
-
return this.classifyNotFound(response, key);
|
|
2154
|
+
return await this.classifyNotFound(response, key);
|
|
2027
2155
|
}
|
|
2028
2156
|
const errorText = await response.text();
|
|
2029
2157
|
return err(
|
|
@@ -2089,7 +2217,7 @@ var KVService = class extends BaseService {
|
|
|
2089
2217
|
signal: request.signal
|
|
2090
2218
|
});
|
|
2091
2219
|
if (!response.ok) {
|
|
2092
|
-
return this.createSignedReadUrlError(response, key, path);
|
|
2220
|
+
return await this.createSignedReadUrlError(response, key, path);
|
|
2093
2221
|
}
|
|
2094
2222
|
const signedUrl = this.normalizeSignedReadUrlResponse(
|
|
2095
2223
|
await response.json()
|
|
@@ -2161,10 +2289,73 @@ var KVService = class extends BaseService {
|
|
|
2161
2289
|
* Service identifier for registration.
|
|
2162
2290
|
*/
|
|
2163
2291
|
KVService.serviceName = "kv";
|
|
2292
|
+
|
|
2293
|
+
// src/kv/replication/constants.ts
|
|
2294
|
+
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.";
|
|
2295
|
+
|
|
2296
|
+
// src/kv/replication/identity.ts
|
|
2297
|
+
var SPACE_ID = /^(tinycloud:pkh:eip155:\d+:)([^:]+)(:.+)$/;
|
|
2298
|
+
var PRINCIPAL_DID = /^(did:pkh:eip155:\d+:)([^:]+)$/;
|
|
2299
|
+
function canonicalReplicationIdentity(raw) {
|
|
2300
|
+
if (!raw || typeof raw.host !== "string" || typeof raw.space !== "string" || typeof raw.principal !== "string") {
|
|
2301
|
+
throw new TypeError(
|
|
2302
|
+
"Replication identity host, space, and principal must be strings"
|
|
2303
|
+
);
|
|
2304
|
+
}
|
|
2305
|
+
const authority = /^https?:\/\/([^/?#]*)/i.exec(raw.host)?.[1];
|
|
2306
|
+
let url;
|
|
2307
|
+
try {
|
|
2308
|
+
url = new URL(raw.host);
|
|
2309
|
+
} catch {
|
|
2310
|
+
throw new TypeError(
|
|
2311
|
+
"Replication identity host must be an absolute HTTP(S) URL"
|
|
2312
|
+
);
|
|
2313
|
+
}
|
|
2314
|
+
if (!authority || authority.includes("@") || raw.host.includes("?") || raw.host.includes("#") || url.username || url.password) {
|
|
2315
|
+
throw new TypeError(
|
|
2316
|
+
"Replication identity host must be HTTP(S) without credentials, query, or fragment"
|
|
2317
|
+
);
|
|
2318
|
+
}
|
|
2319
|
+
const space = SPACE_ID.exec(raw.space);
|
|
2320
|
+
const principal = PRINCIPAL_DID.exec(raw.principal);
|
|
2321
|
+
if (!space || !principal) {
|
|
2322
|
+
throw new TypeError(
|
|
2323
|
+
"Replication identity requires a full TinyCloud space id and a pkh principal DID"
|
|
2324
|
+
);
|
|
2325
|
+
}
|
|
2326
|
+
return {
|
|
2327
|
+
host: `${url.origin}${url.pathname.replace(/\/+$/, "")}`,
|
|
2328
|
+
space: `${space[1]}${space[2].toLowerCase()}${space[3]}`,
|
|
2329
|
+
principal: `${principal[1]}${principal[2].toLowerCase()}`
|
|
2330
|
+
};
|
|
2331
|
+
}
|
|
2332
|
+
function replicationIdentityKey(id) {
|
|
2333
|
+
const canonical = canonicalReplicationIdentity(id);
|
|
2334
|
+
return `tc-replication/v1
|
|
2335
|
+
${canonical.host}
|
|
2336
|
+
${canonical.space}
|
|
2337
|
+
${canonical.principal}`;
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
// src/kv/replication/scope.ts
|
|
2341
|
+
function kvPrefixCovers(prefix, key) {
|
|
2342
|
+
if (prefix === "") return true;
|
|
2343
|
+
if (prefix.endsWith("/")) return key.startsWith(prefix);
|
|
2344
|
+
return key === prefix || key.startsWith(`${prefix}/`);
|
|
2345
|
+
}
|
|
2346
|
+
function requiresSecretsOptIn(space, prefix) {
|
|
2347
|
+
if (space === "secrets" || space.endsWith(":secrets")) return true;
|
|
2348
|
+
return prefix.split("/", 1)[0] === "vault";
|
|
2349
|
+
}
|
|
2164
2350
|
export {
|
|
2351
|
+
CLEAR_PENDING_WARNING,
|
|
2165
2352
|
DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
|
|
2166
2353
|
KVAction,
|
|
2167
2354
|
KVService,
|
|
2168
|
-
PrefixedKVService
|
|
2355
|
+
PrefixedKVService,
|
|
2356
|
+
canonicalReplicationIdentity,
|
|
2357
|
+
kvPrefixCovers,
|
|
2358
|
+
replicationIdentityKey,
|
|
2359
|
+
requiresSecretsOptIn
|
|
2169
2360
|
};
|
|
2170
2361
|
//# sourceMappingURL=index.js.map
|