stitchkit 0.20.0 → 0.21.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/server/error-hook.d.ts +5 -1
- package/dist/server/error-hook.d.ts.map +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +11 -7
- package/dist/server/multipart.d.ts +3 -2
- package/dist/server/multipart.d.ts.map +1 -1
- package/llms-full.txt +22 -3
- package/package.json +1 -1
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
* of leaking stitchkit's code to the wire. The envelope stays app-owned via
|
|
10
10
|
* `render`, so the core prescribes no domain shape (ADR 0002).
|
|
11
11
|
*
|
|
12
|
+
* The thrown value is classified through the framework's own `normalizeError`
|
|
13
|
+
* first, so a `ZodError` (invalid input) is an honest `VALIDATION_ERROR` 400 —
|
|
14
|
+
* not a 500 — exactly as the framework default would render it.
|
|
15
|
+
*
|
|
12
16
|
* ```ts
|
|
13
17
|
* const onError = createErrorHook({
|
|
14
18
|
* codeMap: {
|
|
@@ -31,7 +35,7 @@ export interface ResolvedError {
|
|
|
31
35
|
code: string;
|
|
32
36
|
/** HTTP status. */
|
|
33
37
|
status: number;
|
|
34
|
-
/** Safe message — the `AppError`
|
|
38
|
+
/** Safe message — the `AppError` / `ZodError` summary, or a generic string for a raw throw. */
|
|
35
39
|
message: string;
|
|
36
40
|
/** Structured details, when the thrown `AppError` carried them. */
|
|
37
41
|
details?: Record<string, unknown>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-hook.d.ts","sourceRoot":"","sources":["../../src/server/error-hook.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"error-hook.d.ts","sourceRoot":"","sources":["../../src/server/error-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAEtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,uEAAuE;AACvE,MAAM,WAAW,aAAa;IAC5B,wFAAwF;IACxF,IAAI,EAAE,MAAM,CAAC;IACb,mBAAmB;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,+FAA+F;IAC/F,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,6DAA6D;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,oCAAoC;AACpC,MAAM,WAAW,eAAe,CAAC,SAAS,SAAS,MAAM,GAAG,MAAM;IAChE;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,eAAe,EAAE,SAAS,CAAC,CAAC;IAC7C,uDAAuD;IACvD,MAAM,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC;IACzC,yEAAyE;IACzE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;CACzD;AAED,mEAAmE;AACnE,wBAAgB,eAAe,CAAC,SAAS,SAAS,MAAM,GAAG,MAAM,EAC/D,MAAM,EAAE,eAAe,CAAC,SAAS,CAAC,GACjC,WAAW,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,CAyBxC"}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { AppError, appError, badRequest, conflict, forbidden, isStitchErrorCode, notFound, rateLimited, STITCH_ERROR_STATUS, type StitchErrorCode, unauthorized, } from '../contract';
|
|
2
|
+
export { errorCode, formatZodError, normalizeError } from '../internal/errors';
|
|
2
3
|
export { cacheHeaders, createCache } from './cache';
|
|
3
4
|
export { createHandler, createServer } from './create';
|
|
4
5
|
export { createErrorHook, type ErrorHookConfig, type ResolvedError, } from './error-hook';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,SAAS,EACT,iBAAiB,EACjB,QAAQ,EACR,WAAW,EACX,mBAAmB,EACnB,KAAK,eAAe,EACpB,YAAY,GACb,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,SAAS,EACT,iBAAiB,EACjB,QAAQ,EACR,WAAW,EACX,mBAAmB,EACnB,KAAK,eAAe,EACpB,YAAY,GACb,MAAM,aAAa,CAAC;AAIrB,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,EACL,eAAe,EACf,KAAK,eAAe,EACpB,KAAK,aAAa,GACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,cAAc,EAAE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EACL,KAAK,SAAS,EACd,cAAc,EACd,KAAK,gBAAgB,EACrB,SAAS,EACT,QAAQ,GACT,MAAM,QAAQ,CAAC;AAChB,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,oBAAoB,EACzB,cAAc,EACd,oBAAoB,EACpB,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,OAAO,EACP,SAAS,GACV,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,YAAY,EACZ,YAAY,EACZ,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,KAAK,UAAU,EACf,WAAW,EACX,qBAAqB,EACrB,0BAA0B,GAC3B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,mBAAmB,EAAE,KAAK,UAAU,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACrF,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EACL,uBAAuB,EACvB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,YAAY,GACb,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,iBAAiB,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAC9D,OAAO,EACL,KAAK,eAAe,EACpB,SAAS,EACT,eAAe,EACf,aAAa,EACb,eAAe,EACf,cAAc,GACf,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,YAAY,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,KAAK,eAAe,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrE,YAAY,EACV,SAAS,EACT,eAAe,EACf,aAAa,EACb,QAAQ,EACR,cAAc,EACd,SAAS,EACT,QAAQ,EACR,eAAe,EACf,UAAU,EACV,iBAAiB,EACjB,UAAU,EACV,YAAY,GACb,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,KAAK,YAAY,EACjB,wBAAwB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,aAAa,EAClB,aAAa,GACd,MAAM,aAAa,CAAC"}
|
package/dist/server/index.js
CHANGED
|
@@ -39,7 +39,9 @@ import {
|
|
|
39
39
|
appError,
|
|
40
40
|
badRequest,
|
|
41
41
|
conflict,
|
|
42
|
+
errorCode,
|
|
42
43
|
forbidden,
|
|
44
|
+
formatZodError,
|
|
43
45
|
isStitchErrorCode,
|
|
44
46
|
normalizeError,
|
|
45
47
|
notFound,
|
|
@@ -118,15 +120,14 @@ function cacheHeaders(maxAge, scope = "public") {
|
|
|
118
120
|
// src/server/error-hook.ts
|
|
119
121
|
function createErrorHook(config) {
|
|
120
122
|
return (_ctx, error) => {
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
-
const code = config.codeMap && isStitchErrorCode(rawCode) ? config.codeMap[rawCode] : rawCode;
|
|
123
|
+
const appErr = normalizeError(error);
|
|
124
|
+
const code = config.codeMap && isStitchErrorCode(appErr.code) ? config.codeMap[appErr.code] : appErr.code;
|
|
124
125
|
const info = {
|
|
125
126
|
code,
|
|
126
|
-
status:
|
|
127
|
-
message:
|
|
128
|
-
details:
|
|
129
|
-
hint:
|
|
127
|
+
status: appErr.status,
|
|
128
|
+
message: appErr.message,
|
|
129
|
+
details: appErr.details,
|
|
130
|
+
hint: appErr.hint
|
|
130
131
|
};
|
|
131
132
|
config.onError?.(error, info);
|
|
132
133
|
return new Response(JSON.stringify(config.render(info)), {
|
|
@@ -639,15 +640,18 @@ export {
|
|
|
639
640
|
parseBody,
|
|
640
641
|
openApiRoute,
|
|
641
642
|
notFound,
|
|
643
|
+
normalizeError,
|
|
642
644
|
isStitchErrorCode,
|
|
643
645
|
implement,
|
|
644
646
|
getClientInfo,
|
|
645
647
|
generateTraceId,
|
|
646
648
|
generateOpenApiDocument,
|
|
649
|
+
formatZodError,
|
|
647
650
|
forbidden,
|
|
648
651
|
extractToken,
|
|
649
652
|
extractIp,
|
|
650
653
|
errorResponse2 as errorResponse,
|
|
654
|
+
errorCode,
|
|
651
655
|
deriveCodeChallenge,
|
|
652
656
|
defineCookie,
|
|
653
657
|
createSocketIOServer,
|
|
@@ -13,8 +13,9 @@ export interface MultipartResult {
|
|
|
13
13
|
*
|
|
14
14
|
* A multipart text field is always a **string** (per the spec) and is handed to
|
|
15
15
|
* the schema as one — the schema decides its type, exactly as with query params:
|
|
16
|
-
* `z.coerce.number()` for a number, `z.
|
|
17
|
-
* `z.
|
|
16
|
+
* `z.coerce.number()` for a number, `z.stringbool()` for a boolean (NOT
|
|
17
|
+
* `z.coerce.boolean()`, which is `Boolean(str)` — `'false'` would become `true`),
|
|
18
|
+
* and `z.preprocess((v) => JSON.parse(String(v)), Schema)` to opt a field into JSON.
|
|
18
19
|
* Content is never sniffed to guess a type — the contract owns the type, not the
|
|
19
20
|
* value (so an id like `'33111715'` never turns into a number under a `z.string()`).
|
|
20
21
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multipart.d.ts","sourceRoot":"","sources":["../../src/server/multipart.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAInC,mFAAmF;AACnF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,IAAI,CAAC;IACX,8EAA8E;IAC9E,MAAM,EAAE,OAAO,CAAC;CACjB;AA0CD
|
|
1
|
+
{"version":3,"file":"multipart.d.ts","sourceRoot":"","sources":["../../src/server/multipart.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAInC,mFAAmF;AACnF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,IAAI,CAAC;IACX,8EAA8E;IAC9E,MAAM,EAAE,OAAO,CAAC;CACjB;AA0CD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,OAAO,EACZ,SAAS,EAAE,MAAM,EACjB,YAAY,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,EAC/B,QAAQ,SAA2B,GAClC,OAAO,CAAC,eAAe,CAAC,CAyB1B"}
|
package/llms-full.txt
CHANGED
|
@@ -289,8 +289,9 @@ await api.search({ q: 'x', filter: { status: 'active' } })
|
|
|
289
289
|
|
|
290
290
|
Flatten the field (`status: 'active'`) or move the operation to a body verb
|
|
291
291
|
(`POST`). Remember the server parses query values from **strings** — use
|
|
292
|
-
`z.coerce.number()`
|
|
293
|
-
|
|
292
|
+
`z.coerce.number()` for numbers and `z.stringbool()` for booleans in a
|
|
293
|
+
query-input schema (not `z.coerce.boolean()`, which is `Boolean(str)`, so
|
|
294
|
+
`'false'` would become `true`).
|
|
294
295
|
|
|
295
296
|
## Transports
|
|
296
297
|
|
|
@@ -393,7 +394,7 @@ the schema owns its type, exactly as with [query input](#query-input-get--delete
|
|
|
393
394
|
input: z.object({
|
|
394
395
|
id: z.string(), // an id like '33111715' stays a string
|
|
395
396
|
count: z.coerce.number(), // '5' → 5
|
|
396
|
-
active: z.
|
|
397
|
+
active: z.stringbool(), // 'true' → true, 'false' → false
|
|
397
398
|
meta: z.preprocess((v) => JSON.parse(String(v)), MetaSchema), // opt a field into JSON
|
|
398
399
|
})
|
|
399
400
|
```
|
|
@@ -2376,6 +2377,21 @@ createServer({ services, hooks: { onError } })
|
|
|
2376
2377
|
Codes you threw yourself (not stitchkit's) pass through `codeMap` unchanged; the
|
|
2377
2378
|
`satisfies Record<StitchErrorCode, …>` keeps the map exhaustive across upgrades.
|
|
2378
2379
|
|
|
2380
|
+
Invalid input (a `ZodError`) is classified as `VALIDATION_ERROR` 400 before it
|
|
2381
|
+
reaches `render` — a client fault is an honest 400, not a 500. If you write a
|
|
2382
|
+
**bespoke** `onError` instead of using `createErrorHook`, run the thrown value
|
|
2383
|
+
through the exported `normalizeError` to get the same classification (a raw
|
|
2384
|
+
`ZodError` reaches your hook untouched, so the framework can inspect it):
|
|
2385
|
+
|
|
2386
|
+
```ts
|
|
2387
|
+
import { normalizeError } from 'stitchkit/server'
|
|
2388
|
+
|
|
2389
|
+
onError: (ctx, err) => {
|
|
2390
|
+
const e = normalizeError(err) // ZodError → VALIDATION_ERROR 400, else generic 500
|
|
2391
|
+
return jsonError(e.code, e.status, e.message)
|
|
2392
|
+
}
|
|
2393
|
+
```
|
|
2394
|
+
|
|
2379
2395
|
|
|
2380
2396
|
==============================================================================
|
|
2381
2397
|
# Guide: Observability (docs/guide/observability.md)
|
|
@@ -3150,6 +3166,9 @@ Also re-exports the error helpers from `stitchkit/contract`.
|
|
|
3150
3166
|
| `ByteRange` | _type_ | an inclusive `{ start, end }` byte range |
|
|
3151
3167
|
| `respondJson` | function | a raw route's JSON response (`204` for null/undefined) |
|
|
3152
3168
|
| `errorResponse` | function | any thrown value → the framework error envelope + `x-request-id` |
|
|
3169
|
+
| `normalizeError` | function | any thrown value → an `AppError` (`ZodError` → `VALIDATION_ERROR` 400, else generic 500) — the framework's canonical classification, for a bespoke `onError` |
|
|
3170
|
+
| `errorCode` | function | the stable error code for a thrown value (side-effect-free — for log attribution) |
|
|
3171
|
+
| `formatZodError` | function | a `ZodError` → a readable, field-summarised string |
|
|
3153
3172
|
| `parseBody` | function | parse + Zod-validate a JSON body → `data` or `null` (no throw) |
|
|
3154
3173
|
| `HandlerConfig` | _type_ | config for `createHandler` (runtime-agnostic) |
|
|
3155
3174
|
| `BunServerConfig` | _type_ | config for `createServer` (Bun) |
|
package/package.json
CHANGED