stitchkit 0.18.0 → 0.19.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 (68) hide show
  1. package/dist/browser/client.d.ts.map +1 -1
  2. package/dist/browser/socket-io.d.ts.map +1 -1
  3. package/dist/cli.js +3 -3
  4. package/dist/contract/errors-factory.d.ts +18 -0
  5. package/dist/contract/errors-factory.d.ts.map +1 -0
  6. package/dist/contract/factory.d.ts +36 -0
  7. package/dist/contract/factory.d.ts.map +1 -0
  8. package/dist/contract/index.d.ts +2 -0
  9. package/dist/contract/index.d.ts.map +1 -1
  10. package/dist/contract/index.js +6 -1
  11. package/dist/contract/pagination.d.ts.map +1 -1
  12. package/dist/index-809wc1tt.js +18 -0
  13. package/dist/{index-tm7dqzxc.js → index-c7nyw0yt.js} +9 -9
  14. package/dist/{index-dz2k8d5h.js → index-dzx781tm.js} +1 -1
  15. package/dist/{index-ktn76xz0.js → index-ng2v2ts4.js} +17 -1
  16. package/dist/index-tje0q6gp.js +44 -0
  17. package/dist/{index-6qv1fxqy.js → index-wpbn133x.js} +13 -50
  18. package/dist/{index-yzac2zbh.js → index-x62gnfsk.js} +50 -9
  19. package/dist/{index-akv8rk4w.js → index-xmncnw8z.js} +2 -2
  20. package/dist/{index-yv93m4yr.js → index-ynnh9x6h.js} +11 -29
  21. package/dist/index.js +82 -62
  22. package/dist/internal/base64url.d.ts +16 -0
  23. package/dist/internal/base64url.d.ts.map +1 -0
  24. package/dist/node.js +5 -4
  25. package/dist/observability/index.js +2 -2
  26. package/dist/react/entity-cache.d.ts +54 -0
  27. package/dist/react/entity-cache.d.ts.map +1 -0
  28. package/dist/react.d.ts +1 -0
  29. package/dist/react.d.ts.map +1 -1
  30. package/dist/react.js +74 -0
  31. package/dist/server/create.d.ts.map +1 -1
  32. package/dist/server/error-hook.d.ts +56 -0
  33. package/dist/server/error-hook.d.ts.map +1 -0
  34. package/dist/server/index.d.ts +2 -1
  35. package/dist/server/index.d.ts.map +1 -1
  36. package/dist/server/index.js +32 -7
  37. package/dist/server/middleware/auth.d.ts.map +1 -1
  38. package/dist/server/middleware/cors.d.ts +13 -0
  39. package/dist/server/middleware/cors.d.ts.map +1 -1
  40. package/dist/server/middleware/pkce.d.ts +0 -6
  41. package/dist/server/middleware/pkce.d.ts.map +1 -1
  42. package/dist/server/router.d.ts +0 -2
  43. package/dist/server/router.d.ts.map +1 -1
  44. package/dist/server/types.d.ts +0 -8
  45. package/dist/server/types.d.ts.map +1 -1
  46. package/dist/tools/agent.d.ts +2 -2
  47. package/dist/tools/agent.d.ts.map +1 -1
  48. package/dist/tools/cli-format.d.ts +2 -2
  49. package/dist/tools/cli-format.d.ts.map +1 -1
  50. package/dist/tools/cli.d.ts +2 -2
  51. package/dist/tools/cli.d.ts.map +1 -1
  52. package/dist/tools/execute.d.ts +7 -0
  53. package/dist/tools/execute.d.ts.map +1 -1
  54. package/dist/tools/mcp.d.ts +3 -3
  55. package/dist/tools/mcp.d.ts.map +1 -1
  56. package/dist/tools/mount.d.ts +3 -3
  57. package/dist/tools/mount.d.ts.map +1 -1
  58. package/dist/tools/oauth-metadata.d.ts.map +1 -1
  59. package/dist/tools/oauth-provider.d.ts.map +1 -1
  60. package/dist/tools/tool-logger.d.ts +31 -0
  61. package/dist/tools/tool-logger.d.ts.map +1 -0
  62. package/dist/tools/transports.d.ts +28 -0
  63. package/dist/tools/transports.d.ts.map +1 -0
  64. package/dist/tools.d.ts +3 -1
  65. package/dist/tools.d.ts.map +1 -1
  66. package/dist/tools.js +71 -11
  67. package/llms-full.txt +235 -4
  68. package/package.json +1 -1
package/dist/tools.js CHANGED
@@ -2,7 +2,10 @@ import {
2
2
  inputIsQuery,
3
3
  signJwt,
4
4
  verifyPkce
5
- } from "./index-yv93m4yr.js";
5
+ } from "./index-ynnh9x6h.js";
6
+ import {
7
+ DEFAULT_CORS_ALLOW_HEADERS
8
+ } from "./index-tje0q6gp.js";
6
9
  import {
7
10
  coerceJsonArgs,
8
11
  collectTools,
@@ -15,19 +18,19 @@ import {
15
18
  pollUntil,
16
19
  readCapped,
17
20
  toolResultFromError
18
- } from "./index-akv8rk4w.js";
21
+ } from "./index-xmncnw8z.js";
19
22
  import {
20
23
  toJsonSchema
21
24
  } from "./index-0ed3bx43.js";
22
25
  import {
23
26
  AppError,
24
27
  isWithinDir
25
- } from "./index-ktn76xz0.js";
28
+ } from "./index-ng2v2ts4.js";
26
29
  import"./index-khwedj16.js";
27
30
  import {
28
31
  isRecord,
29
32
  typedEntries
30
- } from "./index-tm7dqzxc.js";
33
+ } from "./index-c7nyw0yt.js";
31
34
 
32
35
  // src/tools/agent.ts
33
36
  import { tool, zodSchema } from "ai";
@@ -319,7 +322,7 @@ function wwwAuthenticateHeader(resource) {
319
322
  var PUBLIC_CORS = {
320
323
  "Access-Control-Allow-Origin": "*",
321
324
  "Access-Control-Allow-Methods": "GET, OPTIONS",
322
- "Access-Control-Allow-Headers": "Authorization, Content-Type"
325
+ "Access-Control-Allow-Headers": DEFAULT_CORS_ALLOW_HEADERS
323
326
  };
324
327
  function oauthProtectedResourceRoute(config) {
325
328
  const body = JSON.stringify({
@@ -599,7 +602,7 @@ import { randomUUID as randomUUID2 } from "node:crypto";
599
602
  var PUBLIC_CORS2 = {
600
603
  "Access-Control-Allow-Origin": "*",
601
604
  "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
602
- "Access-Control-Allow-Headers": "Authorization, Content-Type"
605
+ "Access-Control-Allow-Headers": DEFAULT_CORS_ALLOW_HEADERS
603
606
  };
604
607
  var AS_METADATA_PATH = "/.well-known/oauth-authorization-server";
605
608
  var AUTH_CODE_TTL_MS = 60000;
@@ -895,6 +898,12 @@ function withTimeout(options, timeout) {
895
898
  return options;
896
899
  return { ...options, timeout };
897
900
  }
901
+ function withOutput(endpoint, result) {
902
+ const schema = endpoint.output;
903
+ if (!schema)
904
+ return result;
905
+ return result.then((value) => value === undefined ? undefined : schema.parse(value));
906
+ }
898
907
  function isParamArray(value) {
899
908
  return Array.isArray(value) && value.every((v) => typeof v === "string" || typeof v === "number");
900
909
  }
@@ -958,18 +967,21 @@ function createHttpMethod(endpoint, prefix, client, config) {
958
967
  if (!isMultipartFile(file)) {
959
968
  throw new Error(`Missing multipart file field: ${endpoint.multipart}`);
960
969
  }
970
+ if (httpMethod === "get" || httpMethod === "delete") {
971
+ throw new Error(`Multipart endpoint ${endpoint.method} ${endpoint.path} must be POST / PUT / PATCH`);
972
+ }
961
973
  const formData = new FormData;
962
974
  appendMultipartFile(formData, endpoint.multipart, file);
963
975
  appendFormFields(formData, firstArg, new Set([...prefixKeys, endpoint.multipart]));
964
- return client.post(url, formData, withTimeout(undefined, endpoint.timeout));
976
+ return withOutput(endpoint, client[httpMethod](url, formData, withTimeout(undefined, endpoint.timeout)));
965
977
  }
966
978
  if (isGet) {
967
979
  const params = collectQueryParams(firstArg, prefixKeys, endpoint);
968
- return client.get(url, withTimeout(params ? { params } : undefined, endpoint.timeout));
980
+ return withOutput(endpoint, client.get(url, withTimeout(params ? { params } : undefined, endpoint.timeout)));
969
981
  }
970
982
  if (httpMethod === "delete") {
971
983
  const params = collectQueryParams(firstArg, prefixKeys, endpoint);
972
- return client.delete(url, withTimeout(params ? { params } : undefined, endpoint.timeout));
984
+ return withOutput(endpoint, client.delete(url, withTimeout(params ? { params } : undefined, endpoint.timeout)));
973
985
  }
974
986
  const payload = {};
975
987
  for (const [key, value] of Object.entries(firstArg)) {
@@ -977,7 +989,7 @@ function createHttpMethod(endpoint, prefix, client, config) {
977
989
  payload[key] = value;
978
990
  }
979
991
  }
980
- return client[httpMethod](url, Object.keys(payload).length > 0 ? payload : undefined, withTimeout(undefined, endpoint.timeout));
992
+ return withOutput(endpoint, client[httpMethod](url, Object.keys(payload).length > 0 ? payload : undefined, withTimeout(undefined, endpoint.timeout)));
981
993
  };
982
994
  }
983
995
  function createFetchMethod(endpoint, prefix, config, contractConfig) {
@@ -994,6 +1006,7 @@ function createFetchMethod(endpoint, prefix, config, contractConfig) {
994
1006
  Accept: "application/json",
995
1007
  ...typeof config.headers === "function" ? config.headers() : config.headers
996
1008
  };
1009
+ const signal = endpoint.timeout !== undefined ? AbortSignal.timeout(endpoint.timeout) : undefined;
997
1010
  const isQuery = inputIsQuery(endpoint.method);
998
1011
  const hasBody = !isQuery && !endpoint.multipart && endpoint.input && args;
999
1012
  if (isQuery && args) {
@@ -1026,7 +1039,8 @@ function createFetchMethod(endpoint, prefix, config, contractConfig) {
1026
1039
  method: endpoint.method,
1027
1040
  headers,
1028
1041
  credentials: config.credentials,
1029
- body: formData
1042
+ body: formData,
1043
+ signal
1030
1044
  });
1031
1045
  if (!res2.ok) {
1032
1046
  await throwForErrorResponse(res2, config, null);
@@ -1040,6 +1054,7 @@ function createFetchMethod(endpoint, prefix, config, contractConfig) {
1040
1054
  method: endpoint.method,
1041
1055
  headers,
1042
1056
  credentials: config.credentials,
1057
+ signal,
1043
1058
  ...hasBody && {
1044
1059
  body: JSON.stringify(stripParams(hasBody, endpoint.path, prefixKeys))
1045
1060
  }
@@ -1165,6 +1180,26 @@ function implementRemote(contract, http, options) {
1165
1180
  methods
1166
1181
  };
1167
1182
  }
1183
+ // src/tools/tool-logger.ts
1184
+ function createToolLogger(config = {}) {
1185
+ const log = config.log ?? ((line) => console.info(line));
1186
+ return {
1187
+ afterToolCall: (toolName, _args, result, durationMs, context, endpoint) => {
1188
+ const record = {
1189
+ tool: toolName,
1190
+ service: endpoint.serviceName,
1191
+ action: endpoint.key,
1192
+ ok: result.ok,
1193
+ code: result.ok ? undefined : result.code,
1194
+ durationMs: Math.round(durationMs),
1195
+ source: String(context.source)
1196
+ };
1197
+ const codePart = result.ok ? "" : ` ${result.code}`;
1198
+ log(`[tool] ${result.ok ? "ok" : "warn"} ${toolName} (${endpoint.serviceName}.${endpoint.key})${codePart} ${record.durationMs}ms`);
1199
+ config.onRecord?.(record);
1200
+ }
1201
+ };
1202
+ }
1168
1203
  // src/tools/toolkit.ts
1169
1204
  function createToolkit() {
1170
1205
  return {
@@ -1176,6 +1211,29 @@ function createToolkit() {
1176
1211
  createCli: (config) => createCli(config)
1177
1212
  };
1178
1213
  }
1214
+ // src/tools/transports.ts
1215
+ var TOOL_TRANSPORTS2 = ["MCP", "AGENT", "CLI"];
1216
+ function emptyCounts() {
1217
+ return { HTTP: 0, MCP: 0, AGENT: 0, CLI: 0 };
1218
+ }
1219
+ function summarizeTransports(services) {
1220
+ const totals = emptyCounts();
1221
+ const perService = services.map((service) => {
1222
+ const counts = emptyCounts();
1223
+ for (const method of Object.values(service.methods)) {
1224
+ if (!method.expose || method.expose.includes("HTTP"))
1225
+ counts.HTTP += 1;
1226
+ }
1227
+ for (const transport of TOOL_TRANSPORTS2) {
1228
+ counts[transport] = collectTools(service, transport).length;
1229
+ }
1230
+ for (const transport of Object.keys(counts)) {
1231
+ totals[transport] += counts[transport];
1232
+ }
1233
+ return { service: service.name, counts };
1234
+ });
1235
+ return { services: services.length, totals, perService };
1236
+ }
1179
1237
  // src/tools/view-file.ts
1180
1238
  import { readFile, realpath, stat } from "node:fs/promises";
1181
1239
  import { extname as extname2, resolve } from "node:path";
@@ -1301,6 +1359,7 @@ function mountViewFile(server, options = {}) {
1301
1359
  export {
1302
1360
  wwwAuthenticateHeader,
1303
1361
  validateMcpSchemas,
1362
+ summarizeTransports,
1304
1363
  resolveMedia,
1305
1364
  protectedResourceMetadataUrl,
1306
1365
  oauthProtectedResourceRoute,
@@ -1318,6 +1377,7 @@ export {
1318
1377
  flattenUnionsDeep,
1319
1378
  flattenDiscriminatedUnion,
1320
1379
  createToolkit,
1380
+ createToolLogger,
1321
1381
  createStdioMcpServer,
1322
1382
  createMcpHandler,
1323
1383
  createCli,
package/llms-full.txt CHANGED
@@ -33,19 +33,23 @@ realtime, `@tanstack/react-query` + `react-query-kit` for React). See
33
33
 
34
34
  ## Entrypoints
35
35
 
36
- stitchkit ships five entrypoints. Each is import-safe for one environment —
36
+ stitchkit ships eight entrypoints. Each is import-safe for one environment —
37
37
  keeping server-only code (`Bun.serve`, the MCP SDK) out of browser bundles.
38
38
 
39
39
  | Import | Use in | Holds |
40
40
  |--------|--------|-------|
41
41
  | `stitchkit` | browser **and** server | `defineContract`, `createClient`, `createHttpClient`, `createSocketIOClient`, `parseSSE`, the error model |
42
42
  | `stitchkit/contract` | browser **and** server | the contract layer alone — `defineContract`, errors, pagination |
43
- | `stitchkit/server` | server | `createServer`, `implement`, hooks, auth, Socket.IO server, server primitives |
44
- | `stitchkit/tools` | server | `createMcpHandler`, `mountMcp`, `mountAgent` |
43
+ | `stitchkit/server` | server (Bun) | `createServer`, `implement`, hooks, auth, Socket.IO server, server primitives |
44
+ | `stitchkit/node` | server (Node ≥ 22) | `serveNode` + the runtime-agnostic core — the Node mirror of `/server` |
45
+ | `stitchkit/tools` | server | `createMcpHandler`, `mountMcp`, `mountAgent`, the OAuth provider, native tools |
46
+ | `stitchkit/cli` | server | `createCli` — the CLI transport, light (no MCP SDK / `ai`) |
47
+ | `stitchkit/observability` | server | the audit layer — `createAuditHook`, trace context, sanitisation |
45
48
  | `stitchkit/react` | browser | `createCursorQuery`, `createCacheBridge` |
46
49
 
47
50
  Rule of thumb: browser code imports `stitchkit` and `stitchkit/react`; server
48
- code adds `stitchkit/server` and `stitchkit/tools`.
51
+ code adds `stitchkit/server` (or `stitchkit/node` on Node) and `stitchkit/tools`.
52
+ The full export list of each is in the [API reference](../api/reference.md).
49
53
 
50
54
  ## Project layout
51
55
 
@@ -427,6 +431,21 @@ for every endpoint that declares none. Scopes are free strings — the framework
427
431
  attaches no meaning, your auth hook does. See
428
432
  [Auth & errors](./auth-and-errors.md).
429
433
 
434
+ A plain `defineContract` defaults a missing `scope` to `'public'` — forget it and
435
+ the endpoint is public (fail-open). If every contract in your app must be scoped,
436
+ `createContractFactory` binds your scope vocabulary once and makes `scope`
437
+ **required and typed** — a missing or mistyped scope is a compile error:
438
+
439
+ ```ts
440
+ // app: one line, once
441
+ export const { defineContract } = createContractFactory<'public' | 'user' | 'admin'>()
442
+
443
+ // scope is now mandatory and checked against the union
444
+ export const users = defineContract({ prefix: 'users', scope: 'user' }, { … })
445
+ ```
446
+
447
+ The vocabulary is yours; the returned contracts are ordinary `ContractDef`s.
448
+
430
449
  ## One source of truth
431
450
 
432
451
  A contract is plain data — no classes, no decorators, no codegen. It is imported
@@ -1440,6 +1459,25 @@ const handleMcp = createMcpHandler({
1440
1459
  })
1441
1460
  ```
1442
1461
 
1462
+ ## Logging tool calls — `createToolLogger`
1463
+
1464
+ Every tool mount fires an `afterToolCall` hook. `createToolLogger` is a ready
1465
+ preset for it — one line logs each call (ok / failed, duration, which endpoint,
1466
+ keyed by the endpoint's stable `serviceName` / `key` identity):
1467
+
1468
+ ```ts
1469
+ import { createToolLogger } from 'stitchkit/tools'
1470
+
1471
+ mountMcp(server, services, { hooks: createToolLogger() })
1472
+ // [tool] ok list_widgets (widgets.list) 12ms
1473
+ // [tool] warn get_widget (widgets.get) NOT_FOUND 4ms
1474
+ ```
1475
+
1476
+ Pass `log` to redirect the line, or `onRecord` to feed a metrics sink the
1477
+ structured `ToolCallRecord`. For a boot-time picture of what is exposed where,
1478
+ `summarizeTransports(services)` returns per-transport operation counts (HTTP /
1479
+ MCP / AGENT / CLI) for you to log.
1480
+
1443
1481
  ## One handler, three callers
1444
1482
 
1445
1483
  A contract handler runs the same for an HTTP request, an MCP tool call and an
@@ -1858,6 +1896,35 @@ onSuccess: () => bridge.markFresh(['notes'])
1858
1896
  to the socket and call `queryClient` directly. The bridge just centralises the
1859
1897
  event-to-cache mapping and the echo guard.
1860
1898
 
1899
+ ### Entity cache handlers
1900
+
1901
+ The created / updated / deleted events of one entity almost always patch the
1902
+ cache the same way: prepend to the list, replace by id, remove by id — plus the
1903
+ detail query. `createEntityCacheHandlers` builds those three handlers from a
1904
+ small config, so you wire them onto the bridge instead of hand-rolling the
1905
+ updater per entity:
1906
+
1907
+ ```ts
1908
+ import { createEntityCacheHandlers } from 'stitchkit/react'
1909
+
1910
+ const widgetCache = createEntityCacheHandlers<Widget>({
1911
+ getId: (w) => w.id,
1912
+ listKey: ['widgets'],
1913
+ detailKey: (id) => ['widgets', id],
1914
+ })
1915
+
1916
+ createCacheBridge({ socket, queryClient, handlers: {
1917
+ widgetCreated: widgetCache.created,
1918
+ widgetUpdated: widgetCache.updated,
1919
+ widgetDeleted: widgetCache.deleted,
1920
+ }})
1921
+ ```
1922
+
1923
+ It patches stitchkit's `Paginated<T>` list envelope (plain or an infinite list
1924
+ of pages) and honours the same `isFresh` echo guard. It deliberately does **not**
1925
+ flatten pages or add a `useAllX` surface — flattening stays in the component;
1926
+ this only keeps the cache correct.
1927
+
1861
1928
  ## Raw binary lane (Bun)
1862
1929
 
1863
1930
  Socket.IO carries binary fine — for most streams a binary event (`pcm(frame)`)
@@ -2242,6 +2309,54 @@ onError: (ctx, err) => {
2242
2309
  }
2243
2310
  ```
2244
2311
 
2312
+ ## Domain errors — `defineErrors`
2313
+
2314
+ Declaring your app's error codes once gives you typed throwers on the server and
2315
+ a code table the client matches with autocomplete — instead of reading the raw
2316
+ `message` string (which breaks the moment a code expects a string but gets an
2317
+ object):
2318
+
2319
+ ```ts
2320
+ export const { errors, codes, isCode } = defineErrors({
2321
+ SESSION_NOT_FOUND: 404,
2322
+ QUOTA_EXCEEDED: 429,
2323
+ })
2324
+
2325
+ // server — a typed thrower, the right HTTP status baked in
2326
+ throw errors.SESSION_NOT_FOUND('no such session')
2327
+
2328
+ // client — match the code, never a magic string
2329
+ if (err instanceof ApiError && err.code === codes.SESSION_NOT_FOUND) { … }
2330
+ ```
2331
+
2332
+ The `code` rides through unchanged in both the HTTP envelope and the MCP tool
2333
+ result, so one vocabulary covers every transport. The codes are yours; the core
2334
+ stays domain-free.
2335
+
2336
+ ## `createErrorHook`
2337
+
2338
+ `createErrorHook` is the code-map above, packaged — you supply the exhaustive
2339
+ `codeMap` and the envelope shape, it does the normalisation (including the
2340
+ never-leak-an-internal-message rule for a raw throw):
2341
+
2342
+ ```ts
2343
+ const onError = createErrorHook({
2344
+ codeMap: {
2345
+ BAD_REQUEST: 'bad_request', VALIDATION_ERROR: 'bad_request',
2346
+ UNAUTHORIZED: 'unauthenticated', FORBIDDEN: 'forbidden',
2347
+ NOT_FOUND: 'not_found', METHOD_NOT_ALLOWED: 'not_found',
2348
+ CONFLICT: 'conflict', RATE_LIMITED: 'rate_limited',
2349
+ INTERNAL_SERVER_ERROR: 'internal',
2350
+ } satisfies Record<StitchErrorCode, string>,
2351
+ render: (info) => ({ ok: false, error: { code: info.code, message: info.message } }),
2352
+ })
2353
+
2354
+ createServer({ services, hooks: { onError } })
2355
+ ```
2356
+
2357
+ Codes you threw yourself (not stitchkit's) pass through `codeMap` unchanged; the
2358
+ `satisfies Record<StitchErrorCode, …>` keeps the map exhaustive across upgrades.
2359
+
2245
2360
 
2246
2361
  ==============================================================================
2247
2362
  # Guide: Observability (docs/guide/observability.md)
@@ -2414,6 +2529,11 @@ fresh root `traceparent` on every request, which the server then continues. The
2414
2529
  trace helpers themselves are browser-safe and also exported from the root
2415
2530
  `stitchkit` entry — a custom client can format its own header.
2416
2531
 
2532
+ The default CORS allow-list already permits `traceparent` / `tracestate`, so a
2533
+ cross-origin `trace: true` client works out of the box. If you set a custom
2534
+ `cors.headers`, extend `DEFAULT_CORS_ALLOW_HEADERS` rather than replacing it, or
2535
+ the preflight will reject the trace header.
2536
+
2417
2537
  > **Span ids live in the request context, not on `ctx`.** The handler `ctx`
2418
2538
  > carries a single `traceId`; the full `{ traceId, spanId, parentSpanId }` is on
2419
2539
  > the observability request context. To stamp `spanId` / `parentSpanId` into an
@@ -2938,6 +3058,8 @@ from the root `stitchkit`.
2938
3058
  | Export | Kind | Summary |
2939
3059
  |--------|------|---------|
2940
3060
  | `defineContract` | function | declare a contract — [guide](../guide/contracts.md#definecontract) |
3061
+ | `createContractFactory` | function | a `defineContract` with a required, typed `scope` — [guide](../guide/contracts.md#scope) |
3062
+ | `ScopedDefineContract` | _type_ | the `defineContract` `createContractFactory` returns |
2941
3063
  | `ALL_TRANSPORTS` | constant | `['HTTP', 'MCP', 'AGENT', 'CLI']` |
2942
3064
  | `ContractDef` | _type_ | a defined contract |
2943
3065
  | `ContractMeta` | _type_ | a contract's `prefix` + optional `scope` |
@@ -2954,6 +3076,8 @@ from the root `stitchkit`.
2954
3076
  | `ScopedEndpointFn` | _type_ | one method's signature with the consumed keys folded in |
2955
3077
  | `MultipartFile` | _type_ | a `multipart` file field — `Blob \| FileDescriptor` |
2956
3078
  | `FileDescriptor` | _type_ | a React Native / Expo file — `{ uri, name, type }` |
3079
+ | `EndpointToolAnnotations` | _type_ | MCP behavioural hints on an endpoint (`readOnlyHint` / `destructiveHint` / `title`) |
3080
+ | `EndpointUiMeta` | _type_ | MCP Apps widget metadata on an endpoint |
2957
3081
 
2958
3082
  ### Errors
2959
3083
 
@@ -2968,6 +3092,9 @@ from the root `stitchkit`.
2968
3092
  | `conflict` | function | throw `409 CONFLICT` |
2969
3093
  | `rateLimited` | function | throw `429 RATE_LIMITED` |
2970
3094
  | `appError` | function | throw an `AppError` for any code |
3095
+ | `defineErrors` | function | declare domain error codes → typed throwers + a code table — [guide](../guide/auth-and-errors.md#domain-errors--defineerrors) |
3096
+ | `DefinedErrors` | _type_ | the `{ errors, codes, isCode }` handle `defineErrors` returns |
3097
+ | `ErrorThrower` | _type_ | one `defineErrors` thrower — `(message?, details?, hint?) => never` |
2971
3098
  | `STITCH_ERROR_STATUS` | const | `code → HTTP status` map for stitchkit's own error codes — [guide](../guide/auth-and-errors.md#stitch-codes-vs-your-codes) |
2972
3099
  | `StitchErrorCode` | _type_ | a code stitchkit itself emits (`keyof STITCH_ERROR_STATUS`) |
2973
3100
  | `isStitchErrorCode` | function | type guard — is a code one of stitchkit's own? |
@@ -3023,14 +3150,22 @@ Also re-exports the error helpers from `stitchkit/contract`.
3023
3150
  | Export | Kind | Summary |
3024
3151
  |--------|------|---------|
3025
3152
  | `createAuthHook` | function | a scope-enforcing `beforeHandle` hook — [guide](../guide/auth-and-errors.md#createauthhook) |
3153
+ | `createErrorHook` | function | an `onError` hook from a code map + envelope renderer — [guide](../guide/auth-and-errors.md#createerrorhook) |
3154
+ | `ErrorHookConfig` | _type_ | config for `createErrorHook` |
3155
+ | `ResolvedError` | _type_ | the normalised error handed to `createErrorHook`'s `render` |
3026
3156
  | `createBearerResolver` | function | a bearer-token identity resolver |
3157
+ | `signJwt` | function | sign an HS256 JWT |
3027
3158
  | `verifyJwt` | function | verify an HS256 JWT |
3028
3159
  | `extractToken` | function | read a bearer token from header or cookie |
3160
+ | `deriveCodeChallenge` | function | PKCE — derive the `code_challenge` from a verifier |
3161
+ | `verifyPkce` | function | PKCE — verify a verifier against a stored challenge |
3029
3162
  | `AuthHook` | _type_ | the hook `createAuthHook` returns |
3030
3163
  | `AuthHookConfig` | _type_ | config for `createAuthHook` |
3031
3164
  | `AuthRule` | _type_ | `'public' \| 'authenticated' \| predicate` |
3032
3165
  | `BearerResolverConfig` | _type_ | config for `createBearerResolver` |
3033
3166
  | `JwtPayload` | _type_ | a decoded JWT payload |
3167
+ | `SignJwtOptions` | _type_ | options for `signJwt` (expiry, claims) |
3168
+ | `PkceMethod` | _type_ | the PKCE challenge method — `'S256' \| 'plain'` |
3034
3169
 
3035
3170
  ### Cookies & CORS
3036
3171
 
@@ -3041,6 +3176,7 @@ Also re-exports the error helpers from `stitchkit/contract`.
3041
3176
  | `serializeCookie` | function | build a `Set-Cookie` value |
3042
3177
  | `corsHeaders` | function | compute CORS response headers |
3043
3178
  | `corsPreflightResponse` | function | build a preflight `Response` |
3179
+ | `DEFAULT_CORS_ALLOW_HEADERS` | const | the default `Access-Control-Allow-Headers` (incl. `traceparent`) — extend it when overriding `cors.headers` |
3044
3180
  | `CookieDef` | _type_ | the `defineCookie` handle |
3045
3181
  | `CookieOptions` | _type_ | cookie attributes |
3046
3182
  | `CorsConfig` | _type_ | CORS policy |
@@ -3073,9 +3209,11 @@ Also re-exports the error helpers from `stitchkit/contract`.
3073
3209
  | `generateTraceId` | function | a fresh trace id |
3074
3210
  | `resolveTraceId` | function | the default per-request trace-id resolver |
3075
3211
  | `extractIp` | function | the caller IP from a request |
3212
+ | `resolveSocketIp` | function | the caller IP for a Socket.IO handshake (proxy-aware) |
3076
3213
  | `getClientInfo` | function | caller IP + user-agent |
3077
3214
  | `EventBus` | _type_ | the `createEventBus` handle |
3078
3215
  | `RateLimitConfig` | _type_ | config for `createRateLimiter` |
3216
+ | `ClientIpOptions` | _type_ | trusted-proxy config for `extractIp` / `resolveSocketIp` |
3079
3217
  | `ParseSSEOptions` | _type_ | options for `parseSSE` |
3080
3218
 
3081
3219
  ### OpenAPI
@@ -3115,6 +3253,8 @@ audit event. See the [Observability guide](../guide/observability.md).
3115
3253
  | `getTraceId` | function | the active trace id — pass as `traceId` to `createServer` |
3116
3254
  | `getUserId` | function | the active user id, once auth has resolved it |
3117
3255
  | `setRequestUser` | function | attach the resolved user to the active context |
3256
+ | `setRequestEndpoint` | function | attach the resolved endpoint identity to the active context |
3257
+ | `setRequestDimensions` | function | attach custom audit dimensions to the active context |
3118
3258
  | `setRequestError` | function | record the error outcome on the active context |
3119
3259
  | `runWithRequestContext` | function | run a function inside a given context |
3120
3260
  | `RequestContext` | _type_ | the per-request record |
@@ -3177,11 +3317,98 @@ Server-only. Turns contracts into MCP and AI-agent tools. Needs the
3177
3317
  | `ToolExtend` | _type_ | extra-args extension for `mountMcp` / `mountAgent` |
3178
3318
  | `ToolLifecycle` | _type_ | `beforeHandle` / `afterHandle` gate for tool calls — [guide](../guide/mcp-and-agents.md#guarding-tools--lifecycle) |
3179
3319
  | `ToolCallHooks` | _type_ | `beforeToolCall` / `afterToolCall` observability hooks |
3320
+ | `ErrorHintFn` | _type_ | `(toolName, errorCode) => string \| null` — a per-tool recovery hint, shared by every mount |
3180
3321
  | `ToolResult` | _type_ | the result of one tool call |
3181
3322
  | `ToolNameEntry` | _type_ | one `listToolNames` row — `{ name, service, method, transports }` |
3182
3323
  | `IncompatibleSchemaPolicy` | _type_ | `'throw' \| 'skip' \| 'warn'` |
3183
3324
  | `McpMediaContent` | _type_ | a multimodal MCP content item |
3184
3325
 
3326
+ ### Native tools
3327
+
3328
+ Generic host-supplied tools mounted onto a server — not derived from a contract.
3329
+
3330
+ | Export | Kind | Summary |
3331
+ |--------|------|---------|
3332
+ | `mountDownload` | function | a "download a URL to disk" tool (SSRF-guarded, size-capped) |
3333
+ | `mountUpload` | function | an "upload a local file" tool |
3334
+ | `mountWait` | function | a generic `--wait`-style polling tool |
3335
+ | `DownloadToolConfig` | _type_ | config for `mountDownload` |
3336
+ | `UploadToolConfig` | _type_ | config for `mountUpload` |
3337
+ | `WaitToolConfig` | _type_ | config for `mountWait` |
3338
+
3339
+ ### OAuth 2.1 provider
3340
+
3341
+ A native remote-connector auth surface for MCP — [guide](../guide/mcp-and-agents.md#oauth-21--a-native-remote-connector).
3342
+
3343
+ | Export | Kind | Summary |
3344
+ |--------|------|---------|
3345
+ | `mountOAuthProvider` | function | the OAuth 2.1 provider routes (DCR, PKCE, token) |
3346
+ | `oauthProtectedResourceRoute` | function | the RFC 9728 protected-resource-metadata route |
3347
+ | `protectedResourceMetadataUrl` | function | build the metadata URL for a resource |
3348
+ | `wwwAuthenticateHeader` | function | build the `WWW-Authenticate` challenge header |
3349
+ | `PROTECTED_RESOURCE_PATH` | const | the well-known metadata path |
3350
+ | `OAuthProviderConfig` | _type_ | config for `mountOAuthProvider` |
3351
+ | `ProtectedResourceConfig` | _type_ | config for `oauthProtectedResourceRoute` |
3352
+ | `AuthCodeData` | _type_ | a stored authorization-code record |
3353
+ | `AuthRequest` | _type_ | a parsed authorization request |
3354
+ | `ClientMetadata` | _type_ | dynamic-client-registration metadata |
3355
+ | `RefreshData` | _type_ | a stored refresh-token record |
3356
+ | `RegisteredClient` | _type_ | a registered OAuth client |
3357
+
3358
+ ### MCP Apps (widgets)
3359
+
3360
+ Interactive MCP resources — [ADR 0019](../decisions/0019-generic-native-tools.md).
3361
+
3362
+ | Export | Kind | Summary |
3363
+ |--------|------|---------|
3364
+ | `mountMcpResource` | function | mount an MCP Apps widget resource |
3365
+ | `inlineMcpAppBundle` | function | inline a built widget bundle into a resource |
3366
+ | `EXT_APPS_BUNDLE_PLACEHOLDER` | const | the placeholder token `inlineMcpAppBundle` replaces |
3367
+ | `RESOURCE_MIME_TYPE` | const | the MCP Apps resource MIME type |
3368
+ | `McpResourceDef` | _type_ | an MCP Apps resource definition |
3369
+ | `McpAppResourceMeta` | _type_ | resource `_meta` for an MCP App |
3370
+ | `McpAppCsp` | _type_ | the widget content-security policy |
3371
+
3372
+ ### Introspection & internals
3373
+
3374
+ Advanced building blocks — the shared machinery the mounts are built on.
3375
+
3376
+ | Export | Kind | Summary |
3377
+ |--------|------|---------|
3378
+ | `collectTools` | function | resolve a service's methods to mountable tools (the shared resolver) |
3379
+ | `createToolLogger` | function | a ready `afterToolCall` that logs every tool call — [guide](../guide/mcp-and-agents.md#logging-tool-calls--createtoollogger) |
3380
+ | `summarizeTransports` | function | per-transport operation counts for a boot-time summary |
3381
+ | `buildToolManifest` | function | a searchable `{ name, description, inputSchema }` manifest for a `tool_search` tool |
3382
+ | `ToolLoggerConfig` | _type_ | config for `createToolLogger` |
3383
+ | `ToolCallRecord` | _type_ | the structured record `createToolLogger` passes to `onRecord` |
3384
+ | `TransportSummary` | _type_ | the result of `summarizeTransports` |
3385
+ | `TransportCounts` | _type_ | per-transport counts (`{ HTTP, MCP, AGENT, CLI }`) |
3386
+ | `coerceJsonArgs` | function | coerce JSON-stringified array/object tool arguments |
3387
+ | `flattenDiscriminatedUnion` | function | flatten one discriminated union into a single object schema |
3388
+ | `flattenUnionsDeep` | function | flatten discriminated unions at every depth (advertised schema only) |
3389
+ | `MountableTool` | _type_ | one contract method resolved for mounting |
3390
+ | `ToolManifestEntry` | _type_ | one `buildToolManifest` row |
3391
+
3392
+ ---
3393
+
3394
+ ## `stitchkit/node`
3395
+
3396
+ Server-only, for Node ≥ 22 (Bun uses `stitchkit/server`). The runtime-agnostic
3397
+ core plus a Node HTTP adapter — [ADR 0013](../decisions/0013-runtime-agnostic-core.md),
3398
+ [deployment guide](../guide/testing-and-deployment.md#node). Re-exports the
3399
+ runtime-agnostic pieces of `stitchkit/server` and the error helpers.
3400
+
3401
+ | Export | Kind | Summary |
3402
+ |--------|------|---------|
3403
+ | `serveNode` | function | build the router and start a Node HTTP server (via `srvx`) |
3404
+ | `createHandler` | function | the router as a bare `(req) => Response` (same as `/server`) |
3405
+ | `createSocketIOServer` | function | the typed Socket.IO server (same as `/server`) |
3406
+ | `implement` / `createImplement` | function | bind a contract to typed handlers (same as `/server`) |
3407
+ | `NodeServerConfig` | _type_ | config for `serveNode` |
3408
+ | `NodeServerHandle` | _type_ | the `serveNode` handle (`{ port, stop }`) |
3409
+ | `HandlerConfig` / `ServiceDef` / `RawRoute` / `RawRouteContext` / `SocketIOServerConfig` / `SocketIOServerHandle` | _type_ | re-exported from `/server` |
3410
+ | `AppError` + `appError` / `badRequest` / `unauthorized` / `forbidden` / `notFound` / `conflict` / `rateLimited` | — | error helpers (same as `/contract`) |
3411
+
3185
3412
  ---
3186
3413
 
3187
3414
  ## `stitchkit/cli`
@@ -3216,6 +3443,10 @@ and `react-query-kit` peers.
3216
3443
  |--------|------|---------|
3217
3444
  | `createCursorQuery` | function | a cursor-paginated infinite query — [guide](../guide/client.md#cursor-pagination) |
3218
3445
  | `createCacheBridge` | function | sync socket events into the Query cache — [guide](../guide/realtime.md#cache-bridge) |
3446
+ | `createEntityCacheHandlers` | function | created/updated/deleted cache handlers for one entity — [guide](../guide/realtime.md#entity-cache-handlers) |
3447
+ | `EntityCacheConfig` | _type_ | config for `createEntityCacheHandlers` |
3448
+ | `EntityCacheHandlers` | _type_ | the `{ created, updated, deleted }` handlers it returns |
3449
+ | `DeletedPayload` | _type_ | a `deleted` event payload — the entity or a bare `{ id }` |
3219
3450
  | `CursorQueryConfig` | _type_ | config for `createCursorQuery` |
3220
3451
  | `CacheBridge` | _type_ | the `createCacheBridge` handle |
3221
3452
  | `CacheBridgeConfig` | _type_ | config for `createCacheBridge` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
5
5
  "keywords": [
6
6
  "bun",