@tinycloud/operations 0.3.0-beta.0 → 0.3.1-beta.0
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/artifacts.cjs +210 -0
- package/dist/artifacts.cjs.map +1 -1
- package/dist/artifacts.js +210 -0
- package/dist/artifacts.js.map +1 -1
- package/dist/cli-runtime.cjs +211 -1
- package/dist/cli-runtime.cjs.map +1 -1
- package/dist/cli-runtime.js +211 -1
- package/dist/cli-runtime.js.map +1 -1
- package/dist/index.cjs +211 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +211 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/artifacts.js
CHANGED
|
@@ -1468,6 +1468,20 @@ var PrefixedKVService = class _PrefixedKVService {
|
|
|
1468
1468
|
const fullKey = this.getFullKey(key);
|
|
1469
1469
|
return this._kv.get(fullKey, { ...options, prefix: "" });
|
|
1470
1470
|
}
|
|
1471
|
+
async batchGet(keys, options) {
|
|
1472
|
+
const response = await this._kv.batchGet(
|
|
1473
|
+
keys.map((key) => this.getFullKey(key)),
|
|
1474
|
+
{ ...options, prefix: "" }
|
|
1475
|
+
);
|
|
1476
|
+
if (!response.ok) return response;
|
|
1477
|
+
return ok({
|
|
1478
|
+
...response.data,
|
|
1479
|
+
results: response.data.results.map((item, index) => ({
|
|
1480
|
+
...item,
|
|
1481
|
+
key: keys[index]
|
|
1482
|
+
}))
|
|
1483
|
+
});
|
|
1484
|
+
}
|
|
1471
1485
|
/**
|
|
1472
1486
|
* Store a value at a key.
|
|
1473
1487
|
*/
|
|
@@ -1512,6 +1526,20 @@ var PrefixedKVService = class _PrefixedKVService {
|
|
|
1512
1526
|
const fullKey = this.getFullKey(key);
|
|
1513
1527
|
return this._kv.head(fullKey, { ...options, prefix: "" });
|
|
1514
1528
|
}
|
|
1529
|
+
async batchHead(keys, options) {
|
|
1530
|
+
const response = await this._kv.batchHead(
|
|
1531
|
+
keys.map((key) => this.getFullKey(key)),
|
|
1532
|
+
{ ...options, prefix: "" }
|
|
1533
|
+
);
|
|
1534
|
+
if (!response.ok) return response;
|
|
1535
|
+
return ok({
|
|
1536
|
+
...response.data,
|
|
1537
|
+
results: response.data.results.map((item, index) => ({
|
|
1538
|
+
...item,
|
|
1539
|
+
key: keys[index]
|
|
1540
|
+
}))
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1515
1543
|
/**
|
|
1516
1544
|
* Create a short-lived signed URL for reading a KV object.
|
|
1517
1545
|
*/
|
|
@@ -1536,6 +1564,7 @@ var KVAction = {
|
|
|
1536
1564
|
DELETE: "tinycloud.kv/del",
|
|
1537
1565
|
HEAD: "tinycloud.kv/metadata"
|
|
1538
1566
|
};
|
|
1567
|
+
var MAX_KV_BATCH_READ_ITEMS = 100;
|
|
1539
1568
|
function encodeKvBatchPartName(path) {
|
|
1540
1569
|
return encodeURIComponent(path).replace(
|
|
1541
1570
|
/[!'()*]/g,
|
|
@@ -1770,6 +1799,173 @@ var KVService = class extends BaseService {
|
|
|
1770
1799
|
get: (name) => headers.get(name)
|
|
1771
1800
|
};
|
|
1772
1801
|
}
|
|
1802
|
+
createBatchResponseHeaders(values) {
|
|
1803
|
+
const normalized = new Map(
|
|
1804
|
+
Object.entries(values).map(([name, value]) => [name.toLowerCase(), value])
|
|
1805
|
+
);
|
|
1806
|
+
return this.createResponseHeaders({
|
|
1807
|
+
get: (name) => normalized.get(name.toLowerCase()) ?? null
|
|
1808
|
+
});
|
|
1809
|
+
}
|
|
1810
|
+
parseBatchValue(dataBase64, headers, raw = false, binary = false) {
|
|
1811
|
+
const encoded = globalThis.atob(dataBase64);
|
|
1812
|
+
const bytes = Uint8Array.from(encoded, (character) => character.charCodeAt(0));
|
|
1813
|
+
if (binary) return bytes;
|
|
1814
|
+
const text = new TextDecoder().decode(bytes);
|
|
1815
|
+
if (raw) return text;
|
|
1816
|
+
const contentType = Object.entries(headers).find(
|
|
1817
|
+
([name]) => name.toLowerCase() === "content-type"
|
|
1818
|
+
)?.[1];
|
|
1819
|
+
return contentType?.includes("application/json") ? JSON.parse(text) : text;
|
|
1820
|
+
}
|
|
1821
|
+
normalizeBatchReadResponse(data, paths, requireData) {
|
|
1822
|
+
if (!data || typeof data !== "object") return void 0;
|
|
1823
|
+
const response = data;
|
|
1824
|
+
if (!Array.isArray(response.results) || response.results.length !== paths.length) {
|
|
1825
|
+
return void 0;
|
|
1826
|
+
}
|
|
1827
|
+
for (let index = 0; index < response.results.length; index++) {
|
|
1828
|
+
const item = response.results[index];
|
|
1829
|
+
if (!item || typeof item !== "object" || item.key !== paths[index] || typeof item.ok !== "boolean") {
|
|
1830
|
+
return void 0;
|
|
1831
|
+
}
|
|
1832
|
+
if (item.ok && (!item.headers || typeof item.headers !== "object" || Object.values(item.headers).some((value) => typeof value !== "string") || requireData && typeof item.dataBase64 !== "string")) {
|
|
1833
|
+
return void 0;
|
|
1834
|
+
}
|
|
1835
|
+
if (!item.ok && (!item.error || typeof item.error.code !== "string" || typeof item.error.message !== "string")) {
|
|
1836
|
+
return void 0;
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
return response;
|
|
1840
|
+
}
|
|
1841
|
+
async batchRead(keys, action, options) {
|
|
1842
|
+
if (!this.requireAuth()) return err(authRequiredError("kv"));
|
|
1843
|
+
if (keys.length === 0) return ok({ results: [], count: 0 });
|
|
1844
|
+
if (keys.length > MAX_KV_BATCH_READ_ITEMS) {
|
|
1845
|
+
return err(serviceError(
|
|
1846
|
+
ErrorCodes.INVALID_INPUT,
|
|
1847
|
+
`KV batch reads accept at most ${MAX_KV_BATCH_READ_ITEMS} keys`,
|
|
1848
|
+
"kv"
|
|
1849
|
+
));
|
|
1850
|
+
}
|
|
1851
|
+
if (keys.length === 1) {
|
|
1852
|
+
const key = keys[0];
|
|
1853
|
+
const result = action === KVAction.GET ? await this.get(key, options) : await this.head(key, options);
|
|
1854
|
+
return ok({
|
|
1855
|
+
results: [{ key, result }],
|
|
1856
|
+
count: 1
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
if (!this.context.invokeAny) {
|
|
1860
|
+
return err(serviceError(
|
|
1861
|
+
ErrorCodes.INVALID_INPUT,
|
|
1862
|
+
"KV batch reads require SDK runtime support for multi-resource invocations",
|
|
1863
|
+
"kv"
|
|
1864
|
+
));
|
|
1865
|
+
}
|
|
1866
|
+
const getOptions = action === KVAction.GET ? options : void 0;
|
|
1867
|
+
if (getOptions?.maxResponseBytes !== void 0 && (!Number.isSafeInteger(getOptions.maxResponseBytes) || getOptions.maxResponseBytes <= 0)) {
|
|
1868
|
+
return err(serviceError(
|
|
1869
|
+
ErrorCodes.INVALID_INPUT,
|
|
1870
|
+
"KV maxResponseBytes must be a positive safe integer",
|
|
1871
|
+
"kv"
|
|
1872
|
+
));
|
|
1873
|
+
}
|
|
1874
|
+
const paths = keys.map((key) => this.getFullPath(key, options?.prefix));
|
|
1875
|
+
if (new Set(paths).size !== paths.length) {
|
|
1876
|
+
return err(serviceError(
|
|
1877
|
+
ErrorCodes.INVALID_INPUT,
|
|
1878
|
+
"KV batch read received duplicate keys after prefix resolution",
|
|
1879
|
+
"kv"
|
|
1880
|
+
));
|
|
1881
|
+
}
|
|
1882
|
+
try {
|
|
1883
|
+
const session = this.context.session;
|
|
1884
|
+
const invocationHeaders = this.context.invokeAny(
|
|
1885
|
+
session,
|
|
1886
|
+
paths.map((path) => ({
|
|
1887
|
+
spaceId: session.spaceId,
|
|
1888
|
+
service: "kv",
|
|
1889
|
+
path,
|
|
1890
|
+
action
|
|
1891
|
+
}))
|
|
1892
|
+
);
|
|
1893
|
+
const limitHeaders = {};
|
|
1894
|
+
if (getOptions?.maxResponseBytes !== void 0) {
|
|
1895
|
+
limitHeaders["x-tinycloud-max-response-bytes"] = String(
|
|
1896
|
+
getOptions.maxResponseBytes
|
|
1897
|
+
);
|
|
1898
|
+
}
|
|
1899
|
+
const headers = Array.isArray(invocationHeaders) ? [...invocationHeaders, ...Object.entries(limitHeaders)] : { ...invocationHeaders, ...limitHeaders };
|
|
1900
|
+
const response = await this.context.fetch(`${this.host}/invoke`, {
|
|
1901
|
+
method: "POST",
|
|
1902
|
+
headers,
|
|
1903
|
+
signal: this.combineSignals(options?.signal)
|
|
1904
|
+
});
|
|
1905
|
+
if (!response.ok) {
|
|
1906
|
+
const errorText = await response.text();
|
|
1907
|
+
if (response.status === 401 || response.status === 403) {
|
|
1908
|
+
const { resource, action: requiredAction } = parseAuthError(errorText);
|
|
1909
|
+
return err(authUnauthorizedError("kv", errorText, {
|
|
1910
|
+
status: response.status,
|
|
1911
|
+
...requiredAction && { requiredAction },
|
|
1912
|
+
...resource && { resource }
|
|
1913
|
+
}));
|
|
1914
|
+
}
|
|
1915
|
+
if (response.status === 413) {
|
|
1916
|
+
return err(serviceError(
|
|
1917
|
+
ErrorCodes.KV_RESPONSE_TOO_LARGE,
|
|
1918
|
+
"A KV batch value exceeds the requested response limit",
|
|
1919
|
+
"kv",
|
|
1920
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1921
|
+
));
|
|
1922
|
+
}
|
|
1923
|
+
return err(serviceError(
|
|
1924
|
+
ErrorCodes.NETWORK_ERROR,
|
|
1925
|
+
`Failed to batch read ${keys.length} key(s): ${response.status} - ${errorText}`,
|
|
1926
|
+
"kv",
|
|
1927
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1928
|
+
));
|
|
1929
|
+
}
|
|
1930
|
+
const payload = this.normalizeBatchReadResponse(
|
|
1931
|
+
await response.json(),
|
|
1932
|
+
paths,
|
|
1933
|
+
action === KVAction.GET
|
|
1934
|
+
);
|
|
1935
|
+
if (!payload) {
|
|
1936
|
+
return err(serviceError(
|
|
1937
|
+
ErrorCodes.NETWORK_ERROR,
|
|
1938
|
+
"KV batch read response did not match the requested keys",
|
|
1939
|
+
"kv"
|
|
1940
|
+
));
|
|
1941
|
+
}
|
|
1942
|
+
const results = payload.results.map((item, index) => {
|
|
1943
|
+
if (!item.ok) {
|
|
1944
|
+
return {
|
|
1945
|
+
key: keys[index],
|
|
1946
|
+
result: err(serviceError(item.error.code, item.error.message, "kv"))
|
|
1947
|
+
};
|
|
1948
|
+
}
|
|
1949
|
+
const headers2 = item.headers;
|
|
1950
|
+
const data = action === KVAction.HEAD ? void 0 : this.parseBatchValue(
|
|
1951
|
+
item.dataBase64,
|
|
1952
|
+
headers2,
|
|
1953
|
+
getOptions?.raw,
|
|
1954
|
+
getOptions?.binary
|
|
1955
|
+
);
|
|
1956
|
+
return {
|
|
1957
|
+
key: keys[index],
|
|
1958
|
+
result: ok({
|
|
1959
|
+
data,
|
|
1960
|
+
headers: this.createBatchResponseHeaders(headers2)
|
|
1961
|
+
})
|
|
1962
|
+
};
|
|
1963
|
+
});
|
|
1964
|
+
return ok({ results, count: results.length });
|
|
1965
|
+
} catch (error) {
|
|
1966
|
+
return err(wrapError("kv", error));
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1773
1969
|
/**
|
|
1774
1970
|
* Parse response body based on content type.
|
|
1775
1971
|
*
|
|
@@ -1916,6 +2112,13 @@ var KVService = class extends BaseService {
|
|
|
1916
2112
|
}
|
|
1917
2113
|
});
|
|
1918
2114
|
}
|
|
2115
|
+
async batchGet(keys, options) {
|
|
2116
|
+
return this.withTelemetry(
|
|
2117
|
+
"batchGet",
|
|
2118
|
+
String(keys.length),
|
|
2119
|
+
() => this.batchRead(keys, KVAction.GET, options)
|
|
2120
|
+
);
|
|
2121
|
+
}
|
|
1919
2122
|
/**
|
|
1920
2123
|
* Store a value at a key.
|
|
1921
2124
|
*/
|
|
@@ -2275,6 +2478,13 @@ var KVService = class extends BaseService {
|
|
|
2275
2478
|
}
|
|
2276
2479
|
});
|
|
2277
2480
|
}
|
|
2481
|
+
async batchHead(keys, options) {
|
|
2482
|
+
return this.withTelemetry(
|
|
2483
|
+
"batchHead",
|
|
2484
|
+
String(keys.length),
|
|
2485
|
+
() => this.batchRead(keys, KVAction.HEAD, options)
|
|
2486
|
+
);
|
|
2487
|
+
}
|
|
2278
2488
|
/**
|
|
2279
2489
|
* Create a short-lived signed URL for reading a KV object.
|
|
2280
2490
|
*/
|