stitchkit 0.46.0 → 0.48.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 +13 -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 +67 -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 +195 -94
  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 +171 -74
  45. package/llms-full.txt +411 -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) {
@@ -2362,11 +2448,11 @@ function withOutput(endpoint, result) {
2362
2448
  }
2363
2449
  function createClient(contract, configOrClient, contractConfig) {
2364
2450
  const client = {};
2365
- const makeMethod = isHttpAdapter(configOrClient) ? (endpoint) => createHttpMethod(endpoint, contract.meta.prefix, configOrClient, contractConfig) : (endpoint) => createFetchMethod(endpoint, contract.meta.prefix, configOrClient, contractConfig);
2451
+ const makeExecutor = isHttpAdapter(configOrClient) ? (endpoint) => createHttpExecutor(endpoint, contract.meta.prefix, configOrClient, contractConfig) : (endpoint) => createFetchExecutor(endpoint, contract.meta.prefix, configOrClient, contractConfig);
2366
2452
  for (const [key, endpoint] of typedEntries(contract.endpoints)) {
2367
2453
  if (endpoint.expose && !endpoint.expose.includes("HTTP"))
2368
2454
  continue;
2369
- setClientMethod(client, key, makeMethod(endpoint));
2455
+ setClientMethod(client, key, createEndpointMethod(endpoint, makeExecutor(endpoint), contractConfig));
2370
2456
  }
2371
2457
  return client;
2372
2458
  }
@@ -2376,98 +2462,109 @@ function isHttpAdapter(value) {
2376
2462
  function setClientMethod(target, key, method) {
2377
2463
  target[key] = method;
2378
2464
  }
2379
- function createHttpMethod(endpoint, prefix, client, config) {
2465
+ function createEndpointMethod(endpoint, execute, contractConfig) {
2466
+ const hasScopedArguments = (contractConfig?.stripPrefixKeys?.length ?? 0) > 0;
2467
+ if (endpointHasArguments(endpoint) || hasScopedArguments) {
2468
+ const method2 = (requestArgs) => execute(readClientRequestArgs(requestArgs), undefined);
2469
+ return Object.assign(method2, {
2470
+ withOptions: (requestArgs, options) => execute(readClientRequestArgs(requestArgs), readClientRequestOptions(options))
2471
+ });
2472
+ }
2473
+ const method = () => execute({}, undefined);
2474
+ return Object.assign(method, {
2475
+ withOptions: (options) => execute({}, readClientRequestOptions(options))
2476
+ });
2477
+ }
2478
+ function createHttpExecutor(endpoint, prefix, client, config) {
2380
2479
  const httpMethod = endpoint.method.toLowerCase();
2381
- return (...args) => {
2382
- const firstArg = args[0] ?? {};
2383
- const plan = planClientRequest(endpoint, prefix, firstArg, config);
2480
+ return (requestArgs, options) => {
2481
+ const plan = planClientRequest(endpoint, prefix, requestArgs, config);
2384
2482
  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
2483
  if (httpMethod === "get" || httpMethod === "head" || httpMethod === "delete") {
2390
2484
  throw new Error(`Multipart endpoint ${endpoint.method} ${endpoint.path} must be POST / PUT / PATCH`);
2391
2485
  }
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)));
2486
+ const formData = buildMultipartForm(endpoint.multipart, plan.remainingArgs);
2487
+ return withOutput(endpoint, client[httpMethod](plan.relativeUrl, formData, withTimeout(options, endpoint)));
2396
2488
  }
2397
2489
  if (httpMethod === "get" || httpMethod === "head") {
2398
- return withOutput(endpoint, client[httpMethod](plan.relativeUrl, withTimeout(undefined, endpoint)));
2490
+ return withOutput(endpoint, client[httpMethod](plan.relativeUrl, withTimeout(options, endpoint)));
2399
2491
  }
2400
2492
  if (httpMethod === "delete") {
2401
- return withOutput(endpoint, client.delete(plan.relativeUrl, withTimeout(undefined, endpoint)));
2493
+ return withOutput(endpoint, client.delete(plan.relativeUrl, withTimeout(options, endpoint)));
2402
2494
  }
2403
- return withOutput(endpoint, client[httpMethod](plan.relativeUrl, Object.keys(plan.remainingArgs).length > 0 ? plan.remainingArgs : undefined, withTimeout(undefined, endpoint)));
2495
+ return withOutput(endpoint, client[httpMethod](plan.relativeUrl, Object.keys(plan.remainingArgs).length > 0 ? plan.remainingArgs : undefined, withTimeout(options, endpoint)));
2404
2496
  };
2405
2497
  }
2406
- function createFetchMethod(endpoint, prefix, config, contractConfig) {
2407
- return async (args) => {
2408
- const plan = planClientRequest(endpoint, prefix, args ?? {}, contractConfig);
2498
+ function createFetchExecutor(endpoint, prefix, config, contractConfig) {
2499
+ return async (requestArgs, options) => {
2500
+ const plan = planClientRequest(endpoint, prefix, requestArgs, contractConfig);
2409
2501
  const url = joinClientBaseUrl(config.baseUrl, plan.relativeUrl);
2410
2502
  const headers = {
2411
2503
  Accept: "application/json",
2412
2504
  ...typeof config.headers === "function" ? config.headers() : config.headers
2413
2505
  };
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;
2506
+ const cancellation = createRequestCancellation(options?.signal, endpoint.timeout ?? config.timeout ?? 30000);
2507
+ const hasBody = endpoint.method !== "GET" && endpoint.method !== "HEAD" && endpoint.method !== "DELETE" && !endpoint.multipart && endpoint.input && Object.keys(plan.remainingArgs).length > 0;
2416
2508
  if (hasBody)
2417
2509
  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");
2510
+ try {
2511
+ return await cancellation.run(async (signal) => {
2512
+ const body = endpoint.multipart ? buildMultipartForm(endpoint.multipart, plan.remainingArgs) : hasBody ? JSON.stringify(plan.remainingArgs) : undefined;
2513
+ const res = await fetch(url, {
2514
+ method: endpoint.method,
2515
+ headers,
2516
+ credentials: config.credentials,
2517
+ signal,
2518
+ ...body !== undefined && { body }
2519
+ });
2520
+ if (!res.ok) {
2521
+ await throwForErrorResponse(res, config, { error: res.statusText });
2442
2522
  }
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");
2523
+ if (endpoint.rawResponse)
2524
+ return res;
2525
+ if (!endpoint.output) {
2526
+ const text = await res.text();
2527
+ if (text.length > 0) {
2528
+ throw new Error("Server returned data for an endpoint with no output contract");
2529
+ }
2530
+ return;
2531
+ }
2532
+ return endpoint.output.parse(await res.json());
2533
+ });
2534
+ } catch (error) {
2535
+ if (error instanceof RequestCancellationError) {
2536
+ throw new ApiError(error.cause === "caller" ? "REQUEST_ABORTED" : "REQUEST_TIMEOUT", 0, undefined, error.message);
2465
2537
  }
2466
- return;
2538
+ if (ApiError.is(error))
2539
+ throw error;
2540
+ const message = error instanceof Error ? error.message : undefined;
2541
+ throw new ApiError("UNKNOWN_ERROR", 0, message ? { message } : undefined, message);
2467
2542
  }
2468
- return endpoint.output.parse(await res.json());
2469
2543
  };
2470
2544
  }
2545
+ function endpointHasArguments(endpoint) {
2546
+ return Boolean(endpoint.params || endpoint.input || endpoint.multipart);
2547
+ }
2548
+ function readClientRequestArgs(requestArgs) {
2549
+ if (requestArgs !== undefined && !isRecord(requestArgs)) {
2550
+ throw new TypeError("Endpoint arguments must be an object");
2551
+ }
2552
+ return requestArgs ?? {};
2553
+ }
2554
+ function readClientRequestOptions(value) {
2555
+ if (!isRecord(value))
2556
+ throw new TypeError("Client request options must be an object");
2557
+ const signal = value.signal;
2558
+ if (signal === undefined)
2559
+ return {};
2560
+ if (!isAbortSignal(signal)) {
2561
+ throw new TypeError("Client request signal must be an AbortSignal");
2562
+ }
2563
+ return { signal };
2564
+ }
2565
+ function isAbortSignal(value) {
2566
+ 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";
2567
+ }
2471
2568
  async function throwForErrorResponse(res, config, fallbackBody) {
2472
2569
  const body = await res.json().catch(() => fallbackBody);
2473
2570
  config.onError?.(res.status, body);