stitchkit 0.19.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/{index-wpbn133x.js → index-f5e4efj1.js} +2 -7
- package/dist/node.js +1 -1
- 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 +12 -8
- package/dist/server/multipart.d.ts +11 -3
- package/dist/server/multipart.d.ts.map +1 -1
- package/llms-full.txt +40 -2
- package/package.json +1 -1
|
@@ -73,13 +73,8 @@ async function parseMultipart(req, fileField, fieldsSchema, maxBytes = DEFAULT_M
|
|
|
73
73
|
continue;
|
|
74
74
|
if (isUnsafeKey(key))
|
|
75
75
|
continue;
|
|
76
|
-
if (typeof value === "string")
|
|
77
|
-
|
|
78
|
-
fields[key] = safeJsonParse(value);
|
|
79
|
-
} catch {
|
|
80
|
-
fields[key] = value;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
76
|
+
if (typeof value === "string")
|
|
77
|
+
fields[key] = value;
|
|
83
78
|
}
|
|
84
79
|
return { file, fields: fieldsSchema ? fieldsSchema.parse(fields) : fields };
|
|
85
80
|
}
|
package/dist/node.js
CHANGED
|
@@ -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
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
socketIoLane,
|
|
11
11
|
staticRoute,
|
|
12
12
|
webSocketLane
|
|
13
|
-
} from "../index-
|
|
13
|
+
} from "../index-f5e4efj1.js";
|
|
14
14
|
import {
|
|
15
15
|
createAuthHook,
|
|
16
16
|
createBearerResolver,
|
|
@@ -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,
|
|
@@ -7,9 +7,17 @@ export interface MultipartResult {
|
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
9
|
* Parse a `multipart/form-data` request — extract the file at `fileField` and
|
|
10
|
-
* the remaining fields
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* the remaining fields, then validate them with `fieldsSchema` when given.
|
|
11
|
+
* Rejects with a 400 if the file is missing or the upload exceeds `maxBytes`
|
|
12
|
+
* (default 25 MB).
|
|
13
|
+
*
|
|
14
|
+
* A multipart text field is always a **string** (per the spec) and is handed to
|
|
15
|
+
* the schema as one — the schema decides its type, exactly as with query params:
|
|
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.
|
|
19
|
+
* Content is never sniffed to guess a type — the contract owns the type, not the
|
|
20
|
+
* value (so an id like `'33111715'` never turns into a number under a `z.string()`).
|
|
13
21
|
*/
|
|
14
22
|
export declare function parseMultipart(req: Request, fileField: string, fieldsSchema?: ZodType<unknown>, maxBytes?: number): Promise<MultipartResult>;
|
|
15
23
|
//# sourceMappingURL=multipart.d.ts.map
|
|
@@ -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
|
|
|
@@ -383,6 +384,25 @@ upload: {
|
|
|
383
384
|
The client sends a `multipart/form-data` request; the field value must be a
|
|
384
385
|
`Blob`. See [HTTP server → multipart](./server.md#multipart).
|
|
385
386
|
|
|
387
|
+
### Multipart text fields
|
|
388
|
+
|
|
389
|
+
Any non-file fields sent alongside the file are validated by the endpoint's
|
|
390
|
+
`input` schema. A multipart text field is **always a string** (per the spec) —
|
|
391
|
+
the schema owns its type, exactly as with [query input](#query-input-get--delete):
|
|
392
|
+
|
|
393
|
+
```ts
|
|
394
|
+
input: z.object({
|
|
395
|
+
id: z.string(), // an id like '33111715' stays a string
|
|
396
|
+
count: z.coerce.number(), // '5' → 5
|
|
397
|
+
active: z.stringbool(), // 'true' → true, 'false' → false
|
|
398
|
+
meta: z.preprocess((v) => JSON.parse(String(v)), MetaSchema), // opt a field into JSON
|
|
399
|
+
})
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
The content is never sniffed to guess a type — a field is a string until the
|
|
403
|
+
schema coerces it. Send a JSON blob as a stringified field and parse it with
|
|
404
|
+
`z.preprocess`; do not rely on the framework to auto-decode it.
|
|
405
|
+
|
|
386
406
|
## Pagination
|
|
387
407
|
|
|
388
408
|
Every list endpoint should return the cursor envelope — one shape, one infinite-
|
|
@@ -2357,6 +2377,21 @@ createServer({ services, hooks: { onError } })
|
|
|
2357
2377
|
Codes you threw yourself (not stitchkit's) pass through `codeMap` unchanged; the
|
|
2358
2378
|
`satisfies Record<StitchErrorCode, …>` keeps the map exhaustive across upgrades.
|
|
2359
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
|
+
|
|
2360
2395
|
|
|
2361
2396
|
==============================================================================
|
|
2362
2397
|
# Guide: Observability (docs/guide/observability.md)
|
|
@@ -3131,6 +3166,9 @@ Also re-exports the error helpers from `stitchkit/contract`.
|
|
|
3131
3166
|
| `ByteRange` | _type_ | an inclusive `{ start, end }` byte range |
|
|
3132
3167
|
| `respondJson` | function | a raw route's JSON response (`204` for null/undefined) |
|
|
3133
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 |
|
|
3134
3172
|
| `parseBody` | function | parse + Zod-validate a JSON body → `data` or `null` (no throw) |
|
|
3135
3173
|
| `HandlerConfig` | _type_ | config for `createHandler` (runtime-agnostic) |
|
|
3136
3174
|
| `BunServerConfig` | _type_ | config for `createServer` (Bun) |
|
package/package.json
CHANGED