@tinycloud/operations 0.1.0 → 0.2.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.
@@ -735,6 +735,9 @@ var ErrorCodes = {
735
735
  // KV-specific errors
736
736
  KV_NOT_FOUND: "KV_NOT_FOUND",
737
737
  KV_WRITE_FAILED: "KV_WRITE_FAILED",
738
+ KV_PRECONDITION_FAILED: "KV_PRECONDITION_FAILED",
739
+ KV_CONFLICT: "KV_CONFLICT",
740
+ KV_RESPONSE_TOO_LARGE: "KV_RESPONSE_TOO_LARGE",
738
741
  // SQL-specific errors
739
742
  SQL_ERROR: "SQL_ERROR",
740
743
  SQL_PERMISSION_DENIED: "SQL_PERMISSION_DENIED",
@@ -1681,7 +1684,7 @@ var KVService = class extends BaseService {
1681
1684
  * @param signal - Optional abort signal
1682
1685
  * @returns Fetch response
1683
1686
  */
1684
- async invokeOperation(path, action, body, signal) {
1687
+ async invokeOperation(path, action, body, signal, extraHeaders) {
1685
1688
  const session = this.context.session;
1686
1689
  const headers = this.context.invoke(
1687
1690
  session,
@@ -1689,9 +1692,10 @@ var KVService = class extends BaseService {
1689
1692
  path,
1690
1693
  action
1691
1694
  );
1695
+ const requestHeaders = Array.isArray(headers) ? [...headers, ...Object.entries(extraHeaders ?? {})] : { ...headers, ...extraHeaders };
1692
1696
  return this.context.fetch(`${this.host}/invoke`, {
1693
1697
  method: "POST",
1694
- headers,
1698
+ headers: requestHeaders,
1695
1699
  body,
1696
1700
  signal: this.combineSignals(signal)
1697
1701
  });
@@ -1873,13 +1877,21 @@ var KVService = class extends BaseService {
1873
1877
  if (!this.requireAuth()) {
1874
1878
  return err(authRequiredError("kv"));
1875
1879
  }
1880
+ if (options?.maxResponseBytes !== void 0 && (!Number.isSafeInteger(options.maxResponseBytes) || options.maxResponseBytes <= 0)) {
1881
+ return err(serviceError(
1882
+ ErrorCodes.INVALID_INPUT,
1883
+ "KV maxResponseBytes must be a positive safe integer",
1884
+ "kv"
1885
+ ));
1886
+ }
1876
1887
  const path = this.getFullPath(key, options?.prefix);
1877
1888
  try {
1878
1889
  const response = await this.invokeOperation(
1879
1890
  path,
1880
1891
  KVAction.GET,
1881
1892
  void 0,
1882
- options?.signal
1893
+ options?.signal,
1894
+ options?.maxResponseBytes === void 0 ? void 0 : { "x-tinycloud-max-response-bytes": String(options.maxResponseBytes) }
1883
1895
  );
1884
1896
  if (!response.ok) {
1885
1897
  if (response.status === 401 || response.status === 403) {
@@ -1897,6 +1909,14 @@ var KVService = class extends BaseService {
1897
1909
  return this.classifyNotFound(response, key);
1898
1910
  }
1899
1911
  const errorText = await response.text();
1912
+ if (response.status === 413) {
1913
+ return err(serviceError(
1914
+ ErrorCodes.KV_RESPONSE_TOO_LARGE,
1915
+ `KV value at key "${key}" exceeds the requested response limit`,
1916
+ "kv",
1917
+ { meta: { status: response.status, statusText: response.statusText } }
1918
+ ));
1919
+ }
1900
1920
  return err(
1901
1921
  serviceError(
1902
1922
  ErrorCodes.NETWORK_ERROR,
@@ -1928,6 +1948,13 @@ var KVService = class extends BaseService {
1928
1948
  if (!this.requireAuth()) {
1929
1949
  return err(authRequiredError("kv"));
1930
1950
  }
1951
+ if (options?.ifMatch !== void 0 && options.ifNoneMatch !== void 0) {
1952
+ return err(serviceError(
1953
+ ErrorCodes.INVALID_INPUT,
1954
+ "KV put cannot combine ifMatch and ifNoneMatch",
1955
+ "kv"
1956
+ ));
1957
+ }
1931
1958
  const path = this.getFullPath(key, options?.prefix);
1932
1959
  const body = this.serializePutValue(value, options?.contentType);
1933
1960
  try {
@@ -1935,7 +1962,11 @@ var KVService = class extends BaseService {
1935
1962
  path,
1936
1963
  KVAction.PUT,
1937
1964
  body,
1938
- options?.signal
1965
+ options?.signal,
1966
+ {
1967
+ ...options?.ifMatch === void 0 ? {} : { "if-match": options.ifMatch },
1968
+ ...options?.ifNoneMatch === void 0 ? {} : { "if-none-match": options.ifNoneMatch }
1969
+ }
1939
1970
  );
1940
1971
  if (!response.ok) {
1941
1972
  if (response.status === 401) {
@@ -1948,6 +1979,22 @@ var KVService = class extends BaseService {
1948
1979
  }));
1949
1980
  }
1950
1981
  const errorText = await response.text();
1982
+ if (response.status === 412) {
1983
+ return err(serviceError(
1984
+ ErrorCodes.KV_PRECONDITION_FAILED,
1985
+ `KV precondition failed for key "${key}"`,
1986
+ "kv",
1987
+ { meta: { status: response.status, statusText: response.statusText } }
1988
+ ));
1989
+ }
1990
+ if (response.status === 503 && (options?.ifMatch !== void 0 || options?.ifNoneMatch !== void 0)) {
1991
+ return err(serviceError(
1992
+ ErrorCodes.KV_CONFLICT,
1993
+ `Concurrent KV update conflicted for key "${key}"`,
1994
+ "kv",
1995
+ { meta: { status: response.status, statusText: response.statusText } }
1996
+ ));
1997
+ }
1951
1998
  const quotaError = this.handleQuotaErrorResponse(
1952
1999
  response,
1953
2000
  errorText,
@@ -2083,6 +2130,13 @@ var KVService = class extends BaseService {
2083
2130
  if (!this.requireAuth()) {
2084
2131
  return err(authRequiredError("kv"));
2085
2132
  }
2133
+ if (options?.limit !== void 0 && (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 1e3)) {
2134
+ return err(serviceError(
2135
+ ErrorCodes.INVALID_INPUT,
2136
+ "KV list limit must be an integer from 1 through 1000",
2137
+ "kv"
2138
+ ));
2139
+ }
2086
2140
  let listPath = options?.prefix ?? this._config.prefix ?? "";
2087
2141
  if (options?.path) {
2088
2142
  listPath = listPath ? `${listPath}/${options.path}` : options.path;
@@ -2092,7 +2146,8 @@ var KVService = class extends BaseService {
2092
2146
  listPath,
2093
2147
  KVAction.LIST,
2094
2148
  void 0,
2095
- options?.signal
2149
+ options?.signal,
2150
+ options?.limit === void 0 ? void 0 : { "x-tinycloud-limit": String(options.limit) }
2096
2151
  );
2097
2152
  if (!response.ok) {
2098
2153
  if (response.status === 401) {
@@ -2122,7 +2177,10 @@ var KVService = class extends BaseService {
2122
2177
  (key) => key.startsWith(prefixWithSlash) ? key.slice(prefixWithSlash.length) : key
2123
2178
  );
2124
2179
  }
2125
- return ok({ keys });
2180
+ return ok({
2181
+ keys,
2182
+ ...response.headers.get("x-tinycloud-truncated") === null ? {} : { truncated: response.headers.get("x-tinycloud-truncated") === "true" }
2183
+ });
2126
2184
  } catch (error) {
2127
2185
  return err(wrapError("kv", error));
2128
2186
  }
@@ -2142,7 +2200,8 @@ var KVService = class extends BaseService {
2142
2200
  path,
2143
2201
  KVAction.DELETE,
2144
2202
  void 0,
2145
- options?.signal
2203
+ options?.signal,
2204
+ options?.ifMatch === void 0 ? void 0 : { "if-match": options.ifMatch }
2146
2205
  );
2147
2206
  if (!response.ok) {
2148
2207
  if (response.status === 401) {
@@ -2158,6 +2217,22 @@ var KVService = class extends BaseService {
2158
2217
  return this.classifyNotFound(response, key);
2159
2218
  }
2160
2219
  const errorText = await response.text();
2220
+ if (response.status === 412) {
2221
+ return err(serviceError(
2222
+ ErrorCodes.KV_PRECONDITION_FAILED,
2223
+ `KV precondition failed for key "${key}"`,
2224
+ "kv",
2225
+ { meta: { status: response.status, statusText: response.statusText } }
2226
+ ));
2227
+ }
2228
+ if (response.status === 503 && options?.ifMatch !== void 0) {
2229
+ return err(serviceError(
2230
+ ErrorCodes.KV_CONFLICT,
2231
+ `Concurrent KV delete conflicted for key "${key}"`,
2232
+ "kv",
2233
+ { meta: { status: response.status, statusText: response.statusText } }
2234
+ ));
2235
+ }
2161
2236
  return err(
2162
2237
  serviceError(
2163
2238
  ErrorCodes.NETWORK_ERROR,
@@ -2167,7 +2242,10 @@ var KVService = class extends BaseService {
2167
2242
  )
2168
2243
  );
2169
2244
  }
2170
- return ok(void 0);
2245
+ return ok({
2246
+ data: void 0,
2247
+ headers: this.createResponseHeaders(response.headers)
2248
+ });
2171
2249
  } catch (error) {
2172
2250
  return err(wrapError("kv", error));
2173
2251
  }
@@ -2473,10 +2551,17 @@ var SQLService = class extends BaseService {
2473
2551
  return err(authRequiredError("sql"));
2474
2552
  }
2475
2553
  try {
2554
+ const body = {
2555
+ action: "query",
2556
+ sql,
2557
+ params: serializeSqlValues(params ?? [])
2558
+ };
2559
+ if (options?.maxRows !== void 0) body.maxRows = options.maxRows;
2560
+ if (options?.maxBytes !== void 0) body.maxBytes = options.maxBytes;
2476
2561
  const response = await this.invokeSQL(
2477
2562
  dbName,
2478
2563
  this.actionForSql(sql, SQLAction.READ),
2479
- { action: "query", sql, params: params ?? [] },
2564
+ body,
2480
2565
  options?.signal
2481
2566
  );
2482
2567
  if (!response.ok) {
@@ -2498,7 +2583,7 @@ var SQLService = class extends BaseService {
2498
2583
  const body = {
2499
2584
  action: "execute",
2500
2585
  sql,
2501
- params: params ?? []
2586
+ params: serializeSqlValues(params ?? [])
2502
2587
  };
2503
2588
  if (options?.schema) {
2504
2589
  body.schema = options.schema;
@@ -2823,6 +2908,11 @@ function validateMigrationOptions(options) {
2823
2908
  }
2824
2909
  return null;
2825
2910
  }
2911
+ function serializeSqlValues(values) {
2912
+ return values.map(
2913
+ (value) => value instanceof Uint8Array ? Array.from(value) : value
2914
+ );
2915
+ }
2826
2916
  function migrationKey(namespace, id) {
2827
2917
  return `${encodeURIComponent(namespace)}:${encodeURIComponent(id)}`;
2828
2918
  }