@tinycloud/sdk-services 2.7.0 → 2.8.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/index.cjs CHANGED
@@ -154,6 +154,9 @@ var ErrorCodes = {
154
154
  // KV-specific errors
155
155
  KV_NOT_FOUND: "KV_NOT_FOUND",
156
156
  KV_WRITE_FAILED: "KV_WRITE_FAILED",
157
+ KV_PRECONDITION_FAILED: "KV_PRECONDITION_FAILED",
158
+ KV_CONFLICT: "KV_CONFLICT",
159
+ KV_RESPONSE_TOO_LARGE: "KV_RESPONSE_TOO_LARGE",
157
160
  // SQL-specific errors
158
161
  SQL_ERROR: "SQL_ERROR",
159
162
  SQL_PERMISSION_DENIED: "SQL_PERMISSION_DENIED",
@@ -1611,7 +1614,7 @@ var KVService = class extends BaseService {
1611
1614
  * @param signal - Optional abort signal
1612
1615
  * @returns Fetch response
1613
1616
  */
1614
- async invokeOperation(path, action, body, signal) {
1617
+ async invokeOperation(path, action, body, signal, extraHeaders) {
1615
1618
  const session = this.context.session;
1616
1619
  const headers = this.context.invoke(
1617
1620
  session,
@@ -1619,9 +1622,10 @@ var KVService = class extends BaseService {
1619
1622
  path,
1620
1623
  action
1621
1624
  );
1625
+ const requestHeaders = Array.isArray(headers) ? [...headers, ...Object.entries(extraHeaders ?? {})] : { ...headers, ...extraHeaders };
1622
1626
  return this.context.fetch(`${this.host}/invoke`, {
1623
1627
  method: "POST",
1624
- headers,
1628
+ headers: requestHeaders,
1625
1629
  body,
1626
1630
  signal: this.combineSignals(signal)
1627
1631
  });
@@ -1803,13 +1807,21 @@ var KVService = class extends BaseService {
1803
1807
  if (!this.requireAuth()) {
1804
1808
  return err(authRequiredError("kv"));
1805
1809
  }
1810
+ if (options?.maxResponseBytes !== void 0 && (!Number.isSafeInteger(options.maxResponseBytes) || options.maxResponseBytes <= 0)) {
1811
+ return err(serviceError(
1812
+ ErrorCodes.INVALID_INPUT,
1813
+ "KV maxResponseBytes must be a positive safe integer",
1814
+ "kv"
1815
+ ));
1816
+ }
1806
1817
  const path = this.getFullPath(key, options?.prefix);
1807
1818
  try {
1808
1819
  const response = await this.invokeOperation(
1809
1820
  path,
1810
1821
  KVAction.GET,
1811
1822
  void 0,
1812
- options?.signal
1823
+ options?.signal,
1824
+ options?.maxResponseBytes === void 0 ? void 0 : { "x-tinycloud-max-response-bytes": String(options.maxResponseBytes) }
1813
1825
  );
1814
1826
  if (!response.ok) {
1815
1827
  if (response.status === 401 || response.status === 403) {
@@ -1827,6 +1839,14 @@ var KVService = class extends BaseService {
1827
1839
  return this.classifyNotFound(response, key);
1828
1840
  }
1829
1841
  const errorText = await response.text();
1842
+ if (response.status === 413) {
1843
+ return err(serviceError(
1844
+ ErrorCodes.KV_RESPONSE_TOO_LARGE,
1845
+ `KV value at key "${key}" exceeds the requested response limit`,
1846
+ "kv",
1847
+ { meta: { status: response.status, statusText: response.statusText } }
1848
+ ));
1849
+ }
1830
1850
  return err(
1831
1851
  serviceError(
1832
1852
  ErrorCodes.NETWORK_ERROR,
@@ -1858,6 +1878,13 @@ var KVService = class extends BaseService {
1858
1878
  if (!this.requireAuth()) {
1859
1879
  return err(authRequiredError("kv"));
1860
1880
  }
1881
+ if (options?.ifMatch !== void 0 && options.ifNoneMatch !== void 0) {
1882
+ return err(serviceError(
1883
+ ErrorCodes.INVALID_INPUT,
1884
+ "KV put cannot combine ifMatch and ifNoneMatch",
1885
+ "kv"
1886
+ ));
1887
+ }
1861
1888
  const path = this.getFullPath(key, options?.prefix);
1862
1889
  const body = this.serializePutValue(value, options?.contentType);
1863
1890
  try {
@@ -1865,7 +1892,11 @@ var KVService = class extends BaseService {
1865
1892
  path,
1866
1893
  KVAction.PUT,
1867
1894
  body,
1868
- options?.signal
1895
+ options?.signal,
1896
+ {
1897
+ ...options?.ifMatch === void 0 ? {} : { "if-match": options.ifMatch },
1898
+ ...options?.ifNoneMatch === void 0 ? {} : { "if-none-match": options.ifNoneMatch }
1899
+ }
1869
1900
  );
1870
1901
  if (!response.ok) {
1871
1902
  if (response.status === 401) {
@@ -1878,6 +1909,22 @@ var KVService = class extends BaseService {
1878
1909
  }));
1879
1910
  }
1880
1911
  const errorText = await response.text();
1912
+ if (response.status === 412) {
1913
+ return err(serviceError(
1914
+ ErrorCodes.KV_PRECONDITION_FAILED,
1915
+ `KV precondition failed for key "${key}"`,
1916
+ "kv",
1917
+ { meta: { status: response.status, statusText: response.statusText } }
1918
+ ));
1919
+ }
1920
+ if (response.status === 503 && (options?.ifMatch !== void 0 || options?.ifNoneMatch !== void 0)) {
1921
+ return err(serviceError(
1922
+ ErrorCodes.KV_CONFLICT,
1923
+ `Concurrent KV update conflicted for key "${key}"`,
1924
+ "kv",
1925
+ { meta: { status: response.status, statusText: response.statusText } }
1926
+ ));
1927
+ }
1881
1928
  const quotaError = this.handleQuotaErrorResponse(
1882
1929
  response,
1883
1930
  errorText,
@@ -2013,6 +2060,13 @@ var KVService = class extends BaseService {
2013
2060
  if (!this.requireAuth()) {
2014
2061
  return err(authRequiredError("kv"));
2015
2062
  }
2063
+ if (options?.limit !== void 0 && (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 1e3)) {
2064
+ return err(serviceError(
2065
+ ErrorCodes.INVALID_INPUT,
2066
+ "KV list limit must be an integer from 1 through 1000",
2067
+ "kv"
2068
+ ));
2069
+ }
2016
2070
  let listPath = options?.prefix ?? this._config.prefix ?? "";
2017
2071
  if (options?.path) {
2018
2072
  listPath = listPath ? `${listPath}/${options.path}` : options.path;
@@ -2022,7 +2076,8 @@ var KVService = class extends BaseService {
2022
2076
  listPath,
2023
2077
  KVAction.LIST,
2024
2078
  void 0,
2025
- options?.signal
2079
+ options?.signal,
2080
+ options?.limit === void 0 ? void 0 : { "x-tinycloud-limit": String(options.limit) }
2026
2081
  );
2027
2082
  if (!response.ok) {
2028
2083
  if (response.status === 401) {
@@ -2052,7 +2107,10 @@ var KVService = class extends BaseService {
2052
2107
  (key) => key.startsWith(prefixWithSlash) ? key.slice(prefixWithSlash.length) : key
2053
2108
  );
2054
2109
  }
2055
- return ok({ keys });
2110
+ return ok({
2111
+ keys,
2112
+ ...response.headers.get("x-tinycloud-truncated") === null ? {} : { truncated: response.headers.get("x-tinycloud-truncated") === "true" }
2113
+ });
2056
2114
  } catch (error) {
2057
2115
  return err(wrapError("kv", error));
2058
2116
  }
@@ -2072,7 +2130,8 @@ var KVService = class extends BaseService {
2072
2130
  path,
2073
2131
  KVAction.DELETE,
2074
2132
  void 0,
2075
- options?.signal
2133
+ options?.signal,
2134
+ options?.ifMatch === void 0 ? void 0 : { "if-match": options.ifMatch }
2076
2135
  );
2077
2136
  if (!response.ok) {
2078
2137
  if (response.status === 401) {
@@ -2088,6 +2147,22 @@ var KVService = class extends BaseService {
2088
2147
  return this.classifyNotFound(response, key);
2089
2148
  }
2090
2149
  const errorText = await response.text();
2150
+ if (response.status === 412) {
2151
+ return err(serviceError(
2152
+ ErrorCodes.KV_PRECONDITION_FAILED,
2153
+ `KV precondition failed for key "${key}"`,
2154
+ "kv",
2155
+ { meta: { status: response.status, statusText: response.statusText } }
2156
+ ));
2157
+ }
2158
+ if (response.status === 503 && options?.ifMatch !== void 0) {
2159
+ return err(serviceError(
2160
+ ErrorCodes.KV_CONFLICT,
2161
+ `Concurrent KV delete conflicted for key "${key}"`,
2162
+ "kv",
2163
+ { meta: { status: response.status, statusText: response.statusText } }
2164
+ ));
2165
+ }
2091
2166
  return err(
2092
2167
  serviceError(
2093
2168
  ErrorCodes.NETWORK_ERROR,
@@ -2097,7 +2172,10 @@ var KVService = class extends BaseService {
2097
2172
  )
2098
2173
  );
2099
2174
  }
2100
- return ok(void 0);
2175
+ return ok({
2176
+ data: void 0,
2177
+ headers: this.createResponseHeaders(response.headers)
2178
+ });
2101
2179
  } catch (error) {
2102
2180
  return err(wrapError("kv", error));
2103
2181
  }
@@ -2417,10 +2495,17 @@ var SQLService = class extends BaseService {
2417
2495
  return err(authRequiredError("sql"));
2418
2496
  }
2419
2497
  try {
2498
+ const body = {
2499
+ action: "query",
2500
+ sql,
2501
+ params: serializeSqlValues(params ?? [])
2502
+ };
2503
+ if (options?.maxRows !== void 0) body.maxRows = options.maxRows;
2504
+ if (options?.maxBytes !== void 0) body.maxBytes = options.maxBytes;
2420
2505
  const response = await this.invokeSQL(
2421
2506
  dbName,
2422
2507
  this.actionForSql(sql, SQLAction.READ),
2423
- { action: "query", sql, params: params ?? [] },
2508
+ body,
2424
2509
  options?.signal
2425
2510
  );
2426
2511
  if (!response.ok) {
@@ -2442,7 +2527,7 @@ var SQLService = class extends BaseService {
2442
2527
  const body = {
2443
2528
  action: "execute",
2444
2529
  sql,
2445
- params: params ?? []
2530
+ params: serializeSqlValues(params ?? [])
2446
2531
  };
2447
2532
  if (options?.schema) {
2448
2533
  body.schema = options.schema;
@@ -2767,6 +2852,11 @@ function validateMigrationOptions(options) {
2767
2852
  }
2768
2853
  return null;
2769
2854
  }
2855
+ function serializeSqlValues(values) {
2856
+ return values.map(
2857
+ (value) => value instanceof Uint8Array ? Array.from(value) : value
2858
+ );
2859
+ }
2770
2860
  function migrationKey(namespace, id) {
2771
2861
  return `${encodeURIComponent(namespace)}:${encodeURIComponent(id)}`;
2772
2862
  }