@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.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 : { "x-tinycloud-limit": String(options.limit) }
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(keys);
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",