@tinycloud/cli 1.1.0-beta.14 → 1.1.0-beta.15
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/legacy-entry.js +205 -1
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +4 -4
package/dist/legacy-entry.js
CHANGED
|
@@ -1977,6 +1977,8 @@ var CAPABILITIES = [
|
|
|
1977
1977
|
{ urn: "tinycloud.kv/put", service: "tinycloud.kv", status: "active" },
|
|
1978
1978
|
{ urn: "tinycloud.kv/del", service: "tinycloud.kv", status: "active" },
|
|
1979
1979
|
{ urn: "tinycloud.kv/delete", service: "tinycloud.kv", status: "deprecated-alias", aliasOf: "tinycloud.kv/del" },
|
|
1980
|
+
{ urn: "tinycloud.kv/sync", service: "tinycloud.kv", status: "active" },
|
|
1981
|
+
{ urn: "tinycloud.kv/retain", service: "tinycloud.kv", status: "active" },
|
|
1980
1982
|
{ urn: "tinycloud.sql/read", service: "tinycloud.sql", status: "active" },
|
|
1981
1983
|
{ urn: "tinycloud.sql/select", service: "tinycloud.sql", status: "deprecated-alias", aliasOf: "tinycloud.sql/read" },
|
|
1982
1984
|
{ urn: "tinycloud.sql/write", service: "tinycloud.sql", status: "active" },
|
|
@@ -6556,6 +6558,10 @@ var ErrorCodes = {
|
|
|
6556
6558
|
AUTH_EXPIRED: "AUTH_EXPIRED",
|
|
6557
6559
|
AUTH_REQUIRED: "AUTH_REQUIRED",
|
|
6558
6560
|
AUTH_UNAUTHORIZED: "AUTH_UNAUTHORIZED",
|
|
6561
|
+
/** HTTP 401: the delegation the invocation cites was revoked. */
|
|
6562
|
+
AUTH_DELEGATION_REVOKED: "AUTH_DELEGATION_REVOKED",
|
|
6563
|
+
/** HTTP 401: a delegation the cited one rests on was revoked. */
|
|
6564
|
+
AUTH_DELEGATION_ANCESTOR_REVOKED: "AUTH_DELEGATION_ANCESTOR_REVOKED",
|
|
6559
6565
|
NETWORK_ERROR: "NETWORK_ERROR",
|
|
6560
6566
|
TIMEOUT: "TIMEOUT",
|
|
6561
6567
|
ABORTED: "ABORTED",
|
|
@@ -6567,6 +6573,10 @@ var ErrorCodes = {
|
|
|
6567
6573
|
KV_PRECONDITION_FAILED: "KV_PRECONDITION_FAILED",
|
|
6568
6574
|
KV_CONFLICT: "KV_CONFLICT",
|
|
6569
6575
|
KV_RESPONSE_TOO_LARGE: "KV_RESPONSE_TOO_LARGE",
|
|
6576
|
+
/** HTTP 410 from the change feed: discard sync state and restart without a cursor; `meta.reason` says why. */
|
|
6577
|
+
KV_SYNC_RESET_REQUIRED: "KV_SYNC_RESET_REQUIRED",
|
|
6578
|
+
/** HTTP 403 from the change feed: the retention grant was refused; `meta.reason` says why. */
|
|
6579
|
+
KV_RETENTION_GRANT_REFUSED: "KV_RETENTION_GRANT_REFUSED",
|
|
6570
6580
|
// SQL-specific errors
|
|
6571
6581
|
SQL_ERROR: "SQL_ERROR",
|
|
6572
6582
|
SQL_PERMISSION_DENIED: "SQL_PERMISSION_DENIED",
|
|
@@ -7627,6 +7637,30 @@ var PrefixedKVService = class _PrefixedKVService {
|
|
|
7627
7637
|
removePrefix
|
|
7628
7638
|
});
|
|
7629
7639
|
}
|
|
7640
|
+
/**
|
|
7641
|
+
* Read the change feed for everything under this prefix. A view with an
|
|
7642
|
+
* empty prefix (`""` or `"/"`) is refused: the node does not serve a
|
|
7643
|
+
* whole-space feed.
|
|
7644
|
+
*/
|
|
7645
|
+
async changes(options) {
|
|
7646
|
+
if (this._prefix.replace(/\/+$/, "") === "") {
|
|
7647
|
+
return err(serviceError(
|
|
7648
|
+
ErrorCodes.INVALID_INPUT,
|
|
7649
|
+
"KV changes needs a non-empty prefix; this view has none",
|
|
7650
|
+
"kv"
|
|
7651
|
+
));
|
|
7652
|
+
}
|
|
7653
|
+
const syncPrefix = `${this._prefix}/`;
|
|
7654
|
+
const response = await this._kv.changes({ ...options, prefix: syncPrefix });
|
|
7655
|
+
if (!response.ok) return response;
|
|
7656
|
+
return ok({
|
|
7657
|
+
...response.data,
|
|
7658
|
+
changes: response.data.changes.map((change) => ({
|
|
7659
|
+
...change,
|
|
7660
|
+
key: change.key.startsWith(syncPrefix) ? change.key.slice(syncPrefix.length) : change.key
|
|
7661
|
+
}))
|
|
7662
|
+
});
|
|
7663
|
+
}
|
|
7630
7664
|
/**
|
|
7631
7665
|
* Delete a key.
|
|
7632
7666
|
*/
|
|
@@ -7677,9 +7711,16 @@ var KVAction = {
|
|
|
7677
7711
|
PUT: "tinycloud.kv/put",
|
|
7678
7712
|
LIST: "tinycloud.kv/list",
|
|
7679
7713
|
DELETE: "tinycloud.kv/del",
|
|
7680
|
-
HEAD: "tinycloud.kv/metadata"
|
|
7714
|
+
HEAD: "tinycloud.kv/metadata",
|
|
7715
|
+
/** The change feed; never implied by `*` or `tinycloud.kv/*`. */
|
|
7716
|
+
SYNC: "tinycloud.kv/sync",
|
|
7717
|
+
/** Retention attestation; presented through `retentionGrant`, never invoked or implied. */
|
|
7718
|
+
RETAIN: "tinycloud.kv/retain"
|
|
7681
7719
|
};
|
|
7682
7720
|
var MAX_KV_BATCH_READ_ITEMS = 100;
|
|
7721
|
+
function isJsonObject(value) {
|
|
7722
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7723
|
+
}
|
|
7683
7724
|
function encodeKvBatchPartName(path) {
|
|
7684
7725
|
return encodeURIComponent(path).replace(
|
|
7685
7726
|
/[!'()*]/g,
|
|
@@ -8598,6 +8639,169 @@ var KVService = class extends BaseService {
|
|
|
8598
8639
|
}
|
|
8599
8640
|
});
|
|
8600
8641
|
}
|
|
8642
|
+
/**
|
|
8643
|
+
* Read one page of the `tinycloud.kv/sync` change feed for a prefix.
|
|
8644
|
+
*/
|
|
8645
|
+
async changes(options) {
|
|
8646
|
+
return this.withTelemetry("changes", options?.prefix, async () => {
|
|
8647
|
+
if (!this.requireAuth()) {
|
|
8648
|
+
return err(authRequiredError("kv"));
|
|
8649
|
+
}
|
|
8650
|
+
if (typeof options?.prefix !== "string" || options.prefix.length === 0) {
|
|
8651
|
+
return err(serviceError(
|
|
8652
|
+
ErrorCodes.INVALID_INPUT,
|
|
8653
|
+
"KV changes prefix must be a non-empty string",
|
|
8654
|
+
"kv"
|
|
8655
|
+
));
|
|
8656
|
+
}
|
|
8657
|
+
if (options.limit !== void 0 && (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 1e3)) {
|
|
8658
|
+
return err(serviceError(
|
|
8659
|
+
ErrorCodes.INVALID_INPUT,
|
|
8660
|
+
"KV changes limit must be an integer from 1 through 1000",
|
|
8661
|
+
"kv"
|
|
8662
|
+
));
|
|
8663
|
+
}
|
|
8664
|
+
const headers = {};
|
|
8665
|
+
if (options.limit !== void 0) headers["x-tinycloud-limit"] = String(options.limit);
|
|
8666
|
+
if (options.cursor !== void 0) headers["x-tinycloud-cursor"] = options.cursor;
|
|
8667
|
+
if (options.retentionGrant !== void 0) {
|
|
8668
|
+
headers["x-tinycloud-retention-grant"] = options.retentionGrant;
|
|
8669
|
+
}
|
|
8670
|
+
const request = this.createRequestSignal(options.signal, options.timeout);
|
|
8671
|
+
try {
|
|
8672
|
+
const response = await this.invokeOperation(
|
|
8673
|
+
options.prefix,
|
|
8674
|
+
KVAction.SYNC,
|
|
8675
|
+
void 0,
|
|
8676
|
+
request.signal,
|
|
8677
|
+
headers
|
|
8678
|
+
);
|
|
8679
|
+
if (!response.ok) {
|
|
8680
|
+
return await this.changesFailure(response, options.prefix, request.signal);
|
|
8681
|
+
}
|
|
8682
|
+
const text = await response.text();
|
|
8683
|
+
let body;
|
|
8684
|
+
try {
|
|
8685
|
+
body = JSON.parse(text);
|
|
8686
|
+
} catch {
|
|
8687
|
+
body = void 0;
|
|
8688
|
+
}
|
|
8689
|
+
const page = this.normalizeChangesResponse(body);
|
|
8690
|
+
if (!page) {
|
|
8691
|
+
return err(serviceError(
|
|
8692
|
+
ErrorCodes.NETWORK_ERROR,
|
|
8693
|
+
"KV changes response was not a tinycloud.kv/sync page",
|
|
8694
|
+
"kv",
|
|
8695
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
8696
|
+
));
|
|
8697
|
+
}
|
|
8698
|
+
return ok(page);
|
|
8699
|
+
} catch (error) {
|
|
8700
|
+
return err(wrapError2("kv", error));
|
|
8701
|
+
} finally {
|
|
8702
|
+
request.dispose();
|
|
8703
|
+
}
|
|
8704
|
+
});
|
|
8705
|
+
}
|
|
8706
|
+
/**
|
|
8707
|
+
* Map a failed `kv/sync` response. 410 and the retention 403 carry
|
|
8708
|
+
* `{"error":{"code","reason"}}`; a revoked grant is a 401 whose text names
|
|
8709
|
+
* `delegation-revoked` or `delegation-ancestor-revoked`. A body read that
|
|
8710
|
+
* fails because the request was cancelled or timed out rethrows, so the
|
|
8711
|
+
* caller sees `ABORTED`/`TIMEOUT` rather than a mapped status.
|
|
8712
|
+
*/
|
|
8713
|
+
async changesFailure(response, prefix, signal) {
|
|
8714
|
+
if (response.status === 404) {
|
|
8715
|
+
return this.classifyNotFound(response, prefix);
|
|
8716
|
+
}
|
|
8717
|
+
let errorText;
|
|
8718
|
+
try {
|
|
8719
|
+
errorText = await response.text();
|
|
8720
|
+
} catch (error) {
|
|
8721
|
+
if (signal.aborted) throw error;
|
|
8722
|
+
errorText = "";
|
|
8723
|
+
}
|
|
8724
|
+
const meta = { status: response.status, statusText: response.statusText };
|
|
8725
|
+
let reason;
|
|
8726
|
+
let bodyCode;
|
|
8727
|
+
try {
|
|
8728
|
+
const body = JSON.parse(errorText);
|
|
8729
|
+
const error = isJsonObject(body) ? body.error : void 0;
|
|
8730
|
+
if (isJsonObject(error)) {
|
|
8731
|
+
if (typeof error.code === "string") bodyCode = error.code;
|
|
8732
|
+
if (typeof error.reason === "string") reason = error.reason;
|
|
8733
|
+
}
|
|
8734
|
+
} catch {
|
|
8735
|
+
}
|
|
8736
|
+
const context = `Failed to read KV changes for ${JSON.stringify(prefix)}`;
|
|
8737
|
+
if (response.status === 410) {
|
|
8738
|
+
return err(serviceError(
|
|
8739
|
+
ErrorCodes.KV_SYNC_RESET_REQUIRED,
|
|
8740
|
+
`${context}: the node requires a reset (${reason ?? "reason not given"}); discard sync state and restart without a cursor`,
|
|
8741
|
+
"kv",
|
|
8742
|
+
{ meta: { ...meta, reason } }
|
|
8743
|
+
));
|
|
8744
|
+
}
|
|
8745
|
+
if (response.status === 403 && bodyCode === "RETENTION_GRANT_REFUSED") {
|
|
8746
|
+
return err(serviceError(
|
|
8747
|
+
ErrorCodes.KV_RETENTION_GRANT_REFUSED,
|
|
8748
|
+
`${context}: the retention grant was refused (${reason ?? "reason not given"})`,
|
|
8749
|
+
"kv",
|
|
8750
|
+
{ meta: { ...meta, reason } }
|
|
8751
|
+
));
|
|
8752
|
+
}
|
|
8753
|
+
if (response.status === 401) {
|
|
8754
|
+
const revoked = /\bdelegation-ancestor-revoked\b/.test(errorText) ? ErrorCodes.AUTH_DELEGATION_ANCESTOR_REVOKED : /\bdelegation-revoked\b/.test(errorText) ? ErrorCodes.AUTH_DELEGATION_REVOKED : void 0;
|
|
8755
|
+
if (revoked !== void 0) {
|
|
8756
|
+
return err(serviceError(revoked, `${context}: 401 - ${errorText}`, "kv", { meta }));
|
|
8757
|
+
}
|
|
8758
|
+
}
|
|
8759
|
+
if (response.status === 401 || response.status === 403) {
|
|
8760
|
+
return this.authorizationFailure(context, response, errorText, [prefix], KVAction.SYNC);
|
|
8761
|
+
}
|
|
8762
|
+
return err(serviceError(
|
|
8763
|
+
response.status === 400 ? ErrorCodes.INVALID_INPUT : ErrorCodes.NETWORK_ERROR,
|
|
8764
|
+
`${context}: ${response.status} - ${errorText}`,
|
|
8765
|
+
"kv",
|
|
8766
|
+
{ meta }
|
|
8767
|
+
));
|
|
8768
|
+
}
|
|
8769
|
+
normalizeChangesResponse(data) {
|
|
8770
|
+
if (!isJsonObject(data)) return void 0;
|
|
8771
|
+
const { changes, more, cursor, source, authority } = data;
|
|
8772
|
+
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)) {
|
|
8773
|
+
return void 0;
|
|
8774
|
+
}
|
|
8775
|
+
const { notBefore, expiresAt, retainUntil } = authority;
|
|
8776
|
+
if (notBefore !== null && typeof notBefore !== "string" || expiresAt !== null && typeof expiresAt !== "string" || retainUntil !== null && typeof retainUntil !== "string") {
|
|
8777
|
+
return void 0;
|
|
8778
|
+
}
|
|
8779
|
+
const parsed = [];
|
|
8780
|
+
for (const change of changes) {
|
|
8781
|
+
if (!isJsonObject(change) || typeof change.key !== "string") return void 0;
|
|
8782
|
+
if (change.deleted === true) {
|
|
8783
|
+
parsed.push({ key: change.key, deleted: true });
|
|
8784
|
+
continue;
|
|
8785
|
+
}
|
|
8786
|
+
const { metadata } = change;
|
|
8787
|
+
if (change.deleted !== false || typeof change.etag !== "string" || !isJsonObject(metadata) || !Object.values(metadata).every((value) => typeof value === "string")) {
|
|
8788
|
+
return void 0;
|
|
8789
|
+
}
|
|
8790
|
+
parsed.push({
|
|
8791
|
+
key: change.key,
|
|
8792
|
+
deleted: false,
|
|
8793
|
+
etag: change.etag,
|
|
8794
|
+
metadata
|
|
8795
|
+
});
|
|
8796
|
+
}
|
|
8797
|
+
return {
|
|
8798
|
+
changes: parsed,
|
|
8799
|
+
more,
|
|
8800
|
+
cursor,
|
|
8801
|
+
source: { nodeDid: source.nodeDid, space: source.space, prefix: source.prefix },
|
|
8802
|
+
authority: { notBefore, expiresAt, retainUntil }
|
|
8803
|
+
};
|
|
8804
|
+
}
|
|
8601
8805
|
/**
|
|
8602
8806
|
* Delete a key.
|
|
8603
8807
|
*/
|