@miiajs/multipart 0.6.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ruslan Matiushev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # @miiajs/multipart
2
+
3
+ Streaming `multipart/form-data` for MiiaJS - `@Multipart` with a backpressured part iterator, per-file and per-form limits, buffered `ctx.form()`, and `@ValidateForm`.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @miiajs/multipart
9
+ ```
10
+
11
+ ## Documentation
12
+
13
+ **[miiajs.com/docs/packages/multipart](https://miiajs.com/docs/packages/multipart)**
14
+
15
+ ## License
16
+
17
+ MIT
@@ -0,0 +1,19 @@
1
+ export interface ParsedDisposition {
2
+ name?: string;
3
+ filename?: string;
4
+ }
5
+ /**
6
+ * `Content-Disposition` reader for part headers.
7
+ *
8
+ * multipasta 0.2.8 reads the header with fast-content-type-parse, whose
9
+ * quoted-string pattern stops at U+00FF: a single character above it voids the
10
+ * whole header, so `name="файл"; filename="фото.png"` arrives as no name and no
11
+ * filename at all. The raw header survives on `info.headers`, so the bridge
12
+ * takes both parameters from here instead.
13
+ *
14
+ * RFC 7578 prescribes raw UTF-8 for these parameters and advises against
15
+ * RFC 2231/5987 encoding, so `filename*` is ignored in favour of `filename` -
16
+ * which is what the runtime parsers do as well.
17
+ */
18
+ export declare function parseContentDisposition(header: string | string[] | undefined): ParsedDisposition;
19
+ //# sourceMappingURL=content-disposition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-disposition.d.ts","sourceRoot":"","sources":["../src/content-disposition.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAID;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,GAAG,iBAAiB,CAkBhG"}
@@ -0,0 +1,35 @@
1
+ import { scanParams } from './params.js';
2
+ const EMPTY = {};
3
+ /**
4
+ * `Content-Disposition` reader for part headers.
5
+ *
6
+ * multipasta 0.2.8 reads the header with fast-content-type-parse, whose
7
+ * quoted-string pattern stops at U+00FF: a single character above it voids the
8
+ * whole header, so `name="файл"; filename="фото.png"` arrives as no name and no
9
+ * filename at all. The raw header survives on `info.headers`, so the bridge
10
+ * takes both parameters from here instead.
11
+ *
12
+ * RFC 7578 prescribes raw UTF-8 for these parameters and advises against
13
+ * RFC 2231/5987 encoding, so `filename*` is ignored in favour of `filename` -
14
+ * which is what the runtime parsers do as well.
15
+ */
16
+ export function parseContentDisposition(header) {
17
+ // A part with two `content-disposition` headers arrives as an array; the
18
+ // engine reads nothing out of that shape either.
19
+ if (typeof header !== 'string')
20
+ return EMPTY;
21
+ const semi = header.indexOf(';');
22
+ if (semi === -1)
23
+ return EMPTY;
24
+ let name;
25
+ let filename;
26
+ scanParams(header, semi + 1, (key, value) => {
27
+ // Last one wins, as it does in the engine and in the runtime parsers.
28
+ if (key === 'name')
29
+ name = value;
30
+ else if (key === 'filename')
31
+ filename = value;
32
+ });
33
+ return name === undefined && filename === undefined ? EMPTY : { name, filename };
34
+ }
35
+ //# sourceMappingURL=content-disposition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-disposition.js","sourceRoot":"","sources":["../src/content-disposition.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAOxC,MAAM,KAAK,GAAsB,EAAE,CAAA;AAEnC;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAqC;IAC3E,yEAAyE;IACzE,iDAAiD;IACjD,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAE5C,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAChC,IAAI,IAAI,KAAK,CAAC,CAAC;QAAE,OAAO,KAAK,CAAA;IAE7B,IAAI,IAAwB,CAAA;IAC5B,IAAI,QAA4B,CAAA;IAEhC,UAAU,CAAC,MAAM,EAAE,IAAI,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;QAC1C,sEAAsE;QACtE,IAAI,GAAG,KAAK,MAAM;YAAE,IAAI,GAAG,KAAK,CAAA;aAC3B,IAAI,GAAG,KAAK,UAAU;YAAE,QAAQ,GAAG,KAAK,CAAA;IAC/C,CAAC,CAAC,CAAA;IAEF,OAAO,IAAI,KAAK,SAAS,IAAI,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAA;AAClF,CAAC"}
@@ -0,0 +1,16 @@
1
+ export interface ParsedContentType {
2
+ mediaType: string;
3
+ boundary?: string;
4
+ }
5
+ /**
6
+ * The media type of a `Content-Type` header - parameters dropped, trimmed,
7
+ * lower-cased. Read for the request header and for every part header.
8
+ */
9
+ export declare function parseMediaType(header: string): string;
10
+ /**
11
+ * Minimal `Content-Type` reader. Only the media type and the `boundary`
12
+ * parameter are needed: the media type gates the request, the boundary length
13
+ * sizes the tail buffer in the bridge.
14
+ */
15
+ export declare function parseContentType(header: string): ParsedContentType;
16
+ //# sourceMappingURL=content-type.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-type.d.ts","sourceRoot":"","sources":["../src/content-type.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,CAMlE"}
@@ -0,0 +1,33 @@
1
+ import { scanParams } from './params.js';
2
+ /**
3
+ * The media type of a `Content-Type` header - parameters dropped, trimmed,
4
+ * lower-cased. Read for the request header and for every part header.
5
+ */
6
+ export function parseMediaType(header) {
7
+ const semi = header.indexOf(';');
8
+ return (semi === -1 ? header : header.slice(0, semi)).trim().toLowerCase();
9
+ }
10
+ /**
11
+ * Minimal `Content-Type` reader. Only the media type and the `boundary`
12
+ * parameter are needed: the media type gates the request, the boundary length
13
+ * sizes the tail buffer in the bridge.
14
+ */
15
+ export function parseContentType(header) {
16
+ const semi = header.indexOf(';');
17
+ const mediaType = parseMediaType(header);
18
+ if (semi === -1)
19
+ return { mediaType };
20
+ const boundary = extractBoundary(header, semi + 1);
21
+ return boundary === undefined ? { mediaType } : { mediaType, boundary };
22
+ }
23
+ function extractBoundary(header, start) {
24
+ let boundary;
25
+ scanParams(header, start, (name, value) => {
26
+ if (name !== 'boundary')
27
+ return true;
28
+ boundary = value;
29
+ return false;
30
+ });
31
+ return boundary;
32
+ }
33
+ //# sourceMappingURL=content-type.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-type.js","sourceRoot":"","sources":["../src/content-type.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAOxC;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAChC,OAAO,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;AAC5E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAChC,MAAM,SAAS,GAAG,cAAc,CAAC,MAAM,CAAC,CAAA;IACxC,IAAI,IAAI,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,SAAS,EAAE,CAAA;IACrC,MAAM,QAAQ,GAAG,eAAe,CAAC,MAAM,EAAE,IAAI,GAAG,CAAC,CAAC,CAAA;IAClD,OAAO,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAA;AACzE,CAAC;AAED,SAAS,eAAe,CAAC,MAAc,EAAE,KAAa;IACpD,IAAI,QAA4B,CAAA;IAChC,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QACxC,IAAI,IAAI,KAAK,UAAU;YAAE,OAAO,IAAI,CAAA;QACpC,QAAQ,GAAG,KAAK,CAAA;QAChB,OAAO,KAAK,CAAA;IACd,CAAC,CAAC,CAAA;IACF,OAAO,QAAQ,CAAA;AACjB,CAAC"}
@@ -0,0 +1,36 @@
1
+ import type { ZodLike } from '@miiajs/core';
2
+ import type { MultipartOptions } from './types.js';
3
+ /** Per-handler `@Multipart` options, read back at request time. */
4
+ export declare const MULTIPART_OPTIONS: unique symbol;
5
+ /**
6
+ * Streams a `multipart/form-data` body: the handler gets `ctx.parts` (an async
7
+ * iterator over the parts, one consumed at a time) and `ctx.form()` (the whole
8
+ * form buffered into `File`s and strings). Type the handler's context as
9
+ * `MultipartContext` to see both.
10
+ *
11
+ * A non-`multipart/form-data` request is rejected with 400 before the handler
12
+ * runs, whether or not it reads the body, and whatever is left unread is
13
+ * discarded once the handler returns.
14
+ *
15
+ * Body budget: an explicit `bodyLimit` wins; otherwise
16
+ * `maxFileSize * maxFiles + fieldsBudget` becomes the route's limit when both
17
+ * are finite; with neither, a `@BodyLimit` on the route (or its controller)
18
+ * becomes the budget, and without one only the adapter ceiling applies. The
19
+ * route limit belongs to whichever decorator wrote it last, so do not put
20
+ * `@Multipart` and `@BodyLimit` on the same method.
21
+ */
22
+ export declare const Multipart: (options?: MultipartOptions | undefined) => (target: Function, context: ClassMethodDecoratorContext) => void;
23
+ /**
24
+ * Validates the buffered form against a schema. What the schema sees is a flat
25
+ * object - text fields as strings (last value wins) next to files under their
26
+ * own names, a single `File` or a `File[]` when the name repeats - which is how
27
+ * a multipart body is described in OpenAPI, so a schema reads as
28
+ * `z.object({ title: z.string(), doc: z.instanceof(File) })`.
29
+ *
30
+ * A failed parse answers 422 with the schema's issues; a successful one
31
+ * replaces the cached form, so `ctx.form<T>()` returns the schema's data
32
+ * instead of the raw `FormResult`. The method also needs `@Multipart` - in
33
+ * either order.
34
+ */
35
+ export declare const ValidateForm: (schema: ZodLike<any>) => (target: Function, context: ClassMethodDecoratorContext) => void;
36
+ //# sourceMappingURL=decorators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAA8B,OAAO,EAAE,MAAM,cAAc,CAAA;AAGvE,OAAO,KAAK,EAAc,gBAAgB,EAAiB,MAAM,YAAY,CAAA;AAE7E,mEAAmE;AACnE,eAAO,MAAM,iBAAiB,eAA6B,CAAA;AAsC3D;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,SAAS,8GAYpB,CAAA;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,YAAY,4FAqBvB,CAAA"}
@@ -0,0 +1,174 @@
1
+ import { addToMapMeta, BODY_LIMITS, createMethodDecorator, METHOD_MW, setInMapMeta, UnprocessableException, } from '@miiajs/core';
2
+ import { collectForm } from './form.js';
3
+ import { createPartStream, DEFAULT_FIELDS_BUDGET } from './parser.js';
4
+ /** Per-handler `@Multipart` options, read back at request time. */
5
+ export const MULTIPART_OPTIONS = Symbol('multipartOptions');
6
+ const MULTIPART_STATE = Symbol('multipartState');
7
+ /**
8
+ * The class's decorator metadata, which `tsc` creates only when
9
+ * `Symbol.metadata` exists at class-definition time - a build that drops
10
+ * `@miiajs/core`'s polyfill import leaves it `undefined`, and the decorators
11
+ * below would then fail deep inside a metadata write.
12
+ */
13
+ function metadataOf(context, decorator) {
14
+ const metadata = context.metadata;
15
+ if (!metadata) {
16
+ throw new TypeError(`@${decorator} cannot read decorator metadata. Import @miiajs/core before the decorated class is defined - its Symbol.metadata polyfill has to run first.`);
17
+ }
18
+ return metadata;
19
+ }
20
+ /**
21
+ * Streams a `multipart/form-data` body: the handler gets `ctx.parts` (an async
22
+ * iterator over the parts, one consumed at a time) and `ctx.form()` (the whole
23
+ * form buffered into `File`s and strings). Type the handler's context as
24
+ * `MultipartContext` to see both.
25
+ *
26
+ * A non-`multipart/form-data` request is rejected with 400 before the handler
27
+ * runs, whether or not it reads the body, and whatever is left unread is
28
+ * discarded once the handler returns.
29
+ *
30
+ * Body budget: an explicit `bodyLimit` wins; otherwise
31
+ * `maxFileSize * maxFiles + fieldsBudget` becomes the route's limit when both
32
+ * are finite; with neither, a `@BodyLimit` on the route (or its controller)
33
+ * becomes the budget, and without one only the adapter ceiling applies. The
34
+ * route limit belongs to whichever decorator wrote it last, so do not put
35
+ * `@Multipart` and `@BodyLimit` on the same method.
36
+ */
37
+ export const Multipart = createMethodDecorator((_target, context, options = {}) => {
38
+ const name = String(context.name);
39
+ const metadata = metadataOf(context, 'Multipart');
40
+ setInMapMeta(metadata, MULTIPART_OPTIONS, name, options);
41
+ const bodyLimit = options.bodyLimit ?? deriveBodyLimit(options);
42
+ if (bodyLimit !== undefined)
43
+ setInMapMeta(metadata, BODY_LIMITS, name, bodyLimit);
44
+ // Options come from the metadata, not from this closure: the state may be
45
+ // built by whichever multipart middleware the decorator order put outside.
46
+ const mw = (ctx, next) => withMultipartState(ctx, runtimeOptions(metadata, name), () => next());
47
+ addToMapMeta(metadata, METHOD_MW, name, [mw]);
48
+ });
49
+ /**
50
+ * Validates the buffered form against a schema. What the schema sees is a flat
51
+ * object - text fields as strings (last value wins) next to files under their
52
+ * own names, a single `File` or a `File[]` when the name repeats - which is how
53
+ * a multipart body is described in OpenAPI, so a schema reads as
54
+ * `z.object({ title: z.string(), doc: z.instanceof(File) })`.
55
+ *
56
+ * A failed parse answers 422 with the schema's issues; a successful one
57
+ * replaces the cached form, so `ctx.form<T>()` returns the schema's data
58
+ * instead of the raw `FormResult`. The method also needs `@Multipart` - in
59
+ * either order.
60
+ */
61
+ export const ValidateForm = createMethodDecorator((_target, context, schema) => {
62
+ const name = String(context.name);
63
+ const metadata = metadataOf(context, 'ValidateForm');
64
+ const mw = async (ctx, next) => {
65
+ const declared = metadata[MULTIPART_OPTIONS];
66
+ if (!declared?.has(name)) {
67
+ throw new Error(`@ValidateForm on "${name}" needs @Multipart on the same method - there is no form to validate`);
68
+ }
69
+ await withMultipartState(ctx, runtimeOptions(metadata, name), async (state) => {
70
+ const result = schema.safeParse(flattenForm(await ctx.form()));
71
+ if (!result.success) {
72
+ throw new UnprocessableException('Form validation failed', result.error.issues);
73
+ }
74
+ state.setForm(result.data);
75
+ await next();
76
+ });
77
+ };
78
+ addToMapMeta(metadata, METHOD_MW, name, [mw]);
79
+ });
80
+ /** Fields and files side by side under their own names, the way a schema reads them. */
81
+ function flattenForm({ files, fields }) {
82
+ const flat = { ...fields };
83
+ for (const [name, group] of Object.entries(files)) {
84
+ flat[name] = group.length === 1 ? group[0] : group;
85
+ }
86
+ return flat;
87
+ }
88
+ /**
89
+ * Runs the rest of the pipeline with `ctx.parts` and `ctx.form()` in place.
90
+ * Both multipart middlewares call it, so the decorator order does not matter.
91
+ *
92
+ * Whoever built the state disposes of it: an outer `@ValidateForm` answering
93
+ * 422 keeps the `@Multipart` middleware from ever running, so the stream would
94
+ * otherwise be left behind by the one middleware that could not clean up.
95
+ */
96
+ async function withMultipartState(ctx, options, run) {
97
+ const target = ctx;
98
+ const existing = target[MULTIPART_STATE];
99
+ if (existing)
100
+ return run(existing);
101
+ const state = createState(target, options);
102
+ try {
103
+ await run(state);
104
+ }
105
+ finally {
106
+ await state.parts.return?.();
107
+ }
108
+ }
109
+ /** Attaches `ctx.parts` and `ctx.form()` for the rest of the request. */
110
+ function createState(target, options) {
111
+ const parts = createPartStream(target.req, options);
112
+ const state = {
113
+ parts,
114
+ partsUsed: false,
115
+ form: null,
116
+ setForm(data) {
117
+ state.form = Promise.resolve(data);
118
+ },
119
+ };
120
+ target[MULTIPART_STATE] = state;
121
+ target.parts = trackParts(state);
122
+ target.form = () => {
123
+ if (state.form === null && state.partsUsed) {
124
+ return Promise.reject(new TypeError('ctx.form() cannot run after ctx.parts - the body is consumed once, in one of the two ways'));
125
+ }
126
+ return (state.form ??= collectForm(state.parts));
127
+ };
128
+ return state;
129
+ }
130
+ /**
131
+ * `ctx.parts`, wrapped so the two ways of reading the body cannot be mixed.
132
+ *
133
+ * Both read the same source, so whichever runs second finds it spent. Left
134
+ * alone that surfaces as an empty form or an empty loop - the shape of failure
135
+ * that looks like a working request with no data in it.
136
+ */
137
+ function trackParts(state) {
138
+ const tracked = {
139
+ [Symbol.asyncIterator]() {
140
+ return tracked;
141
+ },
142
+ next() {
143
+ if (state.form !== null) {
144
+ return Promise.reject(new TypeError('ctx.parts cannot run after ctx.form() - the body is consumed once, in one of the two ways'));
145
+ }
146
+ state.partsUsed = true;
147
+ return state.parts.next();
148
+ },
149
+ return: () => state.parts.return?.() ?? Promise.resolve({ value: undefined, done: true }),
150
+ };
151
+ return tracked;
152
+ }
153
+ /**
154
+ * The route's options with the body budget resolved down to its last fallback:
155
+ * the metadata is complete by request time, so a `@BodyLimit` written by
156
+ * another decorator can still become `maxTotalSize`.
157
+ */
158
+ function runtimeOptions(metadata, name) {
159
+ const options = metadata[MULTIPART_OPTIONS]?.get(name) ?? {};
160
+ if (options.bodyLimit !== undefined || deriveBodyLimit(options) !== undefined)
161
+ return options;
162
+ const limits = metadata[BODY_LIMITS];
163
+ const bodyLimit = limits?.get(name) ?? limits?.get('*');
164
+ return bodyLimit === undefined ? options : { ...options, bodyLimit };
165
+ }
166
+ function deriveBodyLimit(options) {
167
+ const { maxFileSize, maxFiles } = options;
168
+ if (maxFileSize === undefined || maxFiles === undefined)
169
+ return undefined;
170
+ if (!Number.isFinite(maxFileSize) || !Number.isFinite(maxFiles))
171
+ return undefined;
172
+ return maxFileSize * maxFiles + (options.fieldsBudget ?? DEFAULT_FIELDS_BUDGET);
173
+ }
174
+ //# sourceMappingURL=decorators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.js","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"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"}
package/dist/form.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import type { FormResult, MultipartPart } from './types.js';
2
+ /**
3
+ * Drains the part iterator in a single pass: files are buffered into `File`
4
+ * objects grouped by part name, fields collapse to the last value under their
5
+ * name.
6
+ *
7
+ * A part with `filename=""` and no bytes is an empty `<input type="file">`, not
8
+ * an upload - it is left out of `files` while staying visible on `ctx.parts`.
9
+ */
10
+ export declare function collectForm(parts: AsyncIterableIterator<MultipartPart>): Promise<FormResult>;
11
+ //# sourceMappingURL=form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../src/form.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE3D;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,qBAAqB,CAAC,aAAa,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,CAqBlG"}
package/dist/form.js ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Drains the part iterator in a single pass: files are buffered into `File`
3
+ * objects grouped by part name, fields collapse to the last value under their
4
+ * name.
5
+ *
6
+ * A part with `filename=""` and no bytes is an empty `<input type="file">`, not
7
+ * an upload - it is left out of `files` while staying visible on `ctx.parts`.
8
+ */
9
+ export async function collectForm(parts) {
10
+ const files = {};
11
+ const fields = {};
12
+ for await (const part of parts) {
13
+ if (part.type === 'field') {
14
+ fields[part.name] = part.value;
15
+ continue;
16
+ }
17
+ const bytes = await part.bytes();
18
+ if (!part.filename && bytes.length === 0)
19
+ continue;
20
+ // `File` wants an ArrayBuffer-backed view; the parser never yields a shared one.
21
+ const file = new File([bytes], part.filename ?? '', { type: part.mediaType });
22
+ const existing = files[part.name];
23
+ if (existing)
24
+ existing.push(file);
25
+ else
26
+ files[part.name] = [file];
27
+ }
28
+ return { files, fields };
29
+ }
30
+ //# sourceMappingURL=form.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form.js","sourceRoot":"","sources":["../src/form.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,KAA2C;IAC3E,MAAM,KAAK,GAA2B,EAAE,CAAA;IACxC,MAAM,MAAM,GAA2B,EAAE,CAAA;IAEzC,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC/B,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC1B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAA;YAC9B,SAAQ;QACV,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,CAAA;QAChC,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,SAAQ;QAElD,iFAAiF;QACjF,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,KAAgC,CAAC,EAAE,IAAI,CAAC,QAAQ,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAA;QACxG,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACjC,IAAI,QAAQ;YAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;;YAC5B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAChC,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;AAC1B,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { Multipart, ValidateForm } from './decorators.js';
2
+ export { createPartStream } from './parser.js';
3
+ export type { FieldPart, FilePart, FormResult, MultipartContext, MultipartOptions, MultipartPart, } from './types.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,YAAY,EACV,SAAS,EACT,QAAQ,EACR,UAAU,EACV,gBAAgB,EAChB,gBAAgB,EAChB,aAAa,GACd,MAAM,YAAY,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ // @miiajs/multipart - public API
2
+ export { Multipart, ValidateForm } from './decorators.js';
3
+ export { createPartStream } from './parser.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iCAAiC;AAEjC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * One pass over an RFC 7231 parameter list - `; name=value` repeated, the value
3
+ * bare or quoted. `Content-Type` and `Content-Disposition` are read with the
4
+ * same walk, so a defect fixed here is fixed for both.
5
+ *
6
+ * A parameter name ends at the nearest `;`, never at an `=` further along: a
7
+ * valueless `foo` between two real parameters is skipped instead of swallowing
8
+ * the name that follows it. `visit` returning `false` ends the walk.
9
+ */
10
+ export declare function scanParams(input: string, start: number, visit: (name: string, value: string) => boolean | void): void;
11
+ //# sourceMappingURL=params.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"params.d.ts","sourceRoot":"","sources":["../src/params.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,IAAI,GAAG,IAAI,CAmCrH"}
package/dist/params.js ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * One pass over an RFC 7231 parameter list - `; name=value` repeated, the value
3
+ * bare or quoted. `Content-Type` and `Content-Disposition` are read with the
4
+ * same walk, so a defect fixed here is fixed for both.
5
+ *
6
+ * A parameter name ends at the nearest `;`, never at an `=` further along: a
7
+ * valueless `foo` between two real parameters is skipped instead of swallowing
8
+ * the name that follows it. `visit` returning `false` ends the walk.
9
+ */
10
+ export function scanParams(input, start, visit) {
11
+ let i = start;
12
+ while (i < input.length) {
13
+ const semi = input.indexOf(';', i);
14
+ const eq = input.indexOf('=', i);
15
+ if (eq === -1 || (semi !== -1 && semi < eq)) {
16
+ if (semi === -1)
17
+ return;
18
+ i = semi + 1;
19
+ continue;
20
+ }
21
+ const name = input.slice(i, eq).trim().toLowerCase();
22
+ let j = eq + 1;
23
+ while (j < input.length && (input[j] === ' ' || input[j] === '\t'))
24
+ j++;
25
+ let value = '';
26
+ if (input[j] === '"') {
27
+ j++;
28
+ while (j < input.length && input[j] !== '"') {
29
+ if (input[j] === '\\' && j + 1 < input.length)
30
+ j++;
31
+ value += input[j];
32
+ j++;
33
+ }
34
+ const next = input.indexOf(';', j);
35
+ i = next === -1 ? input.length : next + 1;
36
+ }
37
+ else {
38
+ const next = input.indexOf(';', j);
39
+ const end = next === -1 ? input.length : next;
40
+ value = input.slice(j, end).trim();
41
+ i = next === -1 ? input.length : next + 1;
42
+ }
43
+ if (visit(name, value) === false)
44
+ return;
45
+ }
46
+ }
47
+ //# sourceMappingURL=params.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"params.js","sourceRoot":"","sources":["../src/params.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAC,KAAa,EAAE,KAAa,EAAE,KAAsD;IAC7G,IAAI,CAAC,GAAG,KAAK,CAAA;IAEb,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QAClC,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;QAChC,IAAI,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,IAAI,GAAG,EAAE,CAAC,EAAE,CAAC;YAC5C,IAAI,IAAI,KAAK,CAAC,CAAC;gBAAE,OAAM;YACvB,CAAC,GAAG,IAAI,GAAG,CAAC,CAAA;YACZ,SAAQ;QACV,CAAC;QAED,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;QACpD,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;QACd,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;YAAE,CAAC,EAAE,CAAA;QAEvE,IAAI,KAAK,GAAG,EAAE,CAAA;QACd,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YACrB,CAAC,EAAE,CAAA;YACH,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;gBAC5C,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM;oBAAE,CAAC,EAAE,CAAA;gBAClD,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,CAAA;gBACjB,CAAC,EAAE,CAAA;YACL,CAAC;YACD,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;YAClC,CAAC,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAA;QAC3C,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAA;YAClC,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA;YAC7C,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;YAClC,CAAC,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAA;QAC3C,CAAC;QAED,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,KAAK;YAAE,OAAM;IAC1C,CAAC;AACH,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { MultipartOptions, MultipartPart } from './types.js';
2
+ export declare const DEFAULT_FIELDS_BUDGET: number;
3
+ /**
4
+ * Streams a `multipart/form-data` request as an async iterator of parts.
5
+ *
6
+ * Backpressure is real: the source is only read when every queue the consumer
7
+ * can still drain is empty. Exactly one part is consumed at a time - moving the
8
+ * iterator forward abandons the previous part and errors its stream rather than
9
+ * closing it, so a half-read upload can never look complete.
10
+ */
11
+ export declare function createPartStream(req: Request, options?: MultipartOptions): AsyncIterableIterator<MultipartPart>;
12
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAY,gBAAgB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAI3E,eAAO,MAAM,qBAAqB,QAAY,CAAA;AAgE9C;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,GAAE,gBAAqB,GAAG,qBAAqB,CAAC,aAAa,CAAC,CA2cnH"}