@tinycloud/operations 0.3.0 → 0.3.1-beta.1
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 +231 -5
- package/dist/artifacts.cjs.map +1 -1
- package/dist/artifacts.js +231 -5
- package/dist/artifacts.js.map +1 -1
- package/dist/cli-runtime.cjs +232 -6
- package/dist/cli-runtime.cjs.map +1 -1
- package/dist/cli-runtime.js +232 -6
- package/dist/cli-runtime.js.map +1 -1
- package/dist/index.cjs +232 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +232 -6
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/artifacts.js
CHANGED
|
@@ -1064,7 +1064,9 @@ function createKVResponseSchema(dataSchema) {
|
|
|
1064
1064
|
var GenericKVResponseSchema = createKVResponseSchema(z.unknown());
|
|
1065
1065
|
var KVListResponseSchema = z.object({
|
|
1066
1066
|
/** Array of keys matching the list criteria */
|
|
1067
|
-
keys: z.array(z.string())
|
|
1067
|
+
keys: z.array(z.string()),
|
|
1068
|
+
truncated: z.boolean().optional(),
|
|
1069
|
+
nextCursor: z.string().optional()
|
|
1068
1070
|
});
|
|
1069
1071
|
var KVListResultSchema = createResultSchema(KVListResponseSchema);
|
|
1070
1072
|
var ServiceRequestEventSchema = z.object({
|
|
@@ -1468,6 +1470,20 @@ var PrefixedKVService = class _PrefixedKVService {
|
|
|
1468
1470
|
const fullKey = this.getFullKey(key);
|
|
1469
1471
|
return this._kv.get(fullKey, { ...options, prefix: "" });
|
|
1470
1472
|
}
|
|
1473
|
+
async batchGet(keys, options) {
|
|
1474
|
+
const response = await this._kv.batchGet(
|
|
1475
|
+
keys.map((key) => this.getFullKey(key)),
|
|
1476
|
+
{ ...options, prefix: "" }
|
|
1477
|
+
);
|
|
1478
|
+
if (!response.ok) return response;
|
|
1479
|
+
return ok({
|
|
1480
|
+
...response.data,
|
|
1481
|
+
results: response.data.results.map((item, index) => ({
|
|
1482
|
+
...item,
|
|
1483
|
+
key: keys[index]
|
|
1484
|
+
}))
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1471
1487
|
/**
|
|
1472
1488
|
* Store a value at a key.
|
|
1473
1489
|
*/
|
|
@@ -1512,6 +1528,20 @@ var PrefixedKVService = class _PrefixedKVService {
|
|
|
1512
1528
|
const fullKey = this.getFullKey(key);
|
|
1513
1529
|
return this._kv.head(fullKey, { ...options, prefix: "" });
|
|
1514
1530
|
}
|
|
1531
|
+
async batchHead(keys, options) {
|
|
1532
|
+
const response = await this._kv.batchHead(
|
|
1533
|
+
keys.map((key) => this.getFullKey(key)),
|
|
1534
|
+
{ ...options, prefix: "" }
|
|
1535
|
+
);
|
|
1536
|
+
if (!response.ok) return response;
|
|
1537
|
+
return ok({
|
|
1538
|
+
...response.data,
|
|
1539
|
+
results: response.data.results.map((item, index) => ({
|
|
1540
|
+
...item,
|
|
1541
|
+
key: keys[index]
|
|
1542
|
+
}))
|
|
1543
|
+
});
|
|
1544
|
+
}
|
|
1515
1545
|
/**
|
|
1516
1546
|
* Create a short-lived signed URL for reading a KV object.
|
|
1517
1547
|
*/
|
|
@@ -1536,6 +1566,7 @@ var KVAction = {
|
|
|
1536
1566
|
DELETE: "tinycloud.kv/del",
|
|
1537
1567
|
HEAD: "tinycloud.kv/metadata"
|
|
1538
1568
|
};
|
|
1569
|
+
var MAX_KV_BATCH_READ_ITEMS = 100;
|
|
1539
1570
|
function encodeKvBatchPartName(path) {
|
|
1540
1571
|
return encodeURIComponent(path).replace(
|
|
1541
1572
|
/[!'()*]/g,
|
|
@@ -1770,6 +1801,173 @@ var KVService = class extends BaseService {
|
|
|
1770
1801
|
get: (name) => headers.get(name)
|
|
1771
1802
|
};
|
|
1772
1803
|
}
|
|
1804
|
+
createBatchResponseHeaders(values) {
|
|
1805
|
+
const normalized = new Map(
|
|
1806
|
+
Object.entries(values).map(([name, value]) => [name.toLowerCase(), value])
|
|
1807
|
+
);
|
|
1808
|
+
return this.createResponseHeaders({
|
|
1809
|
+
get: (name) => normalized.get(name.toLowerCase()) ?? null
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
1812
|
+
parseBatchValue(dataBase64, headers, raw = false, binary = false) {
|
|
1813
|
+
const encoded = globalThis.atob(dataBase64);
|
|
1814
|
+
const bytes = Uint8Array.from(encoded, (character) => character.charCodeAt(0));
|
|
1815
|
+
if (binary) return bytes;
|
|
1816
|
+
const text = new TextDecoder().decode(bytes);
|
|
1817
|
+
if (raw) return text;
|
|
1818
|
+
const contentType = Object.entries(headers).find(
|
|
1819
|
+
([name]) => name.toLowerCase() === "content-type"
|
|
1820
|
+
)?.[1];
|
|
1821
|
+
return contentType?.includes("application/json") ? JSON.parse(text) : text;
|
|
1822
|
+
}
|
|
1823
|
+
normalizeBatchReadResponse(data, paths, requireData) {
|
|
1824
|
+
if (!data || typeof data !== "object") return void 0;
|
|
1825
|
+
const response = data;
|
|
1826
|
+
if (!Array.isArray(response.results) || response.results.length !== paths.length) {
|
|
1827
|
+
return void 0;
|
|
1828
|
+
}
|
|
1829
|
+
for (let index = 0; index < response.results.length; index++) {
|
|
1830
|
+
const item = response.results[index];
|
|
1831
|
+
if (!item || typeof item !== "object" || item.key !== paths[index] || typeof item.ok !== "boolean") {
|
|
1832
|
+
return void 0;
|
|
1833
|
+
}
|
|
1834
|
+
if (item.ok && (!item.headers || typeof item.headers !== "object" || Object.values(item.headers).some((value) => typeof value !== "string") || requireData && typeof item.dataBase64 !== "string")) {
|
|
1835
|
+
return void 0;
|
|
1836
|
+
}
|
|
1837
|
+
if (!item.ok && (!item.error || typeof item.error.code !== "string" || typeof item.error.message !== "string")) {
|
|
1838
|
+
return void 0;
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
return response;
|
|
1842
|
+
}
|
|
1843
|
+
async batchRead(keys, action, options) {
|
|
1844
|
+
if (!this.requireAuth()) return err(authRequiredError("kv"));
|
|
1845
|
+
if (keys.length === 0) return ok({ results: [], count: 0 });
|
|
1846
|
+
if (keys.length > MAX_KV_BATCH_READ_ITEMS) {
|
|
1847
|
+
return err(serviceError(
|
|
1848
|
+
ErrorCodes.INVALID_INPUT,
|
|
1849
|
+
`KV batch reads accept at most ${MAX_KV_BATCH_READ_ITEMS} keys`,
|
|
1850
|
+
"kv"
|
|
1851
|
+
));
|
|
1852
|
+
}
|
|
1853
|
+
if (keys.length === 1) {
|
|
1854
|
+
const key = keys[0];
|
|
1855
|
+
const result = action === KVAction.GET ? await this.get(key, options) : await this.head(key, options);
|
|
1856
|
+
return ok({
|
|
1857
|
+
results: [{ key, result }],
|
|
1858
|
+
count: 1
|
|
1859
|
+
});
|
|
1860
|
+
}
|
|
1861
|
+
if (!this.context.invokeAny) {
|
|
1862
|
+
return err(serviceError(
|
|
1863
|
+
ErrorCodes.INVALID_INPUT,
|
|
1864
|
+
"KV batch reads require SDK runtime support for multi-resource invocations",
|
|
1865
|
+
"kv"
|
|
1866
|
+
));
|
|
1867
|
+
}
|
|
1868
|
+
const getOptions = action === KVAction.GET ? options : void 0;
|
|
1869
|
+
if (getOptions?.maxResponseBytes !== void 0 && (!Number.isSafeInteger(getOptions.maxResponseBytes) || getOptions.maxResponseBytes <= 0)) {
|
|
1870
|
+
return err(serviceError(
|
|
1871
|
+
ErrorCodes.INVALID_INPUT,
|
|
1872
|
+
"KV maxResponseBytes must be a positive safe integer",
|
|
1873
|
+
"kv"
|
|
1874
|
+
));
|
|
1875
|
+
}
|
|
1876
|
+
const paths = keys.map((key) => this.getFullPath(key, options?.prefix));
|
|
1877
|
+
if (new Set(paths).size !== paths.length) {
|
|
1878
|
+
return err(serviceError(
|
|
1879
|
+
ErrorCodes.INVALID_INPUT,
|
|
1880
|
+
"KV batch read received duplicate keys after prefix resolution",
|
|
1881
|
+
"kv"
|
|
1882
|
+
));
|
|
1883
|
+
}
|
|
1884
|
+
try {
|
|
1885
|
+
const session = this.context.session;
|
|
1886
|
+
const invocationHeaders = this.context.invokeAny(
|
|
1887
|
+
session,
|
|
1888
|
+
paths.map((path) => ({
|
|
1889
|
+
spaceId: session.spaceId,
|
|
1890
|
+
service: "kv",
|
|
1891
|
+
path,
|
|
1892
|
+
action
|
|
1893
|
+
}))
|
|
1894
|
+
);
|
|
1895
|
+
const limitHeaders = {};
|
|
1896
|
+
if (getOptions?.maxResponseBytes !== void 0) {
|
|
1897
|
+
limitHeaders["x-tinycloud-max-response-bytes"] = String(
|
|
1898
|
+
getOptions.maxResponseBytes
|
|
1899
|
+
);
|
|
1900
|
+
}
|
|
1901
|
+
const headers = Array.isArray(invocationHeaders) ? [...invocationHeaders, ...Object.entries(limitHeaders)] : { ...invocationHeaders, ...limitHeaders };
|
|
1902
|
+
const response = await this.context.fetch(`${this.host}/invoke`, {
|
|
1903
|
+
method: "POST",
|
|
1904
|
+
headers,
|
|
1905
|
+
signal: this.combineSignals(options?.signal)
|
|
1906
|
+
});
|
|
1907
|
+
if (!response.ok) {
|
|
1908
|
+
const errorText = await response.text();
|
|
1909
|
+
if (response.status === 401 || response.status === 403) {
|
|
1910
|
+
const { resource, action: requiredAction } = parseAuthError(errorText);
|
|
1911
|
+
return err(authUnauthorizedError("kv", errorText, {
|
|
1912
|
+
status: response.status,
|
|
1913
|
+
...requiredAction && { requiredAction },
|
|
1914
|
+
...resource && { resource }
|
|
1915
|
+
}));
|
|
1916
|
+
}
|
|
1917
|
+
if (response.status === 413) {
|
|
1918
|
+
return err(serviceError(
|
|
1919
|
+
ErrorCodes.KV_RESPONSE_TOO_LARGE,
|
|
1920
|
+
"A KV batch value exceeds the requested response limit",
|
|
1921
|
+
"kv",
|
|
1922
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1923
|
+
));
|
|
1924
|
+
}
|
|
1925
|
+
return err(serviceError(
|
|
1926
|
+
ErrorCodes.NETWORK_ERROR,
|
|
1927
|
+
`Failed to batch read ${keys.length} key(s): ${response.status} - ${errorText}`,
|
|
1928
|
+
"kv",
|
|
1929
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
1930
|
+
));
|
|
1931
|
+
}
|
|
1932
|
+
const payload = this.normalizeBatchReadResponse(
|
|
1933
|
+
await response.json(),
|
|
1934
|
+
paths,
|
|
1935
|
+
action === KVAction.GET
|
|
1936
|
+
);
|
|
1937
|
+
if (!payload) {
|
|
1938
|
+
return err(serviceError(
|
|
1939
|
+
ErrorCodes.NETWORK_ERROR,
|
|
1940
|
+
"KV batch read response did not match the requested keys",
|
|
1941
|
+
"kv"
|
|
1942
|
+
));
|
|
1943
|
+
}
|
|
1944
|
+
const results = payload.results.map((item, index) => {
|
|
1945
|
+
if (!item.ok) {
|
|
1946
|
+
return {
|
|
1947
|
+
key: keys[index],
|
|
1948
|
+
result: err(serviceError(item.error.code, item.error.message, "kv"))
|
|
1949
|
+
};
|
|
1950
|
+
}
|
|
1951
|
+
const headers2 = item.headers;
|
|
1952
|
+
const data = action === KVAction.HEAD ? void 0 : this.parseBatchValue(
|
|
1953
|
+
item.dataBase64,
|
|
1954
|
+
headers2,
|
|
1955
|
+
getOptions?.raw,
|
|
1956
|
+
getOptions?.binary
|
|
1957
|
+
);
|
|
1958
|
+
return {
|
|
1959
|
+
key: keys[index],
|
|
1960
|
+
result: ok({
|
|
1961
|
+
data,
|
|
1962
|
+
headers: this.createBatchResponseHeaders(headers2)
|
|
1963
|
+
})
|
|
1964
|
+
};
|
|
1965
|
+
});
|
|
1966
|
+
return ok({ results, count: results.length });
|
|
1967
|
+
} catch (error) {
|
|
1968
|
+
return err(wrapError("kv", error));
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1773
1971
|
/**
|
|
1774
1972
|
* Parse response body based on content type.
|
|
1775
1973
|
*
|
|
@@ -1916,6 +2114,13 @@ var KVService = class extends BaseService {
|
|
|
1916
2114
|
}
|
|
1917
2115
|
});
|
|
1918
2116
|
}
|
|
2117
|
+
async batchGet(keys, options) {
|
|
2118
|
+
return this.withTelemetry(
|
|
2119
|
+
"batchGet",
|
|
2120
|
+
String(keys.length),
|
|
2121
|
+
() => this.batchRead(keys, KVAction.GET, options)
|
|
2122
|
+
);
|
|
2123
|
+
}
|
|
1919
2124
|
/**
|
|
1920
2125
|
* Store a value at a key.
|
|
1921
2126
|
*/
|
|
@@ -2123,7 +2328,10 @@ var KVService = class extends BaseService {
|
|
|
2123
2328
|
KVAction.LIST,
|
|
2124
2329
|
void 0,
|
|
2125
2330
|
options?.signal,
|
|
2126
|
-
options?.limit === void 0 ? void 0 : {
|
|
2331
|
+
options?.limit === void 0 && options?.cursor === void 0 ? void 0 : {
|
|
2332
|
+
...options?.limit === void 0 ? {} : { "x-tinycloud-limit": String(options.limit) },
|
|
2333
|
+
...options?.cursor === void 0 ? {} : { "x-tinycloud-cursor": options.cursor }
|
|
2334
|
+
}
|
|
2127
2335
|
);
|
|
2128
2336
|
if (!response.ok) {
|
|
2129
2337
|
if (response.status === 401) {
|
|
@@ -2155,7 +2363,8 @@ var KVService = class extends BaseService {
|
|
|
2155
2363
|
}
|
|
2156
2364
|
return ok({
|
|
2157
2365
|
keys,
|
|
2158
|
-
...response.headers.get("x-tinycloud-truncated") === null ? {} : { truncated: response.headers.get("x-tinycloud-truncated") === "true" }
|
|
2366
|
+
...response.headers.get("x-tinycloud-truncated") === null ? {} : { truncated: response.headers.get("x-tinycloud-truncated") === "true" },
|
|
2367
|
+
...response.headers.get("x-tinycloud-next-cursor") === null ? {} : { nextCursor: response.headers.get("x-tinycloud-next-cursor") ?? void 0 }
|
|
2159
2368
|
});
|
|
2160
2369
|
} catch (error) {
|
|
2161
2370
|
return err(wrapError("kv", error));
|
|
@@ -2275,6 +2484,13 @@ var KVService = class extends BaseService {
|
|
|
2275
2484
|
}
|
|
2276
2485
|
});
|
|
2277
2486
|
}
|
|
2487
|
+
async batchHead(keys, options) {
|
|
2488
|
+
return this.withTelemetry(
|
|
2489
|
+
"batchHead",
|
|
2490
|
+
String(keys.length),
|
|
2491
|
+
() => this.batchRead(keys, KVAction.HEAD, options)
|
|
2492
|
+
);
|
|
2493
|
+
}
|
|
2278
2494
|
/**
|
|
2279
2495
|
* Create a short-lived signed URL for reading a KV object.
|
|
2280
2496
|
*/
|
|
@@ -4807,6 +5023,10 @@ var DataVaultService = class extends BaseService {
|
|
|
4807
5023
|
* @returns Result with array of key names (vault/ prefix stripped)
|
|
4808
5024
|
*/
|
|
4809
5025
|
async list(options) {
|
|
5026
|
+
const page = await this.listPage(options);
|
|
5027
|
+
return page.ok ? { ok: true, data: page.data.keys } : page;
|
|
5028
|
+
}
|
|
5029
|
+
async listPage(options) {
|
|
4810
5030
|
return this.withTelemetry("list", options?.prefix, async () => {
|
|
4811
5031
|
if (!this.isUnlocked) {
|
|
4812
5032
|
return vaultError({
|
|
@@ -4824,7 +5044,9 @@ var DataVaultService = class extends BaseService {
|
|
|
4824
5044
|
const listPrefix = options?.prefix ? `vault/${options.prefix}` : "vault/";
|
|
4825
5045
|
const listResult = await this.tc.kv.list({
|
|
4826
5046
|
prefix: listPrefix,
|
|
4827
|
-
removePrefix: true
|
|
5047
|
+
removePrefix: true,
|
|
5048
|
+
...options?.limit === void 0 ? {} : { limit: options.limit },
|
|
5049
|
+
...options?.cursor === void 0 ? {} : { cursor: options.cursor }
|
|
4828
5050
|
});
|
|
4829
5051
|
if (!listResult.ok) {
|
|
4830
5052
|
return vaultError({
|
|
@@ -4841,7 +5063,11 @@ var DataVaultService = class extends BaseService {
|
|
|
4841
5063
|
(k) => k.startsWith(userPrefix) ? k.slice(userPrefix.length) : k
|
|
4842
5064
|
);
|
|
4843
5065
|
}
|
|
4844
|
-
return ok(
|
|
5066
|
+
return ok({
|
|
5067
|
+
keys,
|
|
5068
|
+
truncated: listResult.data.truncated === true,
|
|
5069
|
+
...listResult.data.nextCursor === void 0 ? {} : { nextCursor: listResult.data.nextCursor }
|
|
5070
|
+
});
|
|
4845
5071
|
} catch (error) {
|
|
4846
5072
|
return vaultError({
|
|
4847
5073
|
code: "STORAGE_ERROR",
|