@tinycloud/operations 0.3.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.cjs
CHANGED
|
@@ -1492,6 +1492,20 @@ var PrefixedKVService = class _PrefixedKVService {
|
|
|
1492
1492
|
const fullKey = this.getFullKey(key);
|
|
1493
1493
|
return this._kv.get(fullKey, { ...options, prefix: "" });
|
|
1494
1494
|
}
|
|
1495
|
+
async batchGet(keys, options) {
|
|
1496
|
+
const response = await this._kv.batchGet(
|
|
1497
|
+
keys.map((key) => this.getFullKey(key)),
|
|
1498
|
+
{ ...options, prefix: "" }
|
|
1499
|
+
);
|
|
1500
|
+
if (!response.ok) return response;
|
|
1501
|
+
return ok({
|
|
1502
|
+
...response.data,
|
|
1503
|
+
results: response.data.results.map((item, index) => ({
|
|
1504
|
+
...item,
|
|
1505
|
+
key: keys[index]
|
|
1506
|
+
}))
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1495
1509
|
/**
|
|
1496
1510
|
* Store a value at a key.
|
|
1497
1511
|
*/
|
|
@@ -1536,6 +1550,20 @@ var PrefixedKVService = class _PrefixedKVService {
|
|
|
1536
1550
|
const fullKey = this.getFullKey(key);
|
|
1537
1551
|
return this._kv.head(fullKey, { ...options, prefix: "" });
|
|
1538
1552
|
}
|
|
1553
|
+
async batchHead(keys, options) {
|
|
1554
|
+
const response = await this._kv.batchHead(
|
|
1555
|
+
keys.map((key) => this.getFullKey(key)),
|
|
1556
|
+
{ ...options, prefix: "" }
|
|
1557
|
+
);
|
|
1558
|
+
if (!response.ok) return response;
|
|
1559
|
+
return ok({
|
|
1560
|
+
...response.data,
|
|
1561
|
+
results: response.data.results.map((item, index) => ({
|
|
1562
|
+
...item,
|
|
1563
|
+
key: keys[index]
|
|
1564
|
+
}))
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1539
1567
|
/**
|
|
1540
1568
|
* Create a short-lived signed URL for reading a KV object.
|
|
1541
1569
|
*/
|
|
@@ -1560,6 +1588,7 @@ var KVAction = {
|
|
|
1560
1588
|
DELETE: "tinycloud.kv/del",
|
|
1561
1589
|
HEAD: "tinycloud.kv/metadata"
|
|
1562
1590
|
};
|
|
1591
|
+
var MAX_KV_BATCH_READ_ITEMS = 100;
|
|
1563
1592
|
function encodeKvBatchPartName(path) {
|
|
1564
1593
|
return encodeURIComponent(path).replace(
|
|
1565
1594
|
/[!'()*]/g,
|
|
@@ -1794,6 +1823,173 @@ var KVService = class extends BaseService {
|
|
|
1794
1823
|
get: (name) => headers.get(name)
|
|
1795
1824
|
};
|
|
1796
1825
|
}
|
|
1826
|
+
createBatchResponseHeaders(values) {
|
|
1827
|
+
const normalized = new Map(
|
|
1828
|
+
Object.entries(values).map(([name, value]) => [name.toLowerCase(), value])
|
|
1829
|
+
);
|
|
1830
|
+
return this.createResponseHeaders({
|
|
1831
|
+
get: (name) => normalized.get(name.toLowerCase()) ?? null
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
parseBatchValue(dataBase64, headers, raw = false, binary = false) {
|
|
1835
|
+
const encoded = globalThis.atob(dataBase64);
|
|
1836
|
+
const bytes = Uint8Array.from(encoded, (character) => character.charCodeAt(0));
|
|
1837
|
+
if (binary) return bytes;
|
|
1838
|
+
const text = new TextDecoder().decode(bytes);
|
|
1839
|
+
if (raw) return text;
|
|
1840
|
+
const contentType = Object.entries(headers).find(
|
|
1841
|
+
([name]) => name.toLowerCase() === "content-type"
|
|
1842
|
+
)?.[1];
|
|
1843
|
+
return contentType?.includes("application/json") ? JSON.parse(text) : text;
|
|
1844
|
+
}
|
|
1845
|
+
normalizeBatchReadResponse(data, paths, requireData) {
|
|
1846
|
+
if (!data || typeof data !== "object") return void 0;
|
|
1847
|
+
const response = data;
|
|
1848
|
+
if (!Array.isArray(response.results) || response.results.length !== paths.length) {
|
|
1849
|
+
return void 0;
|
|
1850
|
+
}
|
|
1851
|
+
for (let index = 0; index < response.results.length; index++) {
|
|
1852
|
+
const item = response.results[index];
|
|
1853
|
+
if (!item || typeof item !== "object" || item.key !== paths[index] || typeof item.ok !== "boolean") {
|
|
1854
|
+
return void 0;
|
|
1855
|
+
}
|
|
1856
|
+
if (item.ok && (!item.headers || typeof item.headers !== "object" || Object.values(item.headers).some((value) => typeof value !== "string") || requireData && typeof item.dataBase64 !== "string")) {
|
|
1857
|
+
return void 0;
|
|
1858
|
+
}
|
|
1859
|
+
if (!item.ok && (!item.error || typeof item.error.code !== "string" || typeof item.error.message !== "string")) {
|
|
1860
|
+
return void 0;
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
return response;
|
|
1864
|
+
}
|
|
1865
|
+
async batchRead(keys, action, options) {
|
|
1866
|
+
if (!this.requireAuth()) return err(authRequiredError("kv"));
|
|
1867
|
+
if (keys.length === 0) return ok({ results: [], count: 0 });
|
|
1868
|
+
if (keys.length > MAX_KV_BATCH_READ_ITEMS) {
|
|
1869
|
+
return err(serviceError(
|
|
1870
|
+
ErrorCodes.INVALID_INPUT,
|
|
1871
|
+
`KV batch reads accept at most ${MAX_KV_BATCH_READ_ITEMS} keys`,
|
|
1872
|
+
"kv"
|
|
1873
|
+
));
|
|
1874
|
+
}
|
|
1875
|
+
if (keys.length === 1) {
|
|
1876
|
+
const key = keys[0];
|
|
1877
|
+
const result = action === KVAction.GET ? await this.get(key, options) : await this.head(key, options);
|
|
1878
|
+
return ok({
|
|
1879
|
+
results: [{ key, result }],
|
|
1880
|
+
count: 1
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
if (!this.context.invokeAny) {
|
|
1884
|
+
return err(serviceError(
|
|
1885
|
+
ErrorCodes.INVALID_INPUT,
|
|
1886
|
+
"KV batch reads require SDK runtime support for multi-resource invocations",
|
|
1887
|
+
"kv"
|
|
1888
|
+
));
|
|
1889
|
+
}
|
|
1890
|
+
const getOptions = action === KVAction.GET ? options : void 0;
|
|
1891
|
+
if (getOptions?.maxResponseBytes !== void 0 && (!Number.isSafeInteger(getOptions.maxResponseBytes) || getOptions.maxResponseBytes <= 0)) {
|
|
1892
|
+
return err(serviceError(
|
|
1893
|
+
ErrorCodes.INVALID_INPUT,
|
|
1894
|
+
"KV maxResponseBytes must be a positive safe integer",
|
|
1895
|
+
"kv"
|
|
1896
|
+
));
|
|
1897
|
+
}
|
|
1898
|
+
const paths = keys.map((key) => this.getFullPath(key, options?.prefix));
|
|
1899
|
+
if (new Set(paths).size !== paths.length) {
|
|
1900
|
+
return err(serviceError(
|
|
1901
|
+
ErrorCodes.INVALID_INPUT,
|
|
1902
|
+
"KV batch read received duplicate keys after prefix resolution",
|
|
1903
|
+
"kv"
|
|
1904
|
+
));
|
|
1905
|
+
}
|
|
1906
|
+
try {
|
|
1907
|
+
const session = this.context.session;
|
|
1908
|
+
const invocationHeaders = this.context.invokeAny(
|
|
1909
|
+
session,
|
|
1910
|
+
paths.map((path) => ({
|
|
1911
|
+
spaceId: session.spaceId,
|
|
1912
|
+
service: "kv",
|
|
1913
|
+
path,
|
|
1914
|
+
action
|
|
1915
|
+
}))
|
|
1916
|
+
);
|
|
1917
|
+
const limitHeaders = {};
|
|
1918
|
+
if (getOptions?.maxResponseBytes !== void 0) {
|
|
1919
|
+
limitHeaders["x-tinycloud-max-response-bytes"] = String(
|
|
1920
|
+
getOptions.maxResponseBytes
|
|
1921
|
+
);
|
|
1922
|
+
}
|
|
1923
|
+
const headers = Array.isArray(invocationHeaders) ? [...invocationHeaders, ...Object.entries(limitHeaders)] : { ...invocationHeaders, ...limitHeaders };
|
|
1924
|
+
const response = await this.context.fetch(`${this.host}/invoke`, {
|
|
1925
|
+
method: "POST",
|
|
1926
|
+
headers,
|
|
1927
|
+
signal: this.combineSignals(options?.signal)
|
|
1928
|
+
});
|
|
1929
|
+
if (!response.ok) {
|
|
1930
|
+
const errorText = await response.text();
|
|
1931
|
+
if (response.status === 401 || response.status === 403) {
|
|
1932
|
+
const { resource, action: requiredAction } = parseAuthError(errorText);
|
|
1933
|
+
return err(authUnauthorizedError("kv", errorText, {
|
|
1934
|
+
status: response.status,
|
|
1935
|
+
...requiredAction && { requiredAction },
|
|
1936
|
+
...resource && { resource }
|
|
1937
|
+
}));
|
|
1938
|
+
}
|
|
1939
|
+
if (response.status === 413) {
|
|
1940
|
+
return err(serviceError(
|
|
1941
|
+
ErrorCodes.KV_RESPONSE_TOO_LARGE,
|
|
1942
|
+
"A KV batch value exceeds the requested response limit",
|
|
1943
|
+
"kv",
|
|
1944
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1945
|
+
));
|
|
1946
|
+
}
|
|
1947
|
+
return err(serviceError(
|
|
1948
|
+
ErrorCodes.NETWORK_ERROR,
|
|
1949
|
+
`Failed to batch read ${keys.length} key(s): ${response.status} - ${errorText}`,
|
|
1950
|
+
"kv",
|
|
1951
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1952
|
+
));
|
|
1953
|
+
}
|
|
1954
|
+
const payload = this.normalizeBatchReadResponse(
|
|
1955
|
+
await response.json(),
|
|
1956
|
+
paths,
|
|
1957
|
+
action === KVAction.GET
|
|
1958
|
+
);
|
|
1959
|
+
if (!payload) {
|
|
1960
|
+
return err(serviceError(
|
|
1961
|
+
ErrorCodes.NETWORK_ERROR,
|
|
1962
|
+
"KV batch read response did not match the requested keys",
|
|
1963
|
+
"kv"
|
|
1964
|
+
));
|
|
1965
|
+
}
|
|
1966
|
+
const results = payload.results.map((item, index) => {
|
|
1967
|
+
if (!item.ok) {
|
|
1968
|
+
return {
|
|
1969
|
+
key: keys[index],
|
|
1970
|
+
result: err(serviceError(item.error.code, item.error.message, "kv"))
|
|
1971
|
+
};
|
|
1972
|
+
}
|
|
1973
|
+
const headers2 = item.headers;
|
|
1974
|
+
const data = action === KVAction.HEAD ? void 0 : this.parseBatchValue(
|
|
1975
|
+
item.dataBase64,
|
|
1976
|
+
headers2,
|
|
1977
|
+
getOptions?.raw,
|
|
1978
|
+
getOptions?.binary
|
|
1979
|
+
);
|
|
1980
|
+
return {
|
|
1981
|
+
key: keys[index],
|
|
1982
|
+
result: ok({
|
|
1983
|
+
data,
|
|
1984
|
+
headers: this.createBatchResponseHeaders(headers2)
|
|
1985
|
+
})
|
|
1986
|
+
};
|
|
1987
|
+
});
|
|
1988
|
+
return ok({ results, count: results.length });
|
|
1989
|
+
} catch (error) {
|
|
1990
|
+
return err(wrapError("kv", error));
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1797
1993
|
/**
|
|
1798
1994
|
* Parse response body based on content type.
|
|
1799
1995
|
*
|
|
@@ -1940,6 +2136,13 @@ var KVService = class extends BaseService {
|
|
|
1940
2136
|
}
|
|
1941
2137
|
});
|
|
1942
2138
|
}
|
|
2139
|
+
async batchGet(keys, options) {
|
|
2140
|
+
return this.withTelemetry(
|
|
2141
|
+
"batchGet",
|
|
2142
|
+
String(keys.length),
|
|
2143
|
+
() => this.batchRead(keys, KVAction.GET, options)
|
|
2144
|
+
);
|
|
2145
|
+
}
|
|
1943
2146
|
/**
|
|
1944
2147
|
* Store a value at a key.
|
|
1945
2148
|
*/
|
|
@@ -2299,6 +2502,13 @@ var KVService = class extends BaseService {
|
|
|
2299
2502
|
}
|
|
2300
2503
|
});
|
|
2301
2504
|
}
|
|
2505
|
+
async batchHead(keys, options) {
|
|
2506
|
+
return this.withTelemetry(
|
|
2507
|
+
"batchHead",
|
|
2508
|
+
String(keys.length),
|
|
2509
|
+
() => this.batchRead(keys, KVAction.HEAD, options)
|
|
2510
|
+
);
|
|
2511
|
+
}
|
|
2302
2512
|
/**
|
|
2303
2513
|
* Create a short-lived signed URL for reading a KV object.
|
|
2304
2514
|
*/
|