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.
- package/dist/browser/client.d.ts.map +1 -1
- package/dist/browser/socket-io.d.ts.map +1 -1
- package/dist/cli.js +3 -3
- package/dist/contract/errors-factory.d.ts +18 -0
- package/dist/contract/errors-factory.d.ts.map +1 -0
- package/dist/contract/factory.d.ts +36 -0
- package/dist/contract/factory.d.ts.map +1 -0
- package/dist/contract/index.d.ts +2 -0
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +6 -1
- package/dist/contract/pagination.d.ts.map +1 -1
- package/dist/index-809wc1tt.js +18 -0
- package/dist/{index-tm7dqzxc.js → index-c7nyw0yt.js} +9 -9
- package/dist/{index-dz2k8d5h.js → index-dzx781tm.js} +1 -1
- package/dist/{index-ktn76xz0.js → index-ng2v2ts4.js} +17 -1
- package/dist/index-tje0q6gp.js +44 -0
- package/dist/{index-6qv1fxqy.js → index-wpbn133x.js} +13 -50
- package/dist/{index-yzac2zbh.js → index-x62gnfsk.js} +50 -9
- package/dist/{index-akv8rk4w.js → index-xmncnw8z.js} +2 -2
- package/dist/{index-yv93m4yr.js → index-ynnh9x6h.js} +11 -29
- package/dist/index.js +82 -62
- package/dist/internal/base64url.d.ts +16 -0
- package/dist/internal/base64url.d.ts.map +1 -0
- package/dist/node.js +5 -4
- package/dist/observability/index.js +2 -2
- package/dist/react/entity-cache.d.ts +54 -0
- package/dist/react/entity-cache.d.ts.map +1 -0
- package/dist/react.d.ts +1 -0
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +74 -0
- package/dist/server/create.d.ts.map +1 -1
- package/dist/server/error-hook.d.ts +56 -0
- package/dist/server/error-hook.d.ts.map +1 -0
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +32 -7
- package/dist/server/middleware/auth.d.ts.map +1 -1
- package/dist/server/middleware/cors.d.ts +13 -0
- package/dist/server/middleware/cors.d.ts.map +1 -1
- package/dist/server/middleware/pkce.d.ts +0 -6
- package/dist/server/middleware/pkce.d.ts.map +1 -1
- package/dist/server/router.d.ts +0 -2
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/types.d.ts +0 -8
- package/dist/server/types.d.ts.map +1 -1
- package/dist/tools/agent.d.ts +2 -2
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/cli-format.d.ts +2 -2
- package/dist/tools/cli-format.d.ts.map +1 -1
- package/dist/tools/cli.d.ts +2 -2
- package/dist/tools/cli.d.ts.map +1 -1
- package/dist/tools/execute.d.ts +7 -0
- package/dist/tools/execute.d.ts.map +1 -1
- package/dist/tools/mcp.d.ts +3 -3
- package/dist/tools/mcp.d.ts.map +1 -1
- package/dist/tools/mount.d.ts +3 -3
- package/dist/tools/mount.d.ts.map +1 -1
- package/dist/tools/oauth-metadata.d.ts.map +1 -1
- package/dist/tools/oauth-provider.d.ts.map +1 -1
- package/dist/tools/tool-logger.d.ts +31 -0
- package/dist/tools/tool-logger.d.ts.map +1 -0
- package/dist/tools/transports.d.ts +28 -0
- package/dist/tools/transports.d.ts.map +1 -0
- package/dist/tools.d.ts +3 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +71 -11
- package/llms-full.txt +235 -4
- 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-
|
|
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-
|
|
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-
|
|
28
|
+
} from "./index-ng2v2ts4.js";
|
|
26
29
|
import"./index-khwedj16.js";
|
|
27
30
|
import {
|
|
28
31
|
isRecord,
|
|
29
32
|
typedEntries
|
|
30
|
-
} from "./index-
|
|
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":
|
|
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":
|
|
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
|
|
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
|
|
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/
|
|
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