Initial Commit
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
# `single-shot.ts`
|
||||
|
||||
## Purpose
|
||||
|
||||
Internal back-end implementing the `singleShot` operation of `FileUploadService`. Extracted from the main service file to keep each implementation file under the suaveplan 300-line budget while still keeping the responsibility cohesive: size limit → MIME validation (with sniff cross-check) → storage persistence → record save → OTel emission.
|
||||
|
||||
## Features
|
||||
|
||||
- Encapsulates the full single-shot validation pipeline so the public `FileUploadService.singleShot` is a single function call.
|
||||
- Reads the first 4 KiB into the MIME sniffer so spoofed Content-Types are rejected without buffering the whole payload twice.
|
||||
- Counts outcomes (`success` / `rejected` / `error`) onto `upload.singleshot_total` and bytes onto `upload.bytes_total` so dashboards reflect every code path.
|
||||
- Generic in the id generator and clock so `createFileUploadService` can plug in its own deterministic values for tests.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { runSingleShot } from "@suaveplan/api-plugin-file-upload";
|
||||
|
||||
const record = await runSingleShot(
|
||||
{ principalId: "alice", fileName: "hello.txt", bytes, declaredMimeType: "text/plain" },
|
||||
{ config, storage, sessionStore, idGenerator: () => "u1", clock: () => Date.now() }
|
||||
);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { runSingleShot, FileUploadError } from "@suaveplan/api-plugin-file-upload";
|
||||
|
||||
try {
|
||||
await runSingleShot(input, opts);
|
||||
} catch (err) {
|
||||
if (err instanceof FileUploadError && err.uploadCode === "UPLOAD.MIME_REJECTED") {
|
||||
// spoof or denied — render UI-friendly 415 page
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `SingleShotInput` — `{ principalId, tenantId?, fileName, bytes, declaredMimeType }`.
|
||||
- `RunSingleShotOptions` — `{ config, storage, sessionStore, idGenerator, clock }`.
|
||||
- `runSingleShot(input, opts): Promise<UploadRecord>` — see Purpose for pipeline order; throws `UPLOAD.TOO_LARGE`, `UPLOAD.MIME_REJECTED`, or `UPLOAD.STORAGE_FAILURE` depending on which guard fires.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The sniff sample is capped at 4 KiB because every magic-bytes signature the `@suaveplan/mime` registry ships fits well inside that window; sniffing more wastes CPU on tiny files and offers no additional confidence.
|
||||
- Outcomes distinguish `rejected` (validation-side failure, `FileUploadError`) from `error` (storage/store-side failure, any other Error) so on-call rotations can alert on `error` without being paged for legitimate user rejections.
|
||||
Reference in New Issue
Block a user