@tinycloud/sdk-services 3.2.0-beta.2 → 3.2.0-beta.5
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 +605 -357
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -26
- package/dist/index.d.ts +37 -26
- package/dist/index.js +600 -357
- 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 +540 -338
- package/dist/kv/index.cjs.map +1 -1
- package/dist/kv/index.d.cts +18 -468
- package/dist/kv/index.d.ts +18 -468
- package/dist/kv/index.js +534 -337
- package/dist/kv/index.js.map +1 -1
- package/dist/kv/replication/index.cjs +1398 -0
- package/dist/kv/replication/index.cjs.map +1 -0
- package/dist/kv/replication/index.d.cts +10 -0
- package/dist/kv/replication/index.d.ts +10 -0
- package/dist/kv/replication/index.js +1365 -0
- package/dist/kv/replication/index.js.map +1 -0
- package/dist/kv/replication/prefix.cjs +43 -0
- package/dist/kv/replication/prefix.cjs.map +1 -0
- package/dist/kv/replication/prefix.d.cts +6 -0
- package/dist/kv/replication/prefix.d.ts +6 -0
- package/dist/kv/replication/prefix.js +17 -0
- package/dist/kv/replication/prefix.js.map +1 -0
- package/dist/scope-BKge7zDT.d.ts +957 -0
- package/dist/scope-C05VVAXH.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 +31 -1
package/dist/kv/index.cjs
CHANGED
|
@@ -20,10 +20,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/kv/index.ts
|
|
21
21
|
var kv_exports = {};
|
|
22
22
|
__export(kv_exports, {
|
|
23
|
+
CLEAR_PENDING_WARNING: () => CLEAR_PENDING_WARNING,
|
|
23
24
|
DEFAULT_SIGNED_READ_URL_EXPIRY_MS: () => DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
|
|
24
25
|
KVAction: () => KVAction,
|
|
25
26
|
KVService: () => KVService,
|
|
26
|
-
PrefixedKVService: () => PrefixedKVService
|
|
27
|
+
PrefixedKVService: () => PrefixedKVService,
|
|
28
|
+
canonicalReplicationIdentity: () => canonicalReplicationIdentity,
|
|
29
|
+
kvPrefixCovers: () => kvPrefixCovers,
|
|
30
|
+
replicationIdentityKey: () => replicationIdentityKey,
|
|
31
|
+
requiresSecretsOptIn: () => requiresSecretsOptIn
|
|
27
32
|
});
|
|
28
33
|
module.exports = __toCommonJS(kv_exports);
|
|
29
34
|
|
|
@@ -89,7 +94,12 @@ var TelemetryEvents = {
|
|
|
89
94
|
SERVICE_ERROR: "service.error",
|
|
90
95
|
SERVICE_RETRY: "service.retry",
|
|
91
96
|
SESSION_CHANGED: "session.changed",
|
|
92
|
-
SESSION_EXPIRED: "session.expired"
|
|
97
|
+
SESSION_EXPIRED: "session.expired",
|
|
98
|
+
REPLICATION_READ: "replication.read",
|
|
99
|
+
REPLICATION_WRITE: "replication.write",
|
|
100
|
+
REPLICATION_SYNC: "replication.sync",
|
|
101
|
+
REPLICATION_STATE: "replication.state",
|
|
102
|
+
REPLICATION_DIVERGENCE: "replication.divergence"
|
|
93
103
|
};
|
|
94
104
|
function ok(data) {
|
|
95
105
|
return { ok: true, data };
|
|
@@ -617,6 +627,7 @@ var BaseService = class {
|
|
|
617
627
|
const parents = [
|
|
618
628
|
this.abortController.signal,
|
|
619
629
|
this.context?.abortSignal,
|
|
630
|
+
this.context?.operationAbortSignal,
|
|
620
631
|
signal
|
|
621
632
|
].filter((parent) => parent !== void 0);
|
|
622
633
|
for (const parent of parents) {
|
|
@@ -862,14 +873,28 @@ var KVService = class extends BaseService {
|
|
|
862
873
|
*/
|
|
863
874
|
constructor(config = {}) {
|
|
864
875
|
super();
|
|
876
|
+
this.readThrough = null;
|
|
877
|
+
this.readThroughReady = null;
|
|
865
878
|
this._config = config;
|
|
866
879
|
}
|
|
880
|
+
/** Delay KV routing until an asynchronously loaded read-through is attached. */
|
|
881
|
+
setReadThroughReady(ready) {
|
|
882
|
+
const settled = ready.then(() => void 0, () => void 0);
|
|
883
|
+
this.readThroughReady = settled;
|
|
884
|
+
void settled.then(() => {
|
|
885
|
+
if (this.readThroughReady === settled) this.readThroughReady = null;
|
|
886
|
+
});
|
|
887
|
+
}
|
|
867
888
|
/**
|
|
868
889
|
* Get the service configuration.
|
|
869
890
|
*/
|
|
870
891
|
get config() {
|
|
871
892
|
return this._config;
|
|
872
893
|
}
|
|
894
|
+
/** Install or remove the optional replication read-through policy. */
|
|
895
|
+
setReadThrough(readThrough) {
|
|
896
|
+
this.readThrough = readThrough;
|
|
897
|
+
}
|
|
873
898
|
handleQuotaErrorResponse(response, errorText, key) {
|
|
874
899
|
if (response.status !== 402 && response.status !== 413) {
|
|
875
900
|
return void 0;
|
|
@@ -1123,6 +1148,7 @@ var KVService = class extends BaseService {
|
|
|
1123
1148
|
return aligned;
|
|
1124
1149
|
}
|
|
1125
1150
|
async batchRead(keys, action, options) {
|
|
1151
|
+
if (this.readThroughReady !== null) await this.readThroughReady;
|
|
1126
1152
|
if (!this.requireAuth()) return err(authRequiredError("kv"));
|
|
1127
1153
|
if (keys.length === 0) return ok({ results: [], count: 0 });
|
|
1128
1154
|
if (keys.length > MAX_KV_BATCH_READ_ITEMS) {
|
|
@@ -1373,6 +1399,7 @@ var KVService = class extends BaseService {
|
|
|
1373
1399
|
*/
|
|
1374
1400
|
async get(key, options) {
|
|
1375
1401
|
return this.withTelemetry("get", key, async () => {
|
|
1402
|
+
if (this.readThroughReady !== null) await this.readThroughReady;
|
|
1376
1403
|
if (!this.requireAuth()) {
|
|
1377
1404
|
return err(authRequiredError("kv"));
|
|
1378
1405
|
}
|
|
@@ -1386,64 +1413,38 @@ var KVService = class extends BaseService {
|
|
|
1386
1413
|
const path = this.getFullPath(key, options?.prefix);
|
|
1387
1414
|
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
1388
1415
|
try {
|
|
1389
|
-
const
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
response,
|
|
1412
|
-
errorText2,
|
|
1413
|
-
[path],
|
|
1414
|
-
KVAction.GET,
|
|
1415
|
-
structuredCapability ? { permissionHint, ...structuredCapability } : {}
|
|
1416
|
-
);
|
|
1417
|
-
}
|
|
1418
|
-
if (response.status === 404) {
|
|
1419
|
-
return await this.classifyNotFound(response, key);
|
|
1420
|
-
}
|
|
1421
|
-
const errorText = await response.text();
|
|
1422
|
-
if (response.status === 413) {
|
|
1423
|
-
return err(serviceError(
|
|
1424
|
-
ErrorCodes.KV_RESPONSE_TOO_LARGE,
|
|
1425
|
-
`KV value at key ${JSON.stringify(key)} exceeds the requested response limit`,
|
|
1426
|
-
"kv",
|
|
1427
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1428
|
-
));
|
|
1416
|
+
const network = () => this.getFromNetwork(key, path, options, request.signal);
|
|
1417
|
+
if (options?.source === "network") {
|
|
1418
|
+
const observer = this.readThrough;
|
|
1419
|
+
if (observer === null) return await network();
|
|
1420
|
+
let outcome = "error";
|
|
1421
|
+
const startedAt = Date.now();
|
|
1422
|
+
try {
|
|
1423
|
+
const result = await network();
|
|
1424
|
+
outcome = result.ok ? "found" : result.error.code === ErrorCodes.KV_NOT_FOUND ? "not_found" : "error";
|
|
1425
|
+
return result;
|
|
1426
|
+
} finally {
|
|
1427
|
+
try {
|
|
1428
|
+
observer.observeNetworkRequested({
|
|
1429
|
+
op: "get",
|
|
1430
|
+
space: this.context.session.spaceId,
|
|
1431
|
+
path,
|
|
1432
|
+
reason: "NETWORK_REQUESTED",
|
|
1433
|
+
outcome,
|
|
1434
|
+
latencyMs: Date.now() - startedAt
|
|
1435
|
+
});
|
|
1436
|
+
} catch {
|
|
1437
|
+
}
|
|
1429
1438
|
}
|
|
1430
|
-
return err(
|
|
1431
|
-
serviceError(
|
|
1432
|
-
ErrorCodes.NETWORK_ERROR,
|
|
1433
|
-
`Failed to get key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
|
|
1434
|
-
"kv",
|
|
1435
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1436
|
-
)
|
|
1437
|
-
);
|
|
1438
1439
|
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1440
|
+
if (this.readThrough === null) return await network();
|
|
1441
|
+
return await this.readThrough.get({
|
|
1442
|
+
space: this.context.session.spaceId,
|
|
1443
|
+
key,
|
|
1444
|
+
path,
|
|
1445
|
+
options,
|
|
1446
|
+
signal: request.signal,
|
|
1447
|
+
network
|
|
1447
1448
|
});
|
|
1448
1449
|
} catch (error) {
|
|
1449
1450
|
return err(wrapError("kv", error));
|
|
@@ -1452,6 +1453,56 @@ var KVService = class extends BaseService {
|
|
|
1452
1453
|
}
|
|
1453
1454
|
});
|
|
1454
1455
|
}
|
|
1456
|
+
async getFromNetwork(key, path, options, signal) {
|
|
1457
|
+
const response = await this.invokeOperation(
|
|
1458
|
+
path,
|
|
1459
|
+
KVAction.GET,
|
|
1460
|
+
void 0,
|
|
1461
|
+
signal,
|
|
1462
|
+
options?.maxResponseBytes === void 0 ? void 0 : { "x-tinycloud-max-response-bytes": String(options.maxResponseBytes) }
|
|
1463
|
+
);
|
|
1464
|
+
if (!response.ok) {
|
|
1465
|
+
if (response.status === 401 || response.status === 403) {
|
|
1466
|
+
const errorText2 = await this.readAuthorizationText(response);
|
|
1467
|
+
const permissionHint = parsePermissionHintFromErrorText(errorText2);
|
|
1468
|
+
const structuredCapability = permissionHint?.service === "tinycloud.kv" && permissionHint.space === this.context.session.spaceId && permissionHint.path === path ? validatedCapabilityOf({
|
|
1469
|
+
service: "kv",
|
|
1470
|
+
code: ErrorCodes.AUTH_UNAUTHORIZED,
|
|
1471
|
+
meta: {
|
|
1472
|
+
status: response.status,
|
|
1473
|
+
resource: `${this.context.session.spaceId}/kv/${path}`,
|
|
1474
|
+
requiredAction: KVAction.GET
|
|
1475
|
+
}
|
|
1476
|
+
}) : void 0;
|
|
1477
|
+
return this.authorizationFailure(
|
|
1478
|
+
`Failed to get key ${JSON.stringify(key)}`,
|
|
1479
|
+
response,
|
|
1480
|
+
errorText2,
|
|
1481
|
+
[path],
|
|
1482
|
+
KVAction.GET,
|
|
1483
|
+
structuredCapability ? { permissionHint, ...structuredCapability } : {}
|
|
1484
|
+
);
|
|
1485
|
+
}
|
|
1486
|
+
if (response.status === 404) return this.classifyNotFound(response, key);
|
|
1487
|
+
const errorText = await response.text();
|
|
1488
|
+
if (response.status === 413) {
|
|
1489
|
+
return err(serviceError(
|
|
1490
|
+
ErrorCodes.KV_RESPONSE_TOO_LARGE,
|
|
1491
|
+
`KV value at key ${JSON.stringify(key)} exceeds the requested response limit`,
|
|
1492
|
+
"kv",
|
|
1493
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1494
|
+
));
|
|
1495
|
+
}
|
|
1496
|
+
return err(serviceError(
|
|
1497
|
+
ErrorCodes.NETWORK_ERROR,
|
|
1498
|
+
`Failed to get key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
|
|
1499
|
+
"kv",
|
|
1500
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1501
|
+
));
|
|
1502
|
+
}
|
|
1503
|
+
const data = await this.parseResponse(response, options?.raw, options?.binary);
|
|
1504
|
+
return ok({ data, headers: this.createResponseHeaders(response.headers) });
|
|
1505
|
+
}
|
|
1455
1506
|
async batchGet(keys, options) {
|
|
1456
1507
|
return this.withTelemetry(
|
|
1457
1508
|
"batchGet",
|
|
@@ -1464,6 +1515,7 @@ var KVService = class extends BaseService {
|
|
|
1464
1515
|
*/
|
|
1465
1516
|
async put(key, value, options) {
|
|
1466
1517
|
return this.withTelemetry("put", key, async () => {
|
|
1518
|
+
if (this.readThroughReady !== null) await this.readThroughReady;
|
|
1467
1519
|
if (!this.requireAuth()) {
|
|
1468
1520
|
return err(authRequiredError("kv"));
|
|
1469
1521
|
}
|
|
@@ -1478,63 +1530,15 @@ var KVService = class extends BaseService {
|
|
|
1478
1530
|
const body = this.serializePutValue(value, options?.contentType);
|
|
1479
1531
|
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
1480
1532
|
try {
|
|
1481
|
-
const
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
{
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
);
|
|
1491
|
-
if (response.status === 401 || response.status === 403) {
|
|
1492
|
-
return this.authorizationFailure(
|
|
1493
|
-
`Failed to put key ${JSON.stringify(key)}`,
|
|
1494
|
-
response,
|
|
1495
|
-
await this.readAuthorizationText(response),
|
|
1496
|
-
[path],
|
|
1497
|
-
KVAction.PUT
|
|
1498
|
-
);
|
|
1499
|
-
}
|
|
1500
|
-
if (!response.ok) {
|
|
1501
|
-
const errorText = await response.text();
|
|
1502
|
-
if (response.status === 412) {
|
|
1503
|
-
return err(serviceError(
|
|
1504
|
-
ErrorCodes.KV_PRECONDITION_FAILED,
|
|
1505
|
-
`KV precondition failed for key ${JSON.stringify(key)}`,
|
|
1506
|
-
"kv",
|
|
1507
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1508
|
-
));
|
|
1509
|
-
}
|
|
1510
|
-
if (response.status === 503 && (options?.ifMatch !== void 0 || options?.ifNoneMatch !== void 0)) {
|
|
1511
|
-
return err(serviceError(
|
|
1512
|
-
ErrorCodes.KV_CONFLICT,
|
|
1513
|
-
`Concurrent KV update conflicted for key ${JSON.stringify(key)}`,
|
|
1514
|
-
"kv",
|
|
1515
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1516
|
-
));
|
|
1517
|
-
}
|
|
1518
|
-
const quotaError = this.handleQuotaErrorResponse(
|
|
1519
|
-
response,
|
|
1520
|
-
errorText,
|
|
1521
|
-
key
|
|
1522
|
-
);
|
|
1523
|
-
if (quotaError) {
|
|
1524
|
-
return quotaError;
|
|
1525
|
-
}
|
|
1526
|
-
return err(
|
|
1527
|
-
serviceError(
|
|
1528
|
-
ErrorCodes.KV_WRITE_FAILED,
|
|
1529
|
-
`Failed to put key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
|
|
1530
|
-
"kv",
|
|
1531
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1532
|
-
)
|
|
1533
|
-
);
|
|
1534
|
-
}
|
|
1535
|
-
return ok({
|
|
1536
|
-
data: void 0,
|
|
1537
|
-
headers: this.createResponseHeaders(response.headers)
|
|
1533
|
+
const network = () => this.putToNetwork(key, path, body, options, request.signal);
|
|
1534
|
+
if (this.readThrough === null) return await network();
|
|
1535
|
+
return await this.readThrough.write({
|
|
1536
|
+
op: "put",
|
|
1537
|
+
space: this.context.session.spaceId,
|
|
1538
|
+
entries: [{ path, body }],
|
|
1539
|
+
signal: request.signal,
|
|
1540
|
+
conditional: options?.ifMatch !== void 0 || options?.ifNoneMatch !== void 0,
|
|
1541
|
+
network
|
|
1538
1542
|
});
|
|
1539
1543
|
} catch (error) {
|
|
1540
1544
|
return err(wrapError("kv", error));
|
|
@@ -1543,11 +1547,64 @@ var KVService = class extends BaseService {
|
|
|
1543
1547
|
}
|
|
1544
1548
|
});
|
|
1545
1549
|
}
|
|
1550
|
+
async putToNetwork(key, path, body, options, signal) {
|
|
1551
|
+
const response = await this.invokeOperation(
|
|
1552
|
+
path,
|
|
1553
|
+
KVAction.PUT,
|
|
1554
|
+
body,
|
|
1555
|
+
signal,
|
|
1556
|
+
{
|
|
1557
|
+
...options?.ifMatch === void 0 ? {} : { "if-match": options.ifMatch },
|
|
1558
|
+
...options?.ifNoneMatch === void 0 ? {} : { "if-none-match": options.ifNoneMatch }
|
|
1559
|
+
}
|
|
1560
|
+
);
|
|
1561
|
+
if (response.status === 401 || response.status === 403) {
|
|
1562
|
+
return this.authorizationFailure(
|
|
1563
|
+
`Failed to put key ${JSON.stringify(key)}`,
|
|
1564
|
+
response,
|
|
1565
|
+
await this.readAuthorizationText(response),
|
|
1566
|
+
[path],
|
|
1567
|
+
KVAction.PUT
|
|
1568
|
+
);
|
|
1569
|
+
}
|
|
1570
|
+
if (!response.ok) {
|
|
1571
|
+
const errorText = await response.text();
|
|
1572
|
+
if (response.status === 412) {
|
|
1573
|
+
return err(serviceError(
|
|
1574
|
+
ErrorCodes.KV_PRECONDITION_FAILED,
|
|
1575
|
+
`KV precondition failed for key ${JSON.stringify(key)}`,
|
|
1576
|
+
"kv",
|
|
1577
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1578
|
+
));
|
|
1579
|
+
}
|
|
1580
|
+
if (response.status === 503 && (options?.ifMatch !== void 0 || options?.ifNoneMatch !== void 0)) {
|
|
1581
|
+
return err(serviceError(
|
|
1582
|
+
ErrorCodes.KV_CONFLICT,
|
|
1583
|
+
`Concurrent KV update conflicted for key ${JSON.stringify(key)}`,
|
|
1584
|
+
"kv",
|
|
1585
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1586
|
+
));
|
|
1587
|
+
}
|
|
1588
|
+
const quotaError = this.handleQuotaErrorResponse(response, errorText, key);
|
|
1589
|
+
if (quotaError) return quotaError;
|
|
1590
|
+
return err(serviceError(
|
|
1591
|
+
ErrorCodes.KV_WRITE_FAILED,
|
|
1592
|
+
`Failed to put key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
|
|
1593
|
+
"kv",
|
|
1594
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1595
|
+
));
|
|
1596
|
+
}
|
|
1597
|
+
return ok({
|
|
1598
|
+
data: void 0,
|
|
1599
|
+
headers: this.createResponseHeaders(response.headers)
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1546
1602
|
/**
|
|
1547
1603
|
* Store multiple values in one TinyCloud KV invocation.
|
|
1548
1604
|
*/
|
|
1549
1605
|
async batchPut(items, options) {
|
|
1550
1606
|
return this.withTelemetry("batchPut", String(items.length), async () => {
|
|
1607
|
+
if (this.readThroughReady !== null) await this.readThroughReady;
|
|
1551
1608
|
if (!this.requireAuth()) {
|
|
1552
1609
|
return err(authRequiredError("kv"));
|
|
1553
1610
|
}
|
|
@@ -1578,155 +1635,177 @@ var KVService = class extends BaseService {
|
|
|
1578
1635
|
}
|
|
1579
1636
|
seen.add(path);
|
|
1580
1637
|
}
|
|
1581
|
-
let requestMayHaveDispatched = false;
|
|
1582
1638
|
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
1583
1639
|
try {
|
|
1584
|
-
const
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
session,
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1640
|
+
const network = () => this.batchPutToNetwork(items, paths, request.signal);
|
|
1641
|
+
if (this.readThrough === null) return await network();
|
|
1642
|
+
const entries = items.map((item, index) => ({
|
|
1643
|
+
path: paths[index],
|
|
1644
|
+
body: this.serializeBatchPutValue(item)
|
|
1645
|
+
}));
|
|
1646
|
+
return await this.readThrough.write({
|
|
1647
|
+
op: "batchPut",
|
|
1648
|
+
space: session.spaceId,
|
|
1649
|
+
entries,
|
|
1650
|
+
signal: request.signal,
|
|
1651
|
+
network: () => this.batchPutToNetwork(items, paths, request.signal, entries)
|
|
1652
|
+
});
|
|
1653
|
+
} catch (error) {
|
|
1654
|
+
const wrapped = wrapError("kv", error);
|
|
1655
|
+
return err({ ...wrapped, meta: { ...wrapped.meta, requestMayHaveDispatched: false } });
|
|
1656
|
+
} finally {
|
|
1657
|
+
request.dispose();
|
|
1658
|
+
}
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1661
|
+
async batchPutToNetwork(items, paths, signal, entries) {
|
|
1662
|
+
const session = this.context.session;
|
|
1663
|
+
let requestMayHaveDispatched = false;
|
|
1664
|
+
try {
|
|
1665
|
+
const body = new FormData();
|
|
1666
|
+
for (let index = 0; index < items.length; index++) {
|
|
1667
|
+
body.append(
|
|
1668
|
+
encodeKvBatchPartName(paths[index]),
|
|
1669
|
+
entries?.[index]?.body ?? this.serializeBatchPutValue(items[index])
|
|
1599
1670
|
);
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
return this.authorizationFailure(
|
|
1627
|
-
`Failed to batch put ${items.length} key(s)`,
|
|
1628
|
-
response,
|
|
1629
|
-
errorText,
|
|
1630
|
-
paths,
|
|
1631
|
-
KVAction.PUT
|
|
1632
|
-
);
|
|
1633
|
-
}
|
|
1634
|
-
const quotaError = this.handleQuotaErrorResponse(
|
|
1635
|
-
response,
|
|
1636
|
-
errorText,
|
|
1637
|
-
"batch"
|
|
1638
|
-
);
|
|
1639
|
-
if (quotaError) {
|
|
1640
|
-
return quotaError;
|
|
1641
|
-
}
|
|
1671
|
+
}
|
|
1672
|
+
const headers = this.context.invokeAny(
|
|
1673
|
+
session,
|
|
1674
|
+
paths.map((path) => ({
|
|
1675
|
+
spaceId: session.spaceId,
|
|
1676
|
+
service: "kv",
|
|
1677
|
+
path,
|
|
1678
|
+
action: KVAction.PUT
|
|
1679
|
+
}))
|
|
1680
|
+
);
|
|
1681
|
+
const url = `${this.host}/invoke`;
|
|
1682
|
+
const init = {
|
|
1683
|
+
method: "POST",
|
|
1684
|
+
headers,
|
|
1685
|
+
body,
|
|
1686
|
+
signal
|
|
1687
|
+
};
|
|
1688
|
+
const fetchFn = this.context.fetch;
|
|
1689
|
+
requestMayHaveDispatched = true;
|
|
1690
|
+
const response = await fetchFn(url, init);
|
|
1691
|
+
if (!response.ok) {
|
|
1692
|
+
let errorText;
|
|
1693
|
+
try {
|
|
1694
|
+
errorText = await response.text();
|
|
1695
|
+
} catch (textError) {
|
|
1696
|
+
const cause = textError instanceof Error ? textError : new Error(String(textError));
|
|
1642
1697
|
return err(
|
|
1643
1698
|
serviceError(
|
|
1644
1699
|
ErrorCodes.KV_WRITE_FAILED,
|
|
1645
|
-
`Failed to batch put ${items.length} key(s): ${response.status} - ${
|
|
1700
|
+
`Failed to batch put ${items.length} key(s): ${response.status} - <response body could not be read: ${cause.message}>`,
|
|
1646
1701
|
"kv",
|
|
1647
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1702
|
+
{ cause, meta: { status: response.status, statusText: response.statusText } }
|
|
1648
1703
|
)
|
|
1649
1704
|
);
|
|
1650
1705
|
}
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
serviceError(
|
|
1659
|
-
timedOut ? ErrorCodes.TIMEOUT : ErrorCodes.NETWORK_ERROR,
|
|
1660
|
-
timedOut ? `KV batchPut response body did not arrive in time: ${cause.message}` : `KV batchPut response was not valid JSON: ${cause.message}`,
|
|
1661
|
-
"kv",
|
|
1662
|
-
{
|
|
1663
|
-
cause,
|
|
1664
|
-
meta: {
|
|
1665
|
-
...timedOut ? { timeoutMs: cause.timeoutMs } : {},
|
|
1666
|
-
requestMayHaveDispatched: true,
|
|
1667
|
-
responseReceived: true,
|
|
1668
|
-
status: response.status,
|
|
1669
|
-
outcome: "batch-unconfirmed"
|
|
1670
|
-
}
|
|
1671
|
-
}
|
|
1672
|
-
)
|
|
1706
|
+
if (response.status === 401 || response.status === 403) {
|
|
1707
|
+
return this.authorizationFailure(
|
|
1708
|
+
`Failed to batch put ${items.length} key(s)`,
|
|
1709
|
+
response,
|
|
1710
|
+
errorText,
|
|
1711
|
+
paths,
|
|
1712
|
+
KVAction.PUT
|
|
1673
1713
|
);
|
|
1674
1714
|
}
|
|
1675
|
-
const
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
{
|
|
1683
|
-
meta: {
|
|
1684
|
-
requestMayHaveDispatched: true,
|
|
1685
|
-
responseReceived: true,
|
|
1686
|
-
status: response.status,
|
|
1687
|
-
outcome: "batch-unconfirmed"
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
)
|
|
1691
|
-
);
|
|
1715
|
+
const quotaError = this.handleQuotaErrorResponse(
|
|
1716
|
+
response,
|
|
1717
|
+
errorText,
|
|
1718
|
+
"batch"
|
|
1719
|
+
);
|
|
1720
|
+
if (quotaError) {
|
|
1721
|
+
return quotaError;
|
|
1692
1722
|
}
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1723
|
+
return err(
|
|
1724
|
+
serviceError(
|
|
1725
|
+
ErrorCodes.KV_WRITE_FAILED,
|
|
1726
|
+
`Failed to batch put ${items.length} key(s): ${response.status} - ${errorText}`,
|
|
1727
|
+
"kv",
|
|
1728
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1729
|
+
)
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1732
|
+
let rawBody;
|
|
1733
|
+
try {
|
|
1734
|
+
rawBody = await response.json();
|
|
1735
|
+
} catch (jsonError) {
|
|
1736
|
+
const cause = jsonError instanceof Error ? jsonError : new Error(String(jsonError));
|
|
1737
|
+
const timedOut = isRequestTimeoutError(cause);
|
|
1738
|
+
return err(
|
|
1739
|
+
serviceError(
|
|
1740
|
+
timedOut ? ErrorCodes.TIMEOUT : ErrorCodes.NETWORK_ERROR,
|
|
1741
|
+
timedOut ? `KV batchPut response body did not arrive in time: ${cause.message}` : `KV batchPut response was not valid JSON: ${cause.message}`,
|
|
1742
|
+
"kv",
|
|
1743
|
+
{
|
|
1744
|
+
cause,
|
|
1745
|
+
meta: {
|
|
1746
|
+
...timedOut ? { timeoutMs: cause.timeoutMs } : {},
|
|
1747
|
+
requestMayHaveDispatched: true,
|
|
1748
|
+
responseReceived: true,
|
|
1749
|
+
status: response.status,
|
|
1750
|
+
outcome: "batch-unconfirmed"
|
|
1709
1751
|
}
|
|
1710
|
-
|
|
1711
|
-
)
|
|
1712
|
-
|
|
1713
|
-
return ok(batchResponse);
|
|
1714
|
-
} catch (error) {
|
|
1715
|
-
const wrapped = wrapError("kv", error);
|
|
1716
|
-
return err({
|
|
1717
|
-
...wrapped,
|
|
1718
|
-
meta: { ...wrapped.meta, requestMayHaveDispatched }
|
|
1719
|
-
});
|
|
1720
|
-
} finally {
|
|
1721
|
-
request.dispose();
|
|
1752
|
+
}
|
|
1753
|
+
)
|
|
1754
|
+
);
|
|
1722
1755
|
}
|
|
1723
|
-
|
|
1756
|
+
const batchResponse = this.normalizeBatchPutResponse(rawBody);
|
|
1757
|
+
if (!batchResponse) {
|
|
1758
|
+
return err(
|
|
1759
|
+
serviceError(
|
|
1760
|
+
ErrorCodes.NETWORK_ERROR,
|
|
1761
|
+
"KV batchPut response did not include matching written keys and count",
|
|
1762
|
+
"kv",
|
|
1763
|
+
{
|
|
1764
|
+
meta: {
|
|
1765
|
+
requestMayHaveDispatched: true,
|
|
1766
|
+
responseReceived: true,
|
|
1767
|
+
status: response.status,
|
|
1768
|
+
outcome: "batch-unconfirmed"
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
)
|
|
1772
|
+
);
|
|
1773
|
+
}
|
|
1774
|
+
const requestedPaths = new Set(paths);
|
|
1775
|
+
const writtenPaths = new Set(batchResponse.written);
|
|
1776
|
+
const matchesRequest = batchResponse.count === paths.length && batchResponse.written.length === paths.length && writtenPaths.size === paths.length && batchResponse.written.every((key) => requestedPaths.has(key));
|
|
1777
|
+
if (!matchesRequest) {
|
|
1778
|
+
return err(
|
|
1779
|
+
serviceError(
|
|
1780
|
+
ErrorCodes.NETWORK_ERROR,
|
|
1781
|
+
`KV batchPut response did not confirm all ${paths.length} requested key(s) were written`,
|
|
1782
|
+
"kv",
|
|
1783
|
+
{
|
|
1784
|
+
meta: {
|
|
1785
|
+
requestMayHaveDispatched: true,
|
|
1786
|
+
responseReceived: true,
|
|
1787
|
+
status: response.status,
|
|
1788
|
+
outcome: "batch-unconfirmed"
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
)
|
|
1792
|
+
);
|
|
1793
|
+
}
|
|
1794
|
+
return ok(batchResponse);
|
|
1795
|
+
} catch (error) {
|
|
1796
|
+
const wrapped = wrapError("kv", error);
|
|
1797
|
+
return err({
|
|
1798
|
+
...wrapped,
|
|
1799
|
+
meta: { ...wrapped.meta, requestMayHaveDispatched }
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1724
1802
|
}
|
|
1725
1803
|
/**
|
|
1726
1804
|
* List keys with optional prefix filtering.
|
|
1727
1805
|
*/
|
|
1728
1806
|
async list(options) {
|
|
1729
1807
|
return this.withTelemetry("list", options?.prefix, async () => {
|
|
1808
|
+
if (this.readThroughReady !== null) await this.readThroughReady;
|
|
1730
1809
|
if (!this.requireAuth()) {
|
|
1731
1810
|
return err(authRequiredError("kv"));
|
|
1732
1811
|
}
|
|
@@ -1743,49 +1822,44 @@ var KVService = class extends BaseService {
|
|
|
1743
1822
|
}
|
|
1744
1823
|
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
1745
1824
|
try {
|
|
1746
|
-
const
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1825
|
+
const network = () => this.listFromNetwork(listPath, options, request.signal);
|
|
1826
|
+
let page;
|
|
1827
|
+
if (options?.source === "network") {
|
|
1828
|
+
const observer = this.readThrough;
|
|
1829
|
+
if (observer === null) {
|
|
1830
|
+
page = await network();
|
|
1831
|
+
} else {
|
|
1832
|
+
let outcome = "error";
|
|
1833
|
+
const startedAt = Date.now();
|
|
1834
|
+
try {
|
|
1835
|
+
page = await network();
|
|
1836
|
+
outcome = page.ok ? "found" : "error";
|
|
1837
|
+
} finally {
|
|
1838
|
+
try {
|
|
1839
|
+
observer.observeNetworkRequested({
|
|
1840
|
+
op: "list",
|
|
1841
|
+
space: this.context.session.spaceId,
|
|
1842
|
+
path: listPath,
|
|
1843
|
+
reason: "NETWORK_REQUESTED",
|
|
1844
|
+
outcome,
|
|
1845
|
+
latencyMs: Date.now() - startedAt
|
|
1846
|
+
});
|
|
1847
|
+
} catch {
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1765
1850
|
}
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
let keys = await this.parseResponse(response, options?.raw);
|
|
1777
|
-
keys = keys ?? [];
|
|
1778
|
-
if (options?.removePrefix && listPath) {
|
|
1779
|
-
const prefixWithSlash = listPath.endsWith("/") ? listPath : `${listPath}/`;
|
|
1780
|
-
keys = keys.map(
|
|
1781
|
-
(key) => key.startsWith(prefixWithSlash) ? key.slice(prefixWithSlash.length) : key
|
|
1782
|
-
);
|
|
1851
|
+
} else if (this.readThrough === null) {
|
|
1852
|
+
page = await network();
|
|
1853
|
+
} else {
|
|
1854
|
+
page = await this.readThrough.list({
|
|
1855
|
+
space: this.context.session.spaceId,
|
|
1856
|
+
listPath,
|
|
1857
|
+
options,
|
|
1858
|
+
signal: request.signal,
|
|
1859
|
+
network
|
|
1860
|
+
});
|
|
1783
1861
|
}
|
|
1784
|
-
return ok(
|
|
1785
|
-
keys,
|
|
1786
|
-
...response.headers.get("x-tinycloud-truncated") === null ? {} : { truncated: response.headers.get("x-tinycloud-truncated") === "true" },
|
|
1787
|
-
...response.headers.get("x-tinycloud-next-cursor") === null ? {} : { nextCursor: response.headers.get("x-tinycloud-next-cursor") ?? void 0 }
|
|
1788
|
-
});
|
|
1862
|
+
return page.ok ? this.finishList(page.data, listPath, options?.removePrefix) : page;
|
|
1789
1863
|
} catch (error) {
|
|
1790
1864
|
return err(wrapError("kv", error));
|
|
1791
1865
|
} finally {
|
|
@@ -1793,6 +1867,56 @@ var KVService = class extends BaseService {
|
|
|
1793
1867
|
}
|
|
1794
1868
|
});
|
|
1795
1869
|
}
|
|
1870
|
+
async listFromNetwork(listPath, options, signal) {
|
|
1871
|
+
const response = await this.invokeOperation(
|
|
1872
|
+
listPath,
|
|
1873
|
+
KVAction.LIST,
|
|
1874
|
+
void 0,
|
|
1875
|
+
signal,
|
|
1876
|
+
options?.limit === void 0 && options?.cursor === void 0 ? void 0 : {
|
|
1877
|
+
...options?.limit === void 0 ? {} : { "x-tinycloud-limit": String(options.limit) },
|
|
1878
|
+
...options?.cursor === void 0 ? {} : { "x-tinycloud-cursor": options.cursor }
|
|
1879
|
+
}
|
|
1880
|
+
);
|
|
1881
|
+
if (!response.ok) {
|
|
1882
|
+
if (response.status === 401 || response.status === 403) {
|
|
1883
|
+
return this.authorizationFailure(
|
|
1884
|
+
"Failed to list keys",
|
|
1885
|
+
response,
|
|
1886
|
+
await this.readAuthorizationText(response),
|
|
1887
|
+
[listPath],
|
|
1888
|
+
KVAction.LIST
|
|
1889
|
+
);
|
|
1890
|
+
}
|
|
1891
|
+
const errorText = await response.text();
|
|
1892
|
+
return err(serviceError(
|
|
1893
|
+
ErrorCodes.NETWORK_ERROR,
|
|
1894
|
+
`Failed to list keys: ${response.status} - ${errorText}`,
|
|
1895
|
+
"kv",
|
|
1896
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1897
|
+
));
|
|
1898
|
+
}
|
|
1899
|
+
const keys = await this.parseResponse(response, options?.raw) ?? [];
|
|
1900
|
+
return ok({
|
|
1901
|
+
keys,
|
|
1902
|
+
...response.headers.get("x-tinycloud-truncated") === null ? {} : { truncated: response.headers.get("x-tinycloud-truncated") === "true" },
|
|
1903
|
+
...response.headers.get("x-tinycloud-next-cursor") === null ? {} : { nextCursor: response.headers.get("x-tinycloud-next-cursor") ?? void 0 }
|
|
1904
|
+
});
|
|
1905
|
+
}
|
|
1906
|
+
finishList(page, listPath, removePrefix) {
|
|
1907
|
+
let keys = page.keys;
|
|
1908
|
+
if (removePrefix && listPath) {
|
|
1909
|
+
const prefixWithSlash = listPath.endsWith("/") ? listPath : `${listPath}/`;
|
|
1910
|
+
keys = keys.map(
|
|
1911
|
+
(key) => key.startsWith(prefixWithSlash) ? key.slice(prefixWithSlash.length) : key
|
|
1912
|
+
);
|
|
1913
|
+
}
|
|
1914
|
+
return ok({
|
|
1915
|
+
keys,
|
|
1916
|
+
...page.truncated === void 0 ? {} : { truncated: page.truncated },
|
|
1917
|
+
...page.nextCursor === void 0 ? {} : { nextCursor: page.nextCursor }
|
|
1918
|
+
});
|
|
1919
|
+
}
|
|
1796
1920
|
/**
|
|
1797
1921
|
* Read one page of the `tinycloud.kv/sync` change feed for a prefix.
|
|
1798
1922
|
*/
|
|
@@ -1815,6 +1939,7 @@ var KVService = class extends BaseService {
|
|
|
1815
1939
|
"kv"
|
|
1816
1940
|
));
|
|
1817
1941
|
}
|
|
1942
|
+
const requestPrefix = options.prefix === "/" ? "" : options.prefix;
|
|
1818
1943
|
const headers = {};
|
|
1819
1944
|
if (options.limit !== void 0) headers["x-tinycloud-limit"] = String(options.limit);
|
|
1820
1945
|
if (options.cursor !== void 0) headers["x-tinycloud-cursor"] = options.cursor;
|
|
@@ -1824,7 +1949,7 @@ var KVService = class extends BaseService {
|
|
|
1824
1949
|
const request = this.createRequestSignal(options.signal, options.timeout);
|
|
1825
1950
|
try {
|
|
1826
1951
|
const response = await this.invokeOperation(
|
|
1827
|
-
|
|
1952
|
+
requestPrefix,
|
|
1828
1953
|
KVAction.SYNC,
|
|
1829
1954
|
void 0,
|
|
1830
1955
|
request.signal,
|
|
@@ -1840,7 +1965,7 @@ var KVService = class extends BaseService {
|
|
|
1840
1965
|
} catch {
|
|
1841
1966
|
body = void 0;
|
|
1842
1967
|
}
|
|
1843
|
-
const page = this.normalizeChangesResponse(body);
|
|
1968
|
+
const page = this.normalizeChangesResponse(body, options.prefix);
|
|
1844
1969
|
if (!page) {
|
|
1845
1970
|
return err(serviceError(
|
|
1846
1971
|
ErrorCodes.NETWORK_ERROR,
|
|
@@ -1920,7 +2045,7 @@ var KVService = class extends BaseService {
|
|
|
1920
2045
|
{ meta }
|
|
1921
2046
|
));
|
|
1922
2047
|
}
|
|
1923
|
-
normalizeChangesResponse(data) {
|
|
2048
|
+
normalizeChangesResponse(data, requestedPrefix) {
|
|
1924
2049
|
if (!isJsonObject(data)) return void 0;
|
|
1925
2050
|
const { changes, more, cursor, source, authority } = data;
|
|
1926
2051
|
if (!Array.isArray(changes) || typeof more !== "boolean" || typeof cursor !== "string" || !isJsonObject(source) || typeof source.nodeDid !== "string" || typeof source.space !== "string" || typeof source.prefix !== "string" || !isJsonObject(authority)) {
|
|
@@ -1952,7 +2077,11 @@ var KVService = class extends BaseService {
|
|
|
1952
2077
|
changes: parsed,
|
|
1953
2078
|
more,
|
|
1954
2079
|
cursor,
|
|
1955
|
-
source: {
|
|
2080
|
+
source: {
|
|
2081
|
+
nodeDid: source.nodeDid,
|
|
2082
|
+
space: source.space,
|
|
2083
|
+
prefix: requestedPrefix === "/" && source.prefix === "" ? "/" : source.prefix
|
|
2084
|
+
},
|
|
1956
2085
|
authority: { notBefore, expiresAt, retainUntil }
|
|
1957
2086
|
};
|
|
1958
2087
|
}
|
|
@@ -1961,61 +2090,22 @@ var KVService = class extends BaseService {
|
|
|
1961
2090
|
*/
|
|
1962
2091
|
async delete(key, options) {
|
|
1963
2092
|
return this.withTelemetry("delete", key, async () => {
|
|
2093
|
+
if (this.readThroughReady !== null) await this.readThroughReady;
|
|
1964
2094
|
if (!this.requireAuth()) {
|
|
1965
2095
|
return err(authRequiredError("kv"));
|
|
1966
2096
|
}
|
|
1967
2097
|
const path = this.getFullPath(key, options?.prefix);
|
|
1968
2098
|
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
1969
2099
|
try {
|
|
1970
|
-
const
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
return this.authorizationFailure(
|
|
1980
|
-
`Failed to delete key ${JSON.stringify(key)}`,
|
|
1981
|
-
response,
|
|
1982
|
-
await this.readAuthorizationText(response),
|
|
1983
|
-
[path],
|
|
1984
|
-
KVAction.DELETE
|
|
1985
|
-
);
|
|
1986
|
-
}
|
|
1987
|
-
if (response.status === 404) {
|
|
1988
|
-
return await this.classifyNotFound(response, key);
|
|
1989
|
-
}
|
|
1990
|
-
const errorText = await response.text();
|
|
1991
|
-
if (response.status === 412) {
|
|
1992
|
-
return err(serviceError(
|
|
1993
|
-
ErrorCodes.KV_PRECONDITION_FAILED,
|
|
1994
|
-
`KV precondition failed for key ${JSON.stringify(key)}`,
|
|
1995
|
-
"kv",
|
|
1996
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1997
|
-
));
|
|
1998
|
-
}
|
|
1999
|
-
if (response.status === 503 && options?.ifMatch !== void 0) {
|
|
2000
|
-
return err(serviceError(
|
|
2001
|
-
ErrorCodes.KV_CONFLICT,
|
|
2002
|
-
`Concurrent KV delete conflicted for key ${JSON.stringify(key)}`,
|
|
2003
|
-
"kv",
|
|
2004
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2005
|
-
));
|
|
2006
|
-
}
|
|
2007
|
-
return err(
|
|
2008
|
-
serviceError(
|
|
2009
|
-
ErrorCodes.NETWORK_ERROR,
|
|
2010
|
-
`Failed to delete key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
|
|
2011
|
-
"kv",
|
|
2012
|
-
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2013
|
-
)
|
|
2014
|
-
);
|
|
2015
|
-
}
|
|
2016
|
-
return ok({
|
|
2017
|
-
data: void 0,
|
|
2018
|
-
headers: this.createResponseHeaders(response.headers)
|
|
2100
|
+
const network = () => this.deleteFromNetwork(key, path, options, request.signal);
|
|
2101
|
+
if (this.readThrough === null) return await network();
|
|
2102
|
+
return await this.readThrough.write({
|
|
2103
|
+
op: "delete",
|
|
2104
|
+
space: this.context.session.spaceId,
|
|
2105
|
+
entries: [{ path }],
|
|
2106
|
+
signal: request.signal,
|
|
2107
|
+
conditional: options?.ifMatch !== void 0,
|
|
2108
|
+
network
|
|
2019
2109
|
});
|
|
2020
2110
|
} catch (error) {
|
|
2021
2111
|
return err(wrapError("kv", error));
|
|
@@ -2024,6 +2114,54 @@ var KVService = class extends BaseService {
|
|
|
2024
2114
|
}
|
|
2025
2115
|
});
|
|
2026
2116
|
}
|
|
2117
|
+
async deleteFromNetwork(key, path, options, signal) {
|
|
2118
|
+
const response = await this.invokeOperation(
|
|
2119
|
+
path,
|
|
2120
|
+
KVAction.DELETE,
|
|
2121
|
+
void 0,
|
|
2122
|
+
signal,
|
|
2123
|
+
options?.ifMatch === void 0 ? void 0 : { "if-match": options.ifMatch }
|
|
2124
|
+
);
|
|
2125
|
+
if (!response.ok) {
|
|
2126
|
+
if (response.status === 401 || response.status === 403) {
|
|
2127
|
+
return this.authorizationFailure(
|
|
2128
|
+
`Failed to delete key ${JSON.stringify(key)}`,
|
|
2129
|
+
response,
|
|
2130
|
+
await this.readAuthorizationText(response),
|
|
2131
|
+
[path],
|
|
2132
|
+
KVAction.DELETE
|
|
2133
|
+
);
|
|
2134
|
+
}
|
|
2135
|
+
if (response.status === 404) return this.classifyNotFound(response, key);
|
|
2136
|
+
const errorText = await response.text();
|
|
2137
|
+
if (response.status === 412) {
|
|
2138
|
+
return err(serviceError(
|
|
2139
|
+
ErrorCodes.KV_PRECONDITION_FAILED,
|
|
2140
|
+
`KV precondition failed for key ${JSON.stringify(key)}`,
|
|
2141
|
+
"kv",
|
|
2142
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2143
|
+
));
|
|
2144
|
+
}
|
|
2145
|
+
if (response.status === 503 && options?.ifMatch !== void 0) {
|
|
2146
|
+
return err(serviceError(
|
|
2147
|
+
ErrorCodes.KV_CONFLICT,
|
|
2148
|
+
`Concurrent KV delete conflicted for key ${JSON.stringify(key)}`,
|
|
2149
|
+
"kv",
|
|
2150
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2151
|
+
));
|
|
2152
|
+
}
|
|
2153
|
+
return err(serviceError(
|
|
2154
|
+
ErrorCodes.NETWORK_ERROR,
|
|
2155
|
+
`Failed to delete key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
|
|
2156
|
+
"kv",
|
|
2157
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
2158
|
+
));
|
|
2159
|
+
}
|
|
2160
|
+
return ok({
|
|
2161
|
+
data: void 0,
|
|
2162
|
+
headers: this.createResponseHeaders(response.headers)
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2027
2165
|
/**
|
|
2028
2166
|
* Get metadata for a key without retrieving the value.
|
|
2029
2167
|
*/
|
|
@@ -2190,11 +2328,75 @@ var KVService = class extends BaseService {
|
|
|
2190
2328
|
* Service identifier for registration.
|
|
2191
2329
|
*/
|
|
2192
2330
|
KVService.serviceName = "kv";
|
|
2331
|
+
|
|
2332
|
+
// src/kv/replication/constants.ts
|
|
2333
|
+
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.";
|
|
2334
|
+
|
|
2335
|
+
// src/kv/replication/identity.ts
|
|
2336
|
+
var SPACE_ID = /^(tinycloud:pkh:eip155:\d+:)([^:]+)(:.+)$/;
|
|
2337
|
+
var PRINCIPAL_DID = /^(did:pkh:eip155:\d+:)([^:]+)$/;
|
|
2338
|
+
function canonicalReplicationIdentity(raw) {
|
|
2339
|
+
if (!raw || typeof raw.host !== "string" || typeof raw.space !== "string" || typeof raw.principal !== "string") {
|
|
2340
|
+
throw new TypeError(
|
|
2341
|
+
"Replication identity host, space, and principal must be strings"
|
|
2342
|
+
);
|
|
2343
|
+
}
|
|
2344
|
+
const authority = /^https?:\/\/([^/?#]*)/i.exec(raw.host)?.[1];
|
|
2345
|
+
let url;
|
|
2346
|
+
try {
|
|
2347
|
+
url = new URL(raw.host);
|
|
2348
|
+
} catch {
|
|
2349
|
+
throw new TypeError(
|
|
2350
|
+
"Replication identity host must be an absolute HTTP(S) URL"
|
|
2351
|
+
);
|
|
2352
|
+
}
|
|
2353
|
+
if (!authority || authority.includes("@") || raw.host.includes("?") || raw.host.includes("#") || url.username || url.password) {
|
|
2354
|
+
throw new TypeError(
|
|
2355
|
+
"Replication identity host must be HTTP(S) without credentials, query, or fragment"
|
|
2356
|
+
);
|
|
2357
|
+
}
|
|
2358
|
+
const space = SPACE_ID.exec(raw.space);
|
|
2359
|
+
const principal = PRINCIPAL_DID.exec(raw.principal);
|
|
2360
|
+
if (!space || !principal) {
|
|
2361
|
+
throw new TypeError(
|
|
2362
|
+
"Replication identity requires a full TinyCloud space id and a pkh principal DID"
|
|
2363
|
+
);
|
|
2364
|
+
}
|
|
2365
|
+
return {
|
|
2366
|
+
host: `${url.origin}${url.pathname.replace(/\/+$/, "")}`,
|
|
2367
|
+
space: `${space[1]}${space[2].toLowerCase()}${space[3]}`,
|
|
2368
|
+
principal: `${principal[1]}${principal[2].toLowerCase()}`
|
|
2369
|
+
};
|
|
2370
|
+
}
|
|
2371
|
+
function replicationIdentityKey(id) {
|
|
2372
|
+
const canonical = canonicalReplicationIdentity(id);
|
|
2373
|
+
return `tc-replication/v1
|
|
2374
|
+
${canonical.host}
|
|
2375
|
+
${canonical.space}
|
|
2376
|
+
${canonical.principal}`;
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
// src/kv/replication/prefix.ts
|
|
2380
|
+
function kvPrefixCovers(prefix, key) {
|
|
2381
|
+
return prefix === "/" || key.startsWith(prefix);
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
// src/kv/replication/scope.ts
|
|
2385
|
+
function requiresSecretsOptIn(space, prefix) {
|
|
2386
|
+
if (prefix === "/") return true;
|
|
2387
|
+
if (space === "secrets" || space.endsWith(":secrets")) return true;
|
|
2388
|
+
return prefix.split("/", 1)[0] === "vault";
|
|
2389
|
+
}
|
|
2193
2390
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2194
2391
|
0 && (module.exports = {
|
|
2392
|
+
CLEAR_PENDING_WARNING,
|
|
2195
2393
|
DEFAULT_SIGNED_READ_URL_EXPIRY_MS,
|
|
2196
2394
|
KVAction,
|
|
2197
2395
|
KVService,
|
|
2198
|
-
PrefixedKVService
|
|
2396
|
+
PrefixedKVService,
|
|
2397
|
+
canonicalReplicationIdentity,
|
|
2398
|
+
kvPrefixCovers,
|
|
2399
|
+
replicationIdentityKey,
|
|
2400
|
+
requiresSecretsOptIn
|
|
2199
2401
|
});
|
|
2200
2402
|
//# sourceMappingURL=index.cjs.map
|