stitchkit 0.46.0 → 0.47.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.
Files changed (46) hide show
  1. package/README.md +4 -3
  2. package/dist/browser/cancellation.d.ts +14 -0
  3. package/dist/browser/cancellation.d.ts.map +1 -0
  4. package/dist/browser/client-multipart.d.ts +3 -1
  5. package/dist/browser/client-multipart.d.ts.map +1 -1
  6. package/dist/browser/client.d.ts +1 -0
  7. package/dist/browser/client.d.ts.map +1 -1
  8. package/dist/browser/http.d.ts +1 -0
  9. package/dist/browser/http.d.ts.map +1 -1
  10. package/dist/cli.js +2 -2
  11. package/dist/contract/define.d.ts +59 -15
  12. package/dist/contract/define.d.ts.map +1 -1
  13. package/dist/contract/index.d.ts +1 -1
  14. package/dist/contract/index.d.ts.map +1 -1
  15. package/dist/contract/index.js +1 -1
  16. package/dist/{index-zwqty9zf.js → index-44xysy8r.js} +465 -49
  17. package/dist/{index-pwyedf7b.js → index-45dz4m51.js} +46 -0
  18. package/dist/{index-c40tkxcd.js → index-8ekq6res.js} +1 -1
  19. package/dist/{index-5s8b7z6q.js → index-ee621cmy.js} +9 -9
  20. package/dist/{index-62pqb23z.js → index-kp8xamqp.js} +1 -1
  21. package/dist/{index-w1s873ng.js → index-nrytvb30.js} +4 -3
  22. package/dist/index.js +191 -88
  23. package/dist/node.js +2 -2
  24. package/dist/observability/audit.d.ts +22 -3
  25. package/dist/observability/audit.d.ts.map +1 -1
  26. package/dist/observability/index.d.ts +1 -1
  27. package/dist/observability/index.d.ts.map +1 -1
  28. package/dist/observability/index.js +82 -15
  29. package/dist/server/context.d.ts +8 -5
  30. package/dist/server/context.d.ts.map +1 -1
  31. package/dist/server/create.d.ts.map +1 -1
  32. package/dist/server/implement.d.ts +33 -2
  33. package/dist/server/implement.d.ts.map +1 -1
  34. package/dist/server/index.d.ts +3 -3
  35. package/dist/server/index.d.ts.map +1 -1
  36. package/dist/server/index.js +28 -6
  37. package/dist/server/middleware/auth.d.ts +7 -4
  38. package/dist/server/middleware/auth.d.ts.map +1 -1
  39. package/dist/server/multipart.d.ts +13 -18
  40. package/dist/server/multipart.d.ts.map +1 -1
  41. package/dist/server/openapi.d.ts.map +1 -1
  42. package/dist/server/types.d.ts +48 -15
  43. package/dist/server/types.d.ts.map +1 -1
  44. package/dist/tools.js +167 -68
  45. package/llms-full.txt +367 -67
  46. package/package.json +1 -1
package/dist/tools.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  inputIsQuery,
3
3
  signJwt,
4
4
  verifyPkce
5
- } from "./index-w1s873ng.js";
5
+ } from "./index-nrytvb30.js";
6
6
  import {
7
7
  DEFAULT_CORS_ALLOW_HEADERS
8
8
  } from "./index-r1qp4rve.js";
@@ -26,7 +26,7 @@ import {
26
26
  toolErrorFromResult,
27
27
  toolResultFromError,
28
28
  writeDownload
29
- } from "./index-62pqb23z.js";
29
+ } from "./index-kp8xamqp.js";
30
30
  import {
31
31
  isWithinDir,
32
32
  realPathWithinDir,
@@ -34,7 +34,7 @@ import {
34
34
  } from "./index-yxpe3phd.js";
35
35
  import {
36
36
  redact
37
- } from "./index-c40tkxcd.js";
37
+ } from "./index-8ekq6res.js";
38
38
  import {
39
39
  AppError,
40
40
  getRequestContext,
@@ -45,7 +45,7 @@ import {
45
45
  resolvePropagationContext,
46
46
  runWithRequestContext,
47
47
  typedEntries
48
- } from "./index-5s8b7z6q.js";
48
+ } from "./index-ee621cmy.js";
49
49
 
50
50
  // src/tools/agent.ts
51
51
  import { jsonSchema, tool } from "ai";
@@ -2158,6 +2158,63 @@ function mountOAuthProvider(config) {
2158
2158
  };
2159
2159
  return dcrEnabled ? [metadataRoute, registerRoute, authorizeRoute, tokenRoute] : [metadataRoute, authorizeRoute, tokenRoute];
2160
2160
  }
2161
+ // src/browser/cancellation.ts
2162
+ class RequestCancellationError extends Error {
2163
+ cause;
2164
+ constructor(cause) {
2165
+ super(cause === "caller" ? "Request was aborted" : "Request timed out");
2166
+ this.cause = cause;
2167
+ this.name = "RequestCancellationError";
2168
+ }
2169
+ }
2170
+ function createRequestCancellation(caller, timeoutMs) {
2171
+ if (!caller && timeoutMs === undefined) {
2172
+ return { signal: undefined, run: (operation) => operation() };
2173
+ }
2174
+ const controller = new AbortController;
2175
+ let cause;
2176
+ let timer;
2177
+ const abortFromCaller = () => {
2178
+ if (cause)
2179
+ return;
2180
+ cause = "caller";
2181
+ controller.abort(caller?.reason);
2182
+ };
2183
+ if (caller?.aborted)
2184
+ abortFromCaller();
2185
+ else
2186
+ caller?.addEventListener("abort", abortFromCaller, { once: true });
2187
+ if (timeoutMs !== undefined) {
2188
+ timer = setTimeout(() => {
2189
+ if (cause)
2190
+ return;
2191
+ cause = "timeout";
2192
+ controller.abort(new DOMException("Request timed out", "TimeoutError"));
2193
+ }, timeoutMs);
2194
+ }
2195
+ return {
2196
+ signal: controller.signal,
2197
+ async run(operation) {
2198
+ try {
2199
+ if (cause === "caller")
2200
+ throw cancellationError(cause);
2201
+ return await operation(controller.signal);
2202
+ } catch (error) {
2203
+ if (cause)
2204
+ throw cancellationError(cause);
2205
+ throw error;
2206
+ } finally {
2207
+ if (timer !== undefined)
2208
+ clearTimeout(timer);
2209
+ caller?.removeEventListener("abort", abortFromCaller);
2210
+ }
2211
+ }
2212
+ };
2213
+ }
2214
+ function cancellationError(cause) {
2215
+ return new RequestCancellationError(cause);
2216
+ }
2217
+
2161
2218
  // src/browser/client-multipart.ts
2162
2219
  function isFileDescriptor(value) {
2163
2220
  return typeof value === "object" && value !== null && !(value instanceof Blob) && "uri" in value && typeof value.uri === "string" && "name" in value && typeof value.name === "string" && "type" in value && typeof value.type === "string";
@@ -2176,6 +2233,35 @@ function appendFormFields(formData, values, skipKeys) {
2176
2233
  formData.append(key, typeof value === "string" ? value : JSON.stringify(value));
2177
2234
  }
2178
2235
  }
2236
+ function buildMultipartForm(descriptor, values) {
2237
+ const formData = new FormData;
2238
+ const fileFields = new Set(Object.keys(descriptor.files));
2239
+ for (const [field, policy] of Object.entries(descriptor.files)) {
2240
+ const value = values[field];
2241
+ if (value === undefined) {
2242
+ if (policy.required !== false)
2243
+ throw new Error(`Missing multipart file field: ${field}`);
2244
+ continue;
2245
+ }
2246
+ if (policy.multiple === true) {
2247
+ if (!Array.isArray(value) || value.length === 0) {
2248
+ throw new Error(`Multipart file field "${field}" must be a non-empty array`);
2249
+ }
2250
+ for (const file of value) {
2251
+ if (!isMultipartFile(file)) {
2252
+ throw new Error(`Invalid multipart file field: ${field}`);
2253
+ }
2254
+ appendMultipartFile(formData, field, file);
2255
+ }
2256
+ continue;
2257
+ }
2258
+ if (!isMultipartFile(value))
2259
+ throw new Error(`Invalid multipart file field: ${field}`);
2260
+ appendMultipartFile(formData, field, value);
2261
+ }
2262
+ appendFormFields(formData, values, fileFields);
2263
+ return formData;
2264
+ }
2179
2265
 
2180
2266
  // src/browser/client-url.ts
2181
2267
  function isParamArray(value) {
@@ -2379,95 +2465,108 @@ function setClientMethod(target, key, method) {
2379
2465
  function createHttpMethod(endpoint, prefix, client, config) {
2380
2466
  const httpMethod = endpoint.method.toLowerCase();
2381
2467
  return (...args) => {
2382
- const firstArg = args[0] ?? {};
2468
+ const { requestArgs: firstArg, options } = splitClientCall(endpoint, args, config);
2383
2469
  const plan = planClientRequest(endpoint, prefix, firstArg, config);
2384
2470
  if (endpoint.multipart) {
2385
- const file = firstArg[endpoint.multipart];
2386
- if (!isMultipartFile(file)) {
2387
- throw new Error(`Missing multipart file field: ${endpoint.multipart}`);
2388
- }
2389
2471
  if (httpMethod === "get" || httpMethod === "head" || httpMethod === "delete") {
2390
2472
  throw new Error(`Multipart endpoint ${endpoint.method} ${endpoint.path} must be POST / PUT / PATCH`);
2391
2473
  }
2392
- const formData = new FormData;
2393
- appendMultipartFile(formData, endpoint.multipart, file);
2394
- appendFormFields(formData, plan.remainingArgs, new Set([endpoint.multipart]));
2395
- return withOutput(endpoint, client[httpMethod](plan.relativeUrl, formData, withTimeout(undefined, endpoint)));
2474
+ const formData = buildMultipartForm(endpoint.multipart, plan.remainingArgs);
2475
+ return withOutput(endpoint, client[httpMethod](plan.relativeUrl, formData, withTimeout(options, endpoint)));
2396
2476
  }
2397
2477
  if (httpMethod === "get" || httpMethod === "head") {
2398
- return withOutput(endpoint, client[httpMethod](plan.relativeUrl, withTimeout(undefined, endpoint)));
2478
+ return withOutput(endpoint, client[httpMethod](plan.relativeUrl, withTimeout(options, endpoint)));
2399
2479
  }
2400
2480
  if (httpMethod === "delete") {
2401
- return withOutput(endpoint, client.delete(plan.relativeUrl, withTimeout(undefined, endpoint)));
2481
+ return withOutput(endpoint, client.delete(plan.relativeUrl, withTimeout(options, endpoint)));
2402
2482
  }
2403
- return withOutput(endpoint, client[httpMethod](plan.relativeUrl, Object.keys(plan.remainingArgs).length > 0 ? plan.remainingArgs : undefined, withTimeout(undefined, endpoint)));
2483
+ return withOutput(endpoint, client[httpMethod](plan.relativeUrl, Object.keys(plan.remainingArgs).length > 0 ? plan.remainingArgs : undefined, withTimeout(options, endpoint)));
2404
2484
  };
2405
2485
  }
2406
2486
  function createFetchMethod(endpoint, prefix, config, contractConfig) {
2407
- return async (args) => {
2408
- const plan = planClientRequest(endpoint, prefix, args ?? {}, contractConfig);
2487
+ return async (...callArgs) => {
2488
+ const { requestArgs, options } = splitClientCall(endpoint, callArgs, contractConfig);
2489
+ const plan = planClientRequest(endpoint, prefix, requestArgs, contractConfig);
2409
2490
  const url = joinClientBaseUrl(config.baseUrl, plan.relativeUrl);
2410
2491
  const headers = {
2411
2492
  Accept: "application/json",
2412
2493
  ...typeof config.headers === "function" ? config.headers() : config.headers
2413
2494
  };
2414
- const signal = endpoint.timeout !== undefined ? AbortSignal.timeout(endpoint.timeout) : undefined;
2415
- const hasBody = endpoint.method !== "GET" && endpoint.method !== "HEAD" && endpoint.method !== "DELETE" && !endpoint.multipart && endpoint.input && args;
2495
+ const cancellation = createRequestCancellation(options?.signal, endpoint.timeout ?? config.timeout ?? 30000);
2496
+ const hasBody = endpoint.method !== "GET" && endpoint.method !== "HEAD" && endpoint.method !== "DELETE" && !endpoint.multipart && endpoint.input && Object.keys(plan.remainingArgs).length > 0;
2416
2497
  if (hasBody)
2417
2498
  headers["Content-Type"] = "application/json";
2418
- if (endpoint.multipart && args) {
2419
- const file = args[endpoint.multipart];
2420
- if (!isMultipartFile(file)) {
2421
- throw new Error(`Missing multipart file field: ${endpoint.multipart}`);
2422
- }
2423
- const formData = new FormData;
2424
- appendMultipartFile(formData, endpoint.multipart, file);
2425
- appendFormFields(formData, plan.remainingArgs, new Set([endpoint.multipart]));
2426
- const res2 = await fetch(url, {
2427
- method: endpoint.method,
2428
- headers,
2429
- credentials: config.credentials,
2430
- body: formData,
2431
- signal
2432
- });
2433
- if (!res2.ok) {
2434
- await throwForErrorResponse(res2, config, null);
2435
- }
2436
- if (endpoint.rawResponse)
2437
- return res2;
2438
- if (!endpoint.output) {
2439
- const text = await res2.text();
2440
- if (text.length > 0) {
2441
- throw new Error("Server returned data for an endpoint with no output contract");
2499
+ try {
2500
+ return await cancellation.run(async (signal) => {
2501
+ const body = endpoint.multipart ? buildMultipartForm(endpoint.multipart, plan.remainingArgs) : hasBody ? JSON.stringify(plan.remainingArgs) : undefined;
2502
+ const res = await fetch(url, {
2503
+ method: endpoint.method,
2504
+ headers,
2505
+ credentials: config.credentials,
2506
+ signal,
2507
+ ...body !== undefined && { body }
2508
+ });
2509
+ if (!res.ok) {
2510
+ await throwForErrorResponse(res, config, { error: res.statusText });
2442
2511
  }
2443
- return;
2444
- }
2445
- return endpoint.output.parse(await res2.json());
2446
- }
2447
- const res = await fetch(url, {
2448
- method: endpoint.method,
2449
- headers,
2450
- credentials: config.credentials,
2451
- signal,
2452
- ...hasBody && {
2453
- body: JSON.stringify(plan.remainingArgs)
2454
- }
2455
- });
2456
- if (!res.ok) {
2457
- await throwForErrorResponse(res, config, { error: res.statusText });
2458
- }
2459
- if (endpoint.rawResponse)
2460
- return res;
2461
- if (!endpoint.output) {
2462
- const text = await res.text();
2463
- if (text.length > 0) {
2464
- throw new Error("Server returned data for an endpoint with no output contract");
2512
+ if (endpoint.rawResponse)
2513
+ return res;
2514
+ if (!endpoint.output) {
2515
+ const text = await res.text();
2516
+ if (text.length > 0) {
2517
+ throw new Error("Server returned data for an endpoint with no output contract");
2518
+ }
2519
+ return;
2520
+ }
2521
+ return endpoint.output.parse(await res.json());
2522
+ });
2523
+ } catch (error) {
2524
+ if (error instanceof RequestCancellationError) {
2525
+ throw new ApiError(error.cause === "caller" ? "REQUEST_ABORTED" : "REQUEST_TIMEOUT", 0, undefined, error.message);
2465
2526
  }
2466
- return;
2527
+ if (ApiError.is(error))
2528
+ throw error;
2529
+ const message = error instanceof Error ? error.message : undefined;
2530
+ throw new ApiError("UNKNOWN_ERROR", 0, message ? { message } : undefined, message);
2467
2531
  }
2468
- return endpoint.output.parse(await res.json());
2469
2532
  };
2470
2533
  }
2534
+ function endpointHasArguments(endpoint) {
2535
+ return Boolean(endpoint.params || endpoint.input || endpoint.multipart);
2536
+ }
2537
+ function splitClientCall(endpoint, args, contractConfig) {
2538
+ const hasScopedArguments = (contractConfig?.stripPrefixKeys?.length ?? 0) > 0;
2539
+ if (!endpointHasArguments(endpoint) && !hasScopedArguments) {
2540
+ return {
2541
+ requestArgs: {},
2542
+ options: readClientRequestOptions(args[0])
2543
+ };
2544
+ }
2545
+ const requestArgs = args[0];
2546
+ if (requestArgs !== undefined && !isRecord(requestArgs)) {
2547
+ throw new TypeError("Endpoint arguments must be an object");
2548
+ }
2549
+ return {
2550
+ requestArgs: requestArgs ?? {},
2551
+ options: readClientRequestOptions(args[1])
2552
+ };
2553
+ }
2554
+ function readClientRequestOptions(value) {
2555
+ if (value === undefined)
2556
+ return;
2557
+ if (!isRecord(value))
2558
+ throw new TypeError("Client request options must be an object");
2559
+ const signal = value.signal;
2560
+ if (signal === undefined)
2561
+ return {};
2562
+ if (!isAbortSignal(signal)) {
2563
+ throw new TypeError("Client request signal must be an AbortSignal");
2564
+ }
2565
+ return { signal };
2566
+ }
2567
+ function isAbortSignal(value) {
2568
+ return typeof value === "object" && value !== null && "aborted" in value && typeof value.aborted === "boolean" && "addEventListener" in value && typeof value.addEventListener === "function" && "removeEventListener" in value && typeof value.removeEventListener === "function";
2569
+ }
2471
2570
  async function throwForErrorResponse(res, config, fallbackBody) {
2472
2571
  const body = await res.json().catch(() => fallbackBody);
2473
2572
  config.onError?.(res.status, body);