@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.
@@ -1088,7 +1088,9 @@ function createKVResponseSchema(dataSchema) {
1088
1088
  var GenericKVResponseSchema = createKVResponseSchema(import_zod.z.unknown());
1089
1089
  var KVListResponseSchema = import_zod.z.object({
1090
1090
  /** Array of keys matching the list criteria */
1091
- keys: import_zod.z.array(import_zod.z.string())
1091
+ keys: import_zod.z.array(import_zod.z.string()),
1092
+ truncated: import_zod.z.boolean().optional(),
1093
+ nextCursor: import_zod.z.string().optional()
1092
1094
  });
1093
1095
  var KVListResultSchema = createResultSchema(KVListResponseSchema);
1094
1096
  var ServiceRequestEventSchema = import_zod.z.object({
@@ -1492,6 +1494,20 @@ var PrefixedKVService = class _PrefixedKVService {
1492
1494
  const fullKey = this.getFullKey(key);
1493
1495
  return this._kv.get(fullKey, { ...options, prefix: "" });
1494
1496
  }
1497
+ async batchGet(keys, options) {
1498
+ const response = await this._kv.batchGet(
1499
+ keys.map((key) => this.getFullKey(key)),
1500
+ { ...options, prefix: "" }
1501
+ );
1502
+ if (!response.ok) return response;
1503
+ return ok({
1504
+ ...response.data,
1505
+ results: response.data.results.map((item, index) => ({
1506
+ ...item,
1507
+ key: keys[index]
1508
+ }))
1509
+ });
1510
+ }
1495
1511
  /**
1496
1512
  * Store a value at a key.
1497
1513
  */
@@ -1536,6 +1552,20 @@ var PrefixedKVService = class _PrefixedKVService {
1536
1552
  const fullKey = this.getFullKey(key);
1537
1553
  return this._kv.head(fullKey, { ...options, prefix: "" });
1538
1554
  }
1555
+ async batchHead(keys, options) {
1556
+ const response = await this._kv.batchHead(
1557
+ keys.map((key) => this.getFullKey(key)),
1558
+ { ...options, prefix: "" }
1559
+ );
1560
+ if (!response.ok) return response;
1561
+ return ok({
1562
+ ...response.data,
1563
+ results: response.data.results.map((item, index) => ({
1564
+ ...item,
1565
+ key: keys[index]
1566
+ }))
1567
+ });
1568
+ }
1539
1569
  /**
1540
1570
  * Create a short-lived signed URL for reading a KV object.
1541
1571
  */
@@ -1560,6 +1590,7 @@ var KVAction = {
1560
1590
  DELETE: "tinycloud.kv/del",
1561
1591
  HEAD: "tinycloud.kv/metadata"
1562
1592
  };
1593
+ var MAX_KV_BATCH_READ_ITEMS = 100;
1563
1594
  function encodeKvBatchPartName(path) {
1564
1595
  return encodeURIComponent(path).replace(
1565
1596
  /[!'()*]/g,
@@ -1794,6 +1825,173 @@ var KVService = class extends BaseService {
1794
1825
  get: (name) => headers.get(name)
1795
1826
  };
1796
1827
  }
1828
+ createBatchResponseHeaders(values) {
1829
+ const normalized = new Map(
1830
+ Object.entries(values).map(([name, value]) => [name.toLowerCase(), value])
1831
+ );
1832
+ return this.createResponseHeaders({
1833
+ get: (name) => normalized.get(name.toLowerCase()) ?? null
1834
+ });
1835
+ }
1836
+ parseBatchValue(dataBase64, headers, raw = false, binary = false) {
1837
+ const encoded = globalThis.atob(dataBase64);
1838
+ const bytes = Uint8Array.from(encoded, (character) => character.charCodeAt(0));
1839
+ if (binary) return bytes;
1840
+ const text = new TextDecoder().decode(bytes);
1841
+ if (raw) return text;
1842
+ const contentType = Object.entries(headers).find(
1843
+ ([name]) => name.toLowerCase() === "content-type"
1844
+ )?.[1];
1845
+ return contentType?.includes("application/json") ? JSON.parse(text) : text;
1846
+ }
1847
+ normalizeBatchReadResponse(data, paths, requireData) {
1848
+ if (!data || typeof data !== "object") return void 0;
1849
+ const response = data;
1850
+ if (!Array.isArray(response.results) || response.results.length !== paths.length) {
1851
+ return void 0;
1852
+ }
1853
+ for (let index = 0; index < response.results.length; index++) {
1854
+ const item = response.results[index];
1855
+ if (!item || typeof item !== "object" || item.key !== paths[index] || typeof item.ok !== "boolean") {
1856
+ return void 0;
1857
+ }
1858
+ if (item.ok && (!item.headers || typeof item.headers !== "object" || Object.values(item.headers).some((value) => typeof value !== "string") || requireData && typeof item.dataBase64 !== "string")) {
1859
+ return void 0;
1860
+ }
1861
+ if (!item.ok && (!item.error || typeof item.error.code !== "string" || typeof item.error.message !== "string")) {
1862
+ return void 0;
1863
+ }
1864
+ }
1865
+ return response;
1866
+ }
1867
+ async batchRead(keys, action, options) {
1868
+ if (!this.requireAuth()) return err(authRequiredError("kv"));
1869
+ if (keys.length === 0) return ok({ results: [], count: 0 });
1870
+ if (keys.length > MAX_KV_BATCH_READ_ITEMS) {
1871
+ return err(serviceError(
1872
+ ErrorCodes.INVALID_INPUT,
1873
+ `KV batch reads accept at most ${MAX_KV_BATCH_READ_ITEMS} keys`,
1874
+ "kv"
1875
+ ));
1876
+ }
1877
+ if (keys.length === 1) {
1878
+ const key = keys[0];
1879
+ const result = action === KVAction.GET ? await this.get(key, options) : await this.head(key, options);
1880
+ return ok({
1881
+ results: [{ key, result }],
1882
+ count: 1
1883
+ });
1884
+ }
1885
+ if (!this.context.invokeAny) {
1886
+ return err(serviceError(
1887
+ ErrorCodes.INVALID_INPUT,
1888
+ "KV batch reads require SDK runtime support for multi-resource invocations",
1889
+ "kv"
1890
+ ));
1891
+ }
1892
+ const getOptions = action === KVAction.GET ? options : void 0;
1893
+ if (getOptions?.maxResponseBytes !== void 0 && (!Number.isSafeInteger(getOptions.maxResponseBytes) || getOptions.maxResponseBytes <= 0)) {
1894
+ return err(serviceError(
1895
+ ErrorCodes.INVALID_INPUT,
1896
+ "KV maxResponseBytes must be a positive safe integer",
1897
+ "kv"
1898
+ ));
1899
+ }
1900
+ const paths = keys.map((key) => this.getFullPath(key, options?.prefix));
1901
+ if (new Set(paths).size !== paths.length) {
1902
+ return err(serviceError(
1903
+ ErrorCodes.INVALID_INPUT,
1904
+ "KV batch read received duplicate keys after prefix resolution",
1905
+ "kv"
1906
+ ));
1907
+ }
1908
+ try {
1909
+ const session = this.context.session;
1910
+ const invocationHeaders = this.context.invokeAny(
1911
+ session,
1912
+ paths.map((path) => ({
1913
+ spaceId: session.spaceId,
1914
+ service: "kv",
1915
+ path,
1916
+ action
1917
+ }))
1918
+ );
1919
+ const limitHeaders = {};
1920
+ if (getOptions?.maxResponseBytes !== void 0) {
1921
+ limitHeaders["x-tinycloud-max-response-bytes"] = String(
1922
+ getOptions.maxResponseBytes
1923
+ );
1924
+ }
1925
+ const headers = Array.isArray(invocationHeaders) ? [...invocationHeaders, ...Object.entries(limitHeaders)] : { ...invocationHeaders, ...limitHeaders };
1926
+ const response = await this.context.fetch(`${this.host}/invoke`, {
1927
+ method: "POST",
1928
+ headers,
1929
+ signal: this.combineSignals(options?.signal)
1930
+ });
1931
+ if (!response.ok) {
1932
+ const errorText = await response.text();
1933
+ if (response.status === 401 || response.status === 403) {
1934
+ const { resource, action: requiredAction } = parseAuthError(errorText);
1935
+ return err(authUnauthorizedError("kv", errorText, {
1936
+ status: response.status,
1937
+ ...requiredAction && { requiredAction },
1938
+ ...resource && { resource }
1939
+ }));
1940
+ }
1941
+ if (response.status === 413) {
1942
+ return err(serviceError(
1943
+ ErrorCodes.KV_RESPONSE_TOO_LARGE,
1944
+ "A KV batch value exceeds the requested response limit",
1945
+ "kv",
1946
+ { meta: { status: response.status, statusText: response.statusText } }
1947
+ ));
1948
+ }
1949
+ return err(serviceError(
1950
+ ErrorCodes.NETWORK_ERROR,
1951
+ `Failed to batch read ${keys.length} key(s): ${response.status} - ${errorText}`,
1952
+ "kv",
1953
+ { meta: { status: response.status, statusText: response.statusText } }
1954
+ ));
1955
+ }
1956
+ const payload = this.normalizeBatchReadResponse(
1957
+ await response.json(),
1958
+ paths,
1959
+ action === KVAction.GET
1960
+ );
1961
+ if (!payload) {
1962
+ return err(serviceError(
1963
+ ErrorCodes.NETWORK_ERROR,
1964
+ "KV batch read response did not match the requested keys",
1965
+ "kv"
1966
+ ));
1967
+ }
1968
+ const results = payload.results.map((item, index) => {
1969
+ if (!item.ok) {
1970
+ return {
1971
+ key: keys[index],
1972
+ result: err(serviceError(item.error.code, item.error.message, "kv"))
1973
+ };
1974
+ }
1975
+ const headers2 = item.headers;
1976
+ const data = action === KVAction.HEAD ? void 0 : this.parseBatchValue(
1977
+ item.dataBase64,
1978
+ headers2,
1979
+ getOptions?.raw,
1980
+ getOptions?.binary
1981
+ );
1982
+ return {
1983
+ key: keys[index],
1984
+ result: ok({
1985
+ data,
1986
+ headers: this.createBatchResponseHeaders(headers2)
1987
+ })
1988
+ };
1989
+ });
1990
+ return ok({ results, count: results.length });
1991
+ } catch (error) {
1992
+ return err(wrapError("kv", error));
1993
+ }
1994
+ }
1797
1995
  /**
1798
1996
  * Parse response body based on content type.
1799
1997
  *
@@ -1940,6 +2138,13 @@ var KVService = class extends BaseService {
1940
2138
  }
1941
2139
  });
1942
2140
  }
2141
+ async batchGet(keys, options) {
2142
+ return this.withTelemetry(
2143
+ "batchGet",
2144
+ String(keys.length),
2145
+ () => this.batchRead(keys, KVAction.GET, options)
2146
+ );
2147
+ }
1943
2148
  /**
1944
2149
  * Store a value at a key.
1945
2150
  */
@@ -2147,7 +2352,10 @@ var KVService = class extends BaseService {
2147
2352
  KVAction.LIST,
2148
2353
  void 0,
2149
2354
  options?.signal,
2150
- options?.limit === void 0 ? void 0 : { "x-tinycloud-limit": String(options.limit) }
2355
+ options?.limit === void 0 && options?.cursor === void 0 ? void 0 : {
2356
+ ...options?.limit === void 0 ? {} : { "x-tinycloud-limit": String(options.limit) },
2357
+ ...options?.cursor === void 0 ? {} : { "x-tinycloud-cursor": options.cursor }
2358
+ }
2151
2359
  );
2152
2360
  if (!response.ok) {
2153
2361
  if (response.status === 401) {
@@ -2179,7 +2387,8 @@ var KVService = class extends BaseService {
2179
2387
  }
2180
2388
  return ok({
2181
2389
  keys,
2182
- ...response.headers.get("x-tinycloud-truncated") === null ? {} : { truncated: response.headers.get("x-tinycloud-truncated") === "true" }
2390
+ ...response.headers.get("x-tinycloud-truncated") === null ? {} : { truncated: response.headers.get("x-tinycloud-truncated") === "true" },
2391
+ ...response.headers.get("x-tinycloud-next-cursor") === null ? {} : { nextCursor: response.headers.get("x-tinycloud-next-cursor") ?? void 0 }
2183
2392
  });
2184
2393
  } catch (error) {
2185
2394
  return err(wrapError("kv", error));
@@ -2299,6 +2508,13 @@ var KVService = class extends BaseService {
2299
2508
  }
2300
2509
  });
2301
2510
  }
2511
+ async batchHead(keys, options) {
2512
+ return this.withTelemetry(
2513
+ "batchHead",
2514
+ String(keys.length),
2515
+ () => this.batchRead(keys, KVAction.HEAD, options)
2516
+ );
2517
+ }
2302
2518
  /**
2303
2519
  * Create a short-lived signed URL for reading a KV object.
2304
2520
  */
@@ -4831,6 +5047,10 @@ var DataVaultService = class extends BaseService {
4831
5047
  * @returns Result with array of key names (vault/ prefix stripped)
4832
5048
  */
4833
5049
  async list(options) {
5050
+ const page = await this.listPage(options);
5051
+ return page.ok ? { ok: true, data: page.data.keys } : page;
5052
+ }
5053
+ async listPage(options) {
4834
5054
  return this.withTelemetry("list", options?.prefix, async () => {
4835
5055
  if (!this.isUnlocked) {
4836
5056
  return vaultError({
@@ -4848,7 +5068,9 @@ var DataVaultService = class extends BaseService {
4848
5068
  const listPrefix = options?.prefix ? `vault/${options.prefix}` : "vault/";
4849
5069
  const listResult = await this.tc.kv.list({
4850
5070
  prefix: listPrefix,
4851
- removePrefix: true
5071
+ removePrefix: true,
5072
+ ...options?.limit === void 0 ? {} : { limit: options.limit },
5073
+ ...options?.cursor === void 0 ? {} : { cursor: options.cursor }
4852
5074
  });
4853
5075
  if (!listResult.ok) {
4854
5076
  return vaultError({
@@ -4865,7 +5087,11 @@ var DataVaultService = class extends BaseService {
4865
5087
  (k) => k.startsWith(userPrefix) ? k.slice(userPrefix.length) : k
4866
5088
  );
4867
5089
  }
4868
- return ok(keys);
5090
+ return ok({
5091
+ keys,
5092
+ truncated: listResult.data.truncated === true,
5093
+ ...listResult.data.nextCursor === void 0 ? {} : { nextCursor: listResult.data.nextCursor }
5094
+ });
4869
5095
  } catch (error) {
4870
5096
  return vaultError({
4871
5097
  code: "STORAGE_ERROR",