stitchkit 0.19.0 → 0.20.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.
@@ -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
- try {
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
@@ -3,7 +3,7 @@ import {
3
3
  createImplement,
4
4
  createSocketIOServer,
5
5
  implement
6
- } from "./index-wpbn133x.js";
6
+ } from "./index-f5e4efj1.js";
7
7
  import"./index-tje0q6gp.js";
8
8
  import {
9
9
  AppError,
@@ -10,7 +10,7 @@ import {
10
10
  socketIoLane,
11
11
  staticRoute,
12
12
  webSocketLane
13
- } from "../index-wpbn133x.js";
13
+ } from "../index-f5e4efj1.js";
14
14
  import {
15
15
  createAuthHook,
16
16
  createBearerResolver,
@@ -7,9 +7,16 @@ 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 (each JSON-decoded, then validated by `fieldsSchema`
11
- * when given). Rejects with a 400 if the file is missing or the upload exceeds
12
- * `maxBytes` (default 25 MB).
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.coerce.boolean()` for a boolean, and
17
+ * `z.preprocess((v) => JSON.parse(String(v)), Schema)` to opt a field into JSON.
18
+ * Content is never sniffed to guess a type — the contract owns the type, not the
19
+ * value (so an id like `'33111715'` never turns into a number under a `z.string()`).
13
20
  */
14
21
  export declare function parseMultipart(req: Request, fileField: string, fieldsSchema?: ZodType<unknown>, maxBytes?: number): Promise<MultipartResult>;
15
22
  //# 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;;;;;GAKG;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,CA6B1B"}
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;;;;;;;;;;;;GAYG;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
@@ -383,6 +383,25 @@ upload: {
383
383
  The client sends a `multipart/form-data` request; the field value must be a
384
384
  `Blob`. See [HTTP server → multipart](./server.md#multipart).
385
385
 
386
+ ### Multipart text fields
387
+
388
+ Any non-file fields sent alongside the file are validated by the endpoint's
389
+ `input` schema. A multipart text field is **always a string** (per the spec) —
390
+ the schema owns its type, exactly as with [query input](#query-input-get--delete):
391
+
392
+ ```ts
393
+ input: z.object({
394
+ id: z.string(), // an id like '33111715' stays a string
395
+ count: z.coerce.number(), // '5' → 5
396
+ active: z.coerce.boolean(), // 'true' → true
397
+ meta: z.preprocess((v) => JSON.parse(String(v)), MetaSchema), // opt a field into JSON
398
+ })
399
+ ```
400
+
401
+ The content is never sniffed to guess a type — a field is a string until the
402
+ schema coerces it. Send a JSON blob as a stringified field and parse it with
403
+ `z.preprocess`; do not rely on the framework to auto-decode it.
404
+
386
405
  ## Pagination
387
406
 
388
407
  Every list endpoint should return the cursor envelope — one shape, one infinite-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.19.0",
3
+ "version": "0.20.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",