Skip to content

Commit ca167f6

Browse files
fix(service-storage)!: the chunked-upload init door parses its body with the declared request schema (#27781)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DDaLcR4LnipNjp4uwkB7Ze2)_ Fixes #27549 Clause-②: no (narrowing — the init door parses its body with `InitiateChunkedUploadRequestSchema`; a `chunkSize` below the declared minimum is refused 400, not clamped, per triage 6498840027) Metadata-protocol: none Skills-docs: none — no skill or doc teaches the old clamp or a string total; the generated reference (`content/docs/references/api/storage.mdx`) already states the schema the door now enforces Card part (a) only. Part (b) (`GetPresignedUrlRequestSchema.size`) was split to `domain:spec` by triage 6498840027 and is not touched here. ## 维护者速读 - 改了什么:分块上传的开启接口 `POST /api/v1/storage/upload/chunked` 现在用规范里已声明的 `InitiateChunkedUploadRequestSchema` 解析请求体,不合规就 400 拒收,点名出错字段。 - 为什么改:以前只看 `totalSize` 是否为真值,负数或字符串也能开出一个永远无法完成的上传会话;小于 5 MiB 的 `chunkSize` 被悄悄抬到 5 MiB,而规范说的是拒收。 - 风险与回滚:收窄接受面,传字符串 `totalSize` 或小于 5 MiB `chunkSize` 的第三方客户端会开始收到 400;SDK 按同一类型发送不受影响。回滚即还原本 PR 的单个 commit。 - 先看:`packages/services/service-storage/src/storage-routes.ts`(init 处理器的解析与拒收)、`storage-routes.test.ts`(新钉子)、`.changeset/27549-chunked-init-declared-shape.md`。 - 两分钟验证:`pnpm --filter @objectstack/service-storage exec vitest run src/storage-routes.test.ts`,预期 70/70 通过,其中 7 条 REFUSED、3 条 CONTROL。 - 需要你做的:无,席位落地(契约复审 PASS 后)。 ## What changes `POST /api/v1/storage/upload/chunked` (`packages/services/service-storage/src/storage-routes.ts`) now parses its body with `InitiateChunkedUploadRequestSchema` before the pending `sys_file` row, the backend multipart and the `sys_upload_session` row are written. The handler then reads the parsed values. - **Refused `400 INVALID_REQUEST`**, naming each failing field with the schema's own words, with neither row written: a `totalSize` that is not a whole JSON number of at least 1 (`-5`, `"100"`, `0`, `1.5`); a `chunkSize` that is not a whole JSON number of at least 5242880 (it is no longer clamped up); a `bucket` that is not a string; a `metadata` that is not an object of string values. - **Unchanged:** an omitted `chunkSize` takes the schema's declared default of 5242880, so a valid body mints the same session with the same `totalChunks`. A missing `filename`, `mimeType` or `totalSize` keeps its existing message, and an off-vocabulary `scope` keeps its own remedy: the scope gate still runs before the parse. The presence check for `totalSize` moves from truthiness to `== null` (the presigned door's shape), so `0` now reaches the parse and is refused by name. - Same envelope as the presigned door's refusal from PR #27537: `sendError(res, 400, 'INVALID_REQUEST', message)`. No new error code. - **ADR-0087 disposition:** `not-required (no-migration-prescription)`. A REST request body is refused against a spec schema that already declares it, unchanged; no metadata key, export, type or error code moves, so there is nothing for `objectstack migrate meta` to rewrite. The changeset carries the BREAKING banner and the FROM / TO / Fix account. ## Why `InitiateChunkedUploadRequestSchema` declares `totalSize: int().min(1)` and `chunkSize: int().min(5242880)`, and the door enforced neither (declared is not enforced). A negative total minted a session with `totalChunks` of `-0`, so every chunk was refused after #27412 and the upload could never complete; a numeric-string total was stored as sent. Triage ruled (6498840027) that declared is enforced and a `chunkSize` below the minimum is refused, not clamped. ## Tests `packages/services/service-storage/src/storage-routes.test.ts`, in `Chunked upload flow`: - 7 `REFUSED 400 INVALID_REQUEST` rows: negative, numeric-string, zero and fractional `totalSize`; `chunkSize` 1024; numeric-string `chunkSize`; a non-string `metadata` value. Each asserts `status`, `success: false`, `error.code`, that the message names the field, and that `createFile` and `createSession` were never called. - 3 `CONTROL` rows: an omitted `chunkSize` (default 5242880, 3 chunks for 12 000 000 bytes), `chunkSize` at the minimum (3 chunks) and above it (10485760, 2 chunks). Each asserts the response's `chunkSize` / `totalChunks`, the stored session's `chunk_size` / `total_chunks`, and the stored file size. <details><summary>Ablation: drop the parse, then restore</summary> Run with `node scripts/ablation-replace.mjs` on `storage-routes.ts`, after the fix was committed (`e3c83f13`). Anchor `const parsed = InitiateChunkedUploadRequestSchema.safeParse(body);` replaced with a pass-through `{ success: true, data: body }`. The anchor went from 1 to 0 hits on disk and the blob from `6b8c79453e4b` to `86146bd7a141`. - Mutated: `Tests 12 failed | 58 passed (70)`. All 7 REFUSED rows and the omitted-`chunkSize` CONTROL went red, plus 4 existing terminal-status tests whose init omits `chunkSize` (a `NaN` chunk count without the schema default). The two CONTROL rows that send a `chunkSize` stayed green, as expected. - Restored: blob after restore `6b8c79453e4b` equals the HEAD blob, and `git diff HEAD` is empty. Then `Tests 70 passed (70)`. The subject is imported from `src/` (`./storage-routes.js`), so no dist rebuild sits between the mutation and the run. </details> ## Acceptance notes - The parse covers the whole declared body, as the ruling says, so `bucket` and `metadata` narrow too (listed in the changeset). `null` for `chunkSize`, `bucket` or `metadata` was read as omitted and is now refused, because the schema declares them optional, not nullable. - `filename: ''` and `mimeType: ''` stay refused by the existing presence check. That check is stricter than the schema's `z.string()`, and this PR leaves it as it was. - `scope ?? 'user'` in the handler body is now redundant (the parse applies the declared default) and is left as it was, to keep the diff small. ## Verification All local runs are on head `26e6d217`. <details><summary>Package tests and typecheck</summary> - `pnpm --filter @objectstack/service-storage test`: `Test Files 70 passed (70)`, `Tests 1297 passed (1297)`, lock VERDICT command-exit 0. - `pnpm --filter @objectstack/service-storage typecheck`: exit 0; `check:test-typecheck: OK`, 0 errors. - `pnpm lint`: exit 0. </details> <details><summary>Derived gate families (dispatch-gates --commands, 64 families)</summary> `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 64 families from the 3 changed paths at `26e6d217`. All 64 exited 0. `check:i18n` and `check:dual-build-cjs-loads` first exited 3 (prerequisite not met) and passed after the build closure they name: `check:i18n` OK, 10 packages in sync; `check:dual-build-cjs-loads` 112 entry points across 69 packages load. `--ran` reconciliation, with an exit code on every line: `64 derived famil(ies) accounted for — 64 run, 0 NOT-MEASURED (a DERIVED zero — all 64 recorded an exit code and none of them is 3)`. </details> <details><summary>Dogfood battery</summary> Full battery once on the final head `26e6d217` (title carries `!`, `Clause-②:` carries the narrowing arm): build closure `@objectstack/dogfood^...`, then `pnpm --filter @objectstack/dogfood test` under the verify lock, no shard and no path. `Test Files 355 passed | 3 skipped (358)`, `Tests 2867 passed | 20 skipped (2887)`, lock VERDICT command-exit 0, held 21m00s. No failures. </details> ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (`packages/spec` not touched) - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` passes. - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` touched: n/a. - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English. - [x] The *Before Submitting* list in CONTRIBUTING.md is done. --- _Generated by [Claude Code](https://claude.ai/code/session_016WV4d1JR8d3KVqut8R6TWN)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3cc4563 commit ca167f6

4 files changed

Lines changed: 117 additions & 10 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
'@objectstack/service-storage': patch
3+
---
4+
5+
fix(service-storage): the chunked-upload init door parses its body with `InitiateChunkedUploadRequestSchema`, so a `totalSize` that is not a positive whole number and a `chunkSize` below the declared minimum are refused instead of minting a session that can never complete
6+
7+
Clause-②: no (narrowing)
8+
Metadata-protocol: none
9+
10+
<!-- adr-0087: not-required (no-migration-prescription) A REST request body is refused at the chunked-upload init door when it does not parse with the spec schema it already declares (`InitiateChunkedUploadRequestSchema`, unchanged): no key of any metadata schema is removed, renamed or re-shaped, no export, type or error code is removed or renamed, and the refusal reuses the existing 400 INVALID_REQUEST envelope. There is nothing for `objectstack migrate meta` to rewrite and no tombstone. -->
11+
12+
**BREAKING** (an accept-set narrowing), shipped as `patch`: nothing a metadata author writes changes (`Metadata-protocol: none`), so the level follows the kind, and the banner and the ADR-0087 disposition stay.
13+
14+
**FROM.** `POST /api/v1/storage/upload/chunked` checked `totalSize` for truthiness only and read the rest of the body as sent:
15+
16+
- A negative `totalSize` such as `-5` answered `200` and minted a session with `totalChunks` of `-0`, so every chunk was then refused and the upload could never complete. A numeric string such as `"100"` was stored on the pending `sys_file` row as sent, and a fraction was accepted.
17+
- A `chunkSize` below 5242880 (5 MiB, the declared minimum) was silently clamped up to 5242880; `null` was read as omitted, and a non-numeric one produced a `NaN` chunk count.
18+
- `bucket` and `metadata` were stored whatever their type.
19+
20+
**TO.** The body is parsed with `InitiateChunkedUploadRequestSchema` before the `sys_file` row, the backend multipart and the upload-session row:
21+
22+
- `totalSize` must be a whole number of at least 1, sent as a JSON number.
23+
- `chunkSize`, when sent, must be a whole number of at least 5242880, sent as a JSON number; a smaller one is refused, not clamped. An omitted `chunkSize` keeps the declared default of 5242880, so `totalChunks` for a valid body is unchanged.
24+
- `bucket`, when sent, must be a string, and `metadata`, when sent, an object of string values.
25+
- Anything else answers `400 INVALID_REQUEST` naming each failing field, and neither row is written. A missing `filename`, `mimeType` or `totalSize` and an off-vocabulary `scope` keep their existing `400` answers.
26+
27+
**Fix.** A client that sent a smaller `chunkSize` sends 5242880 or more, or omits it. A client that sent `totalSize` as a string sends the JSON number. The SDK's `storage.initChunkedUpload` takes the schema's own input type, so a typed caller already sends an in-schema body; no Console path calls this door.

‎docs/qa/platform-checklist/areas/attachments-storage.json‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -808,7 +808,7 @@
808808
"title": "Chunked uploads are resumable; abandoned sessions are reaped and their backend multipart uploads ABORTED before the row (the only pointer) is deleted",
809809
"since": "v15.1",
810810
"status": "active",
811-
"revision": 6,
811+
"revision": 7,
812812
"priority": "P2",
813813
"surface": "api",
814814
"personas": ["seeded admin (admin@objectos.ai)"],
@@ -823,7 +823,7 @@
823823
]
824824
},
825825
"steps": [
826-
"POST /api/v1/storage/upload/chunked { filename, mimeType, totalSize, chunkSize } and capture { uploadId, fileId } — the init route's required trio is filename/mimeType/`totalSize` (a body spelling `size` is refused 400 INVALID_REQUEST), and `chunkSize` is floored at 5 MiB, so a smaller requested value silently yields a different totalChunks than the caller computed (storage-routes.ts)",
826+
"POST /api/v1/storage/upload/chunked { filename, mimeType, totalSize, chunkSize } and capture { uploadId, fileId } — the init route's required trio is filename/mimeType/`totalSize` (a body spelling `size` is refused 400 INVALID_REQUEST), and the body is parsed with InitiateChunkedUploadRequestSchema: a `totalSize` that is not a whole JSON number of at least 1, or a `chunkSize` below 5 MiB (5242880), is refused 400 INVALID_REQUEST naming the field, never clamped; an omitted `chunkSize` takes the declared 5242880 default (storage-routes.ts)",
827827
"PUT one chunk, then GET /upload/chunked/<uploadId>/progress and record uploadedChunks/percentComplete/status ('in_progress') — progress is the first step of the SDK's resumeUpload",
828828
"complete a full session on a second upload and verify its sys_upload_session row reaches status 'completed'",
829829
"abandon the first session mid-flight; backdate its expires_at past the 1d TTL (system write) and trigger the lifecycle sweep",
@@ -882,7 +882,7 @@
882882
"packages/services/service-storage/src/attachment-lifecycle.ts#createUploadSessionReapGuard (createUploadSessionReapGuard — abort-before-reap contract, completed/no-backend confirm branches, veto-on-failure)",
883883
"packages/services/service-storage/src/objects/system-upload-session.object.ts#expires_at (status enum = the variants list; ttl expires_at+1d, retention 7d terminal statuses)",
884884
"packages/services/service-storage/src/storage-route-ledger.ts (upload-chunked family)",
885-
"packages/services/service-storage/src/storage-routes.ts#mimeType (POST /upload/chunked init contract: filename/mimeType/totalSize required, else 400 INVALID_REQUEST; chunkSize = Math.max(reqChunkSize ?? 5242880, 5242880), i.e. a 5 MiB floor)",
885+
"packages/services/service-storage/src/storage-routes.ts#mimeType (POST /upload/chunked init contract: filename/mimeType/totalSize required, else 400 INVALID_REQUEST; the body parsed with InitiateChunkedUploadRequestSchema, so a chunkSize below 5242880 is refused 400 INVALID_REQUEST, not floored, and an omitted one takes the 5242880 default)",
886886
"packages/services/service-storage/src/storage-routes.ts#expireIfPastDeadline (expireIfPastDeadline / markSessionFailed — the #7667 producers for 'expired' and 'failed'; chunk + complete refuse 410 UPLOAD_SESSION_EXPIRED, progress reports the status)",
887887
"packages/spec/src/api/storage.zod.ts#UploadProgressSchema (UploadProgressSchema.status — the client-facing declaration the enum must stay in step with)",
888888
"docs/plans/release-15.1-test-plan.md §C4 (#2970 item 4)"
@@ -903,6 +903,12 @@
903903
"date": "2026-10-28",
904904
"change": "automated.ref names two more storage-routes cases and the chunked dogfood file. Checklist-accuracy finding of the 18.2.0 wave 2 (QA run #26468, Tier 1 half A, at 756220c4; wave anchor #25807, slice B): storage-routes.test.ts carries two more terminal-status cases (expired on progress, a completed session left alone), and storage-chunked-resume-integrity.dogfood.test.ts drives the chunked doors over HTTP without reading the session row, so acceptance[0] is still unpinned. Re-read at base fd246c22 (storage-routes.test.ts, the two cases named in the ref). No clause changed",
905905
"ref": "#25807"
906+
},
907+
{
908+
"revision": 7,
909+
"date": "2026-10-29",
910+
"change": "steps[0] and the storage-routes.ts source line described the 5 MiB chunkSize floor (a smaller requested chunkSize silently clamped up). The init door now parses its body with InitiateChunkedUploadRequestSchema, so a chunkSize below 5242880 and a totalSize that is not a whole number of at least 1 are refused 400 INVALID_REQUEST naming the field; an omitted chunkSize keeps the 5242880 default. Both strings now describe the refusal. No clause, oracle or variant changed",
911+
"ref": "#27549"
906912
}
907913
]
908914
},

‎packages/services/service-storage/src/storage-routes.test.ts‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,56 @@ describe('Storage REST Routes', () => {
255255
expect(progressRes._json.data.uploadedChunks).toBe(0);
256256
expect(progressRes._json.data.status).toBe('in_progress');
257257
});
258+
259+
// [#27549] The init body is parsed with `InitiateChunkedUploadRequestSchema`:
260+
// declared is enforced. `totalSize` was checked for truthiness only, so a
261+
// negative or string total minted a session no chunk could ever complete,
262+
// and a `chunkSize` under the declared minimum was clamped up instead of
263+
// refused. Each refusal names its field and writes neither row.
264+
it.each<[string, Record<string, unknown>, string]>([
265+
['a negative totalSize', { totalSize: -5 }, 'totalSize'],
266+
['a numeric-string totalSize', { totalSize: '100' }, 'totalSize'],
267+
['a zero totalSize', { totalSize: 0 }, 'totalSize'],
268+
['a fractional totalSize', { totalSize: 1.5 }, 'totalSize'],
269+
['a chunkSize below the declared minimum', { totalSize: 100, chunkSize: 1024 }, 'chunkSize'],
270+
['a numeric-string chunkSize', { totalSize: 100, chunkSize: '5242880' }, 'chunkSize'],
271+
['a metadata value that is not a string', { totalSize: 100, metadata: { tag: 1 } }, 'metadata'],
272+
])('REFUSED 400 INVALID_REQUEST: %s, with no sys_file or upload-session row written', async (_label, fields, named) => {
273+
const initHandler = httpServer._getHandler('POST', '/api/v1/storage/upload/chunked')!;
274+
const createFile = vi.spyOn(store, 'createFile');
275+
const createSession = vi.spyOn(store, 'createSession');
276+
const res = createMockRes();
277+
await initHandler(createMockReq({ body: { filename: 'b.bin', mimeType: 'application/octet-stream', ...fields } }), res);
278+
279+
expect(res._status, JSON.stringify(res._json)).toBe(400);
280+
expect(res._json.success).toBe(false);
281+
expect(res._json.error.code).toBe('INVALID_REQUEST');
282+
expect(res._json.error.message).toContain(named);
283+
expect(createFile, 'no pending sys_file row').not.toHaveBeenCalled();
284+
expect(createSession, 'no upload-session row').not.toHaveBeenCalled();
285+
});
286+
287+
it.each<[string, number | undefined, number, number]>([
288+
['an omitted chunkSize takes the declared default', undefined, 5242880, 3],
289+
['a chunkSize at the declared minimum', 5242880, 5242880, 3],
290+
['a chunkSize above the declared minimum', 10485760, 10485760, 2],
291+
])('CONTROL: %s and mints a session with the same total_chunks', async (_label, chunkSize, servedChunkSize, totalChunks) => {
292+
const initHandler = httpServer._getHandler('POST', '/api/v1/storage/upload/chunked')!;
293+
const totalSize = 12_000_000;
294+
const res = createMockRes();
295+
await initHandler(
296+
createMockReq({ body: { filename: 'b.bin', mimeType: 'application/octet-stream', totalSize, chunkSize } }),
297+
res,
298+
);
299+
300+
expect(res._status, JSON.stringify(res._json)).toBe(200);
301+
expect(res._json.data.chunkSize).toBe(servedChunkSize);
302+
expect(res._json.data.totalChunks).toBe(totalChunks);
303+
const session = await store.getSession(res._json.data.uploadId);
304+
expect(session!.total_chunks).toBe(totalChunks);
305+
expect(session!.chunk_size).toBe(servedChunkSize);
306+
expect((await store.getFile(res._json.data.fileId))!.size).toBe(totalSize);
307+
});
258308
});
259309

260310
// ── terminal session statuses (#7667) ──────────────────────────────────

‎packages/services/service-storage/src/storage-routes.ts‎

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import type { IHttpServer, IHttpRequest, IHttpResponse, IStorageService, RouteHa
55
import type { ErrorCode, RegisteredErrorCode, StandardErrorCode } from '@objectstack/spec/api';
66
// [#22470] The ONE upload-scope list — the upload requests' `scope` and the
77
// `sys_file.scope` select read it too. A value import: the doors ask it.
8-
import { UploadScopeSchema } from '@objectstack/spec/api';
8+
// [#27549] The chunked-upload init door parses its whole body with the
9+
// declared request schema, so declared is enforced.
10+
import { InitiateChunkedUploadRequestSchema, UploadScopeSchema } from '@objectstack/spec/api';
911
// The declared envelope is written in ONE place for the whole platform (#3973).
1012
// [#23174] A catch-all 5xx answers through `sendServerFault`: the route's
1113
// generic text on the wire, the caught error with its stack in the fault log.
@@ -174,6 +176,18 @@ function isDeclaredByteCount(size: unknown): size is number {
174176
const PRESIGNED_SIZE_REFUSAL_MESSAGE =
175177
'size must be the file size in bytes: a non-negative number, sent as a JSON number';
176178

179+
/**
180+
* [#27549] The chunked-upload init door's refusal for a body that does not
181+
* parse with `InitiateChunkedUploadRequestSchema`: every failing field named
182+
* with the schema's own words. `totalSize` is a whole number of bytes, at
183+
* least 1, and `chunkSize` (when sent) a whole number at or above the
184+
* declared 5 MiB minimum — a smaller one is refused, never clamped up.
185+
*/
186+
function chunkedInitRefusalMessage(issues: ReadonlyArray<{ path: ReadonlyArray<PropertyKey>; message: string }>): string {
187+
const named = issues.map((issue) => `${issue.path.map(String).join('.') || '(body)'}: ${issue.message}`);
188+
return `The chunked upload request does not match its declared shape. ${named.join('; ')}`;
189+
}
190+
177191
/**
178192
* [#27412] The chunk door's `chunkIndex` path segment, read strictly: decimal
179193
* digits only, so `-1`, `0.5`, `1e2` and `0x1` are not indexes. `null` ⇒ not
@@ -1100,21 +1114,31 @@ export function registerStorageFamily(
11001114
try {
11011115
const session = await requireUploadSession(req, res);
11021116
if (session === false) return;
1103-
const { filename, mimeType, totalSize, chunkSize: reqChunkSize, scope, bucket, metadata } = req.body ?? {};
1104-
if (!filename || !mimeType || !totalSize) {
1117+
const body = req.body ?? {};
1118+
if (!body.filename || !body.mimeType || body.totalSize == null) {
11051119
sendError(res, 400, 'INVALID_REQUEST', 'filename, mimeType, and totalSize are required');
11061120
return;
11071121
}
11081122
// [#22470] Same scope gate as the presigned door — before the file row,
1109-
// the backend multipart and the session row.
1110-
if (!requireAcceptedUploadScope(scope, res)) return;
1123+
// the backend multipart and the session row. Asked before the parse
1124+
// below so an off-vocabulary scope keeps its own remedy.
1125+
if (!requireAcceptedUploadScope(body.scope, res)) return;
1126+
// [#27549] Declared is enforced: the body must parse with the declared
1127+
// request schema, before anything is stored or minted. A total that is
1128+
// not a positive whole number used to mint a session no chunk could
1129+
// complete, and a `chunkSize` under the minimum was clamped up.
1130+
const parsed = InitiateChunkedUploadRequestSchema.safeParse(body);
1131+
if (!parsed.success) {
1132+
sendError(res, 400, 'INVALID_REQUEST', chunkedInitRefusalMessage(parsed.error.issues));
1133+
return;
1134+
}
1135+
const { filename, mimeType, totalSize, chunkSize, scope, bucket, metadata } = parsed.data;
11111136
// [#22283] The declared total against the saved limit — before the file
11121137
// row, the backend multipart and the session row. The chunk door judges
11131138
// the bytes as they arrive.
11141139
const { sessionTtl, maxUploadBytes } = currentLimits();
1115-
if (!requireWithinUploadLimit(Number(totalSize), maxUploadBytes, (n) => `The declared total size is ${n} bytes`, res)) return;
1140+
if (!requireWithinUploadLimit(totalSize, maxUploadBytes, (n) => `The declared total size is ${n} bytes`, res)) return;
11161141

1117-
const chunkSize = Math.max(reqChunkSize ?? 5242880, 5242880);
11181142
const totalChunks = Math.ceil(totalSize / chunkSize);
11191143

11201144
const fileId = randomUUID();

0 commit comments

Comments
 (0)