partial-content 1.0.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.
Files changed (81) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +601 -0
  4. package/SECURITY.md +92 -0
  5. package/dist/azure.d.ts +79 -0
  6. package/dist/azure.d.ts.map +1 -0
  7. package/dist/azure.js +251 -0
  8. package/dist/azure.js.map +1 -0
  9. package/dist/content-disposition.d.ts +74 -0
  10. package/dist/content-disposition.d.ts.map +1 -0
  11. package/dist/content-disposition.js +253 -0
  12. package/dist/content-disposition.js.map +1 -0
  13. package/dist/fs.d.ts +86 -0
  14. package/dist/fs.d.ts.map +1 -0
  15. package/dist/fs.js +375 -0
  16. package/dist/fs.js.map +1 -0
  17. package/dist/gcs.d.ts +72 -0
  18. package/dist/gcs.d.ts.map +1 -0
  19. package/dist/gcs.js +202 -0
  20. package/dist/gcs.js.map +1 -0
  21. package/dist/hono.d.ts +92 -0
  22. package/dist/hono.d.ts.map +1 -0
  23. package/dist/hono.js +61 -0
  24. package/dist/hono.js.map +1 -0
  25. package/dist/http.d.ts +70 -0
  26. package/dist/http.d.ts.map +1 -0
  27. package/dist/http.js +281 -0
  28. package/dist/http.js.map +1 -0
  29. package/dist/index.d.ts +21 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +27 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/kernel.d.ts +541 -0
  34. package/dist/kernel.d.ts.map +1 -0
  35. package/dist/kernel.js +1218 -0
  36. package/dist/kernel.js.map +1 -0
  37. package/dist/memory.d.ts +55 -0
  38. package/dist/memory.d.ts.map +1 -0
  39. package/dist/memory.js +107 -0
  40. package/dist/memory.js.map +1 -0
  41. package/dist/mime.d.ts +49 -0
  42. package/dist/mime.d.ts.map +1 -0
  43. package/dist/mime.js +150 -0
  44. package/dist/mime.js.map +1 -0
  45. package/dist/node.d.ts +84 -0
  46. package/dist/node.d.ts.map +1 -0
  47. package/dist/node.js +215 -0
  48. package/dist/node.js.map +1 -0
  49. package/dist/object-store.d.ts +472 -0
  50. package/dist/object-store.d.ts.map +1 -0
  51. package/dist/object-store.js +335 -0
  52. package/dist/object-store.js.map +1 -0
  53. package/dist/r2.d.ts +94 -0
  54. package/dist/r2.d.ts.map +1 -0
  55. package/dist/r2.js +150 -0
  56. package/dist/r2.js.map +1 -0
  57. package/dist/s3.d.ts +49 -0
  58. package/dist/s3.d.ts.map +1 -0
  59. package/dist/s3.js +263 -0
  60. package/dist/s3.js.map +1 -0
  61. package/dist/web.d.ts +336 -0
  62. package/dist/web.d.ts.map +1 -0
  63. package/dist/web.js +1094 -0
  64. package/dist/web.js.map +1 -0
  65. package/docs/DESIGN.md +426 -0
  66. package/package.json +182 -0
  67. package/src/azure.ts +329 -0
  68. package/src/content-disposition.ts +300 -0
  69. package/src/fs.ts +469 -0
  70. package/src/gcs.ts +290 -0
  71. package/src/hono.ts +123 -0
  72. package/src/http.ts +351 -0
  73. package/src/index.ts +85 -0
  74. package/src/kernel.ts +1498 -0
  75. package/src/memory.ts +148 -0
  76. package/src/mime.ts +160 -0
  77. package/src/node.ts +261 -0
  78. package/src/object-store.ts +665 -0
  79. package/src/r2.ts +232 -0
  80. package/src/s3.ts +324 -0
  81. package/src/web.ts +1603 -0
package/dist/s3.js ADDED
@@ -0,0 +1,263 @@
1
+ /**
2
+ * S3-compatible ObjectStore adapter for partial-content.
3
+ *
4
+ * Implements the read-only {@link ObjectStore} interface from the kernel,
5
+ * wrapping `@aws-sdk/client-s3` HeadObject/GetObject commands. Covers:
6
+ * - AWS S3
7
+ * - Cloudflare R2 (S3-compatible mode)
8
+ * - Hetzner Object Storage
9
+ * - MinIO / Backblaze B2 / Wasabi
10
+ *
11
+ * For native R2 bindings (without the AWS SDK), use `partial-content/r2`.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import { S3Client } from "@aws-sdk/client-s3";
16
+ * import { s3Store } from "partial-content/s3";
17
+ *
18
+ * const client = new S3Client({ region: "eu-central-1" });
19
+ * const store = s3Store({ client, bucket: "documents" });
20
+ * ```
21
+ *
22
+ * @packageDocumentation
23
+ */
24
+ import { S3Client, GetObjectCommand, HeadObjectCommand, NoSuchKey, NotFound, } from "@aws-sdk/client-s3";
25
+ import { ObjectNotFoundError, ObjectChangedError, StoreUnavailableError, classifyStoreRead, nodeStreamToWeb, guardStreamLength, resolveServedRange, buildContentDisposition, isOpenEndedRange, } from "./index.js";
26
+ // Re-export for convenience
27
+ export { ObjectNotFoundError, ObjectChangedError, StoreUnavailableError };
28
+ // ─── S3 Body -> Web ReadableStream ──────────────────────────────────────────
29
+ /**
30
+ * Convert an S3 SDK response Body to a web ReadableStream.
31
+ *
32
+ * The SDK Body is a web ReadableStream in Bun/Deno, a Node Readable in
33
+ * Node.js, and may expose `transformToWebStream()` as a convenience.
34
+ * We try each path in order of preference.
35
+ */
36
+ function toWebStream(body, signal, expectedBytes) {
37
+ // Every branch enforces the committed length: an S3-compatible backend that
38
+ // ends a body cleanly but short of ContentLength (some do in-flight body
39
+ // retries) must error the stream, not silently under-run the response. The
40
+ // web-stream branches are the ones Node aws-sdk v3 and Bun/Deno actually take,
41
+ // so guarding only the Node-Readable fallback would leave the guard dead.
42
+ if (body instanceof ReadableStream) {
43
+ return guardStreamLength(body, expectedBytes);
44
+ }
45
+ if (typeof body.transformToWebStream === "function") {
46
+ return guardStreamLength(body.transformToWebStream(), expectedBytes);
47
+ }
48
+ // Node Readable fallback: the shared utility auto-detects destroy() and
49
+ // applies the same length guard internally.
50
+ return nodeStreamToWeb(body, { signal, expectedBytes });
51
+ }
52
+ // ─── Factory ────────────────────────────────────────────────────────────────
53
+ /**
54
+ * Create an {@link ObjectStore} backed by an S3-compatible bucket.
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * import { S3Client } from "@aws-sdk/client-s3";
59
+ * import { s3Store } from "partial-content/s3";
60
+ *
61
+ * const client = new S3Client({ region: "eu-central-1" });
62
+ * const store = s3Store({ client, bucket: "documents" });
63
+ *
64
+ * // Use with partial-content's evaluateConditionalRequest:
65
+ * const meta = await store.headObject("reports/q4.pdf");
66
+ * ```
67
+ */
68
+ export function s3Store(opts) {
69
+ const { client, bucket } = opts;
70
+ return {
71
+ supportsRange: true,
72
+ // 206 bounds/total are parsed from S3's actual Content-Range: the
73
+ // orchestrator may skip the validating HEAD for plain range requests.
74
+ authoritativeRange: true,
75
+ async headObject(key, opts) {
76
+ const response = await classifyStoreRead(key, () => client.send(
77
+ // ChecksumMode is required for S3 to return ChecksumSHA256 at all;
78
+ // without it the digest would silently never be emitted.
79
+ new HeadObjectCommand({ Bucket: bucket, Key: key, ChecksumMode: "ENABLED" }), { abortSignal: opts?.signal }), s3Classifiers);
80
+ if (response.ContentLength == null) {
81
+ throw new Error(`HeadObject returned no ContentLength for ${key}`);
82
+ }
83
+ return {
84
+ contentLength: response.ContentLength,
85
+ etag: response.ETag,
86
+ lastModified: response.LastModified?.toUTCString(),
87
+ digest: toReprDigest(response.ChecksumSHA256),
88
+ };
89
+ },
90
+ async getObject(key, opts) {
91
+ const { range, signal, ifMatch } = opts ?? {};
92
+ // Open-ended fast-path ranges (`bytes=a-`) carry the OPEN_ENDED
93
+ // sentinel end; emit the bare open form so no 16-digit last-byte-pos
94
+ // reaches the wire (S3 clamps it, but strict proxies may reject it).
95
+ const rangeHeader = range
96
+ ? (isOpenEndedRange(range) ? `bytes=${range.start}-` : `bytes=${range.start}-${range.end}`)
97
+ : undefined;
98
+ const response = await classifyStoreRead(key, () => client.send(new GetObjectCommand({
99
+ Bucket: bucket,
100
+ Key: key,
101
+ ChecksumMode: "ENABLED",
102
+ // Pin the read to the validated representation: S3 rejects with
103
+ // 412 PreconditionFailed if the object changed since HEAD.
104
+ ...(ifMatch ? { IfMatch: ifMatch } : {}),
105
+ ...(rangeHeader ? { Range: rangeHeader } : {}),
106
+ }), { abortSignal: signal }), s3Classifiers);
107
+ if (!response.Body) {
108
+ throw new Error(`S3 GetObject returned empty body for ${key}`);
109
+ }
110
+ const stream = toWebStream(response.Body, signal, response.ContentLength);
111
+ // A null ContentLength on a GET with a live body is a degenerate response:
112
+ // fabricating `0` would commit `Content-Length: 0` over real bytes (a wire
113
+ // framing error) and silently disable the short-read guard. headObject
114
+ // already fails loudly on this; mirror it -- cancel the body and throw.
115
+ if (response.ContentLength == null) {
116
+ stream.cancel().catch(() => { });
117
+ throw new Error(`S3 GetObject returned no ContentLength for ${key}`);
118
+ }
119
+ const contentLength = response.ContentLength;
120
+ // Extract served bounds + total size from Content-Range
121
+ // (e.g. "bytes 0-999/5000") via the shared resolver, or fall back to
122
+ // ContentLength for full responses. A Content-Range S3 emits but the
123
+ // resolver cannot parse means the byte accounting is untrustworthy:
124
+ // cancel the live body and fail loudly rather than guess.
125
+ let totalSize;
126
+ let served;
127
+ if (response.ContentRange) {
128
+ const resolved = resolveServedRange(response.ContentRange);
129
+ if (!resolved) {
130
+ stream.cancel().catch(() => { });
131
+ throw new Error(`S3 returned unparseable Content-Range for ${key}: ${response.ContentRange}`);
132
+ }
133
+ served = resolved.served;
134
+ totalSize = resolved.totalSize;
135
+ }
136
+ else {
137
+ totalSize = contentLength;
138
+ }
139
+ return {
140
+ body: stream,
141
+ contentLength,
142
+ totalSize,
143
+ range: served,
144
+ etag: response.ETag,
145
+ lastModified: response.LastModified?.toUTCString(),
146
+ // Repr-Digest MUST hash the full representation. AWS omits the checksum
147
+ // on ranged GETs, but a non-conforming S3-compatible backend could
148
+ // return a range-scoped one; never advertise it as a whole-object
149
+ // digest. HEAD (always whole-object) and full 200s still carry it.
150
+ digest: served ? undefined : toReprDigest(response.ChecksumSHA256),
151
+ };
152
+ },
153
+ async createSignedUrl(key, signOpts) {
154
+ try {
155
+ // A signed URL is a 302 redirect target: the client fetches bytes
156
+ // DIRECTLY from S3, bypassing the serve route's security headers
157
+ // (nosniff, CSP, CORP). Force a download disposition AND an inert
158
+ // content type so a stored SVG/HTML polyglot cannot render inline off
159
+ // the header-less origin response. Both are S3 query-parameter
160
+ // overrides honored on the presigned GET; the stored object is
161
+ // untouched. `downloadFilename` only customizes the name.
162
+ const command = new GetObjectCommand({
163
+ Bucket: bucket,
164
+ Key: key,
165
+ ResponseContentType: "application/octet-stream",
166
+ ResponseContentDisposition: buildContentDisposition(signOpts.downloadFilename ?? "download", { type: "attachment" }),
167
+ });
168
+ // Lazy import: the presigner is an optional peer needed ONLY for
169
+ // signed URLs. A static import would crash `partial-content/s3` at
170
+ // module load for every consumer who installed just
171
+ // @aws-sdk/client-s3 (the documented baseline) and never presigns.
172
+ const { getSignedUrl } = await import("@aws-sdk/s3-request-presigner");
173
+ const url = await getSignedUrl(client, command, {
174
+ expiresIn: signOpts.expiresInSeconds,
175
+ });
176
+ return { ok: true, url };
177
+ }
178
+ catch (err) {
179
+ return {
180
+ ok: false,
181
+ error: err instanceof Error ? err.message : String(err),
182
+ };
183
+ }
184
+ },
185
+ };
186
+ }
187
+ // ─── Internal Helpers ───────────────────────────────────────────────────────
188
+ /** A raw base64-encoded SHA-256 hash: exactly 43 base64 chars plus padding. */
189
+ const SHA256_BASE64_RE = /^[A-Za-z0-9+/]{43}=$/;
190
+ /**
191
+ * Validate an S3 checksum for use as an RFC 9530 representation digest.
192
+ *
193
+ * Multipart uploads produce composite checksums ("checksum-of-checksums")
194
+ * in the form `<base64>-<partCount>`, which do NOT hash the object bytes
195
+ * and are invalid inside `Repr-Digest: sha-256=:...:`. Only a plain
196
+ * base64 SHA-256 of the full object passes through.
197
+ */
198
+ function toReprDigest(checksum) {
199
+ return checksum && SHA256_BASE64_RE.test(checksum) ? checksum : undefined;
200
+ }
201
+ /**
202
+ * Check if an S3 error indicates the object does not exist.
203
+ *
204
+ * Covers:
205
+ * - AWS SDK v3 `NoSuchKey` and `NotFound` error classes
206
+ * - Generic S3-compatible providers that set `$metadata.httpStatusCode: 404`
207
+ * - Providers that set `name: "NotFound"` without using the SDK error classes
208
+ */
209
+ function isNotFoundError(err) {
210
+ if (err instanceof NoSuchKey || err instanceof NotFound)
211
+ return true;
212
+ if (err instanceof Error && err.name === "NotFound")
213
+ return true;
214
+ const meta = err.$metadata;
215
+ if (meta?.httpStatusCode === 404)
216
+ return true;
217
+ return false;
218
+ }
219
+ /**
220
+ * Check if an S3 error is a failed IfMatch precondition (object changed).
221
+ * The SDK surfaces this as name "PreconditionFailed" with HTTP 412.
222
+ */
223
+ function isPreconditionFailedError(err) {
224
+ if (err instanceof Error && err.name === "PreconditionFailed")
225
+ return true;
226
+ const meta = err.$metadata;
227
+ return meta?.httpStatusCode === 412;
228
+ }
229
+ /**
230
+ * Check if an S3 error is a transient throttle/overload the client should
231
+ * retry (mapped to a 503, not a 502). The AWS SDK exhausts its own adaptive
232
+ * retries first, so reaching here means sustained pressure.
233
+ *
234
+ * Covers:
235
+ * - `$retryable.throttling` (the SDK's own throttle classification)
236
+ * - HTTP 503 (`SlowDown`) and 429 (`TooManyRequests`) on `$metadata`
237
+ * - the named throttle errors S3-compatible backends raise without setting
238
+ * an HTTP status code
239
+ */
240
+ function isThrottledError(err) {
241
+ if (err.$retryable?.throttling === true)
242
+ return true;
243
+ const meta = err.$metadata;
244
+ if (meta?.httpStatusCode === 503 || meta?.httpStatusCode === 429)
245
+ return true;
246
+ if (err instanceof Error) {
247
+ const name = err.name;
248
+ return (name === "SlowDown" ||
249
+ name === "ThrottlingException" ||
250
+ name === "TooManyRequestsException" ||
251
+ name === "RequestThrottled" ||
252
+ name === "RequestThrottledException" ||
253
+ name === "ProvisionedThroughputExceededException");
254
+ }
255
+ return false;
256
+ }
257
+ /** The ordered error-classification set shared by headObject and getObject. */
258
+ const s3Classifiers = {
259
+ notFound: isNotFoundError,
260
+ changed: isPreconditionFailedError,
261
+ throttled: isThrottledError,
262
+ };
263
+ //# sourceMappingURL=s3.js.map
package/dist/s3.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3.js","sourceRoot":"","sources":["../src/s3.ts"],"names":[],"mappings":"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"}
package/dist/web.d.ts ADDED
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Web / Fetch API adapter for partial-content.
3
+ *
4
+ * Composes a partial-content {@link ObjectStore} with the kernel's conditional
5
+ * request evaluation to produce a standards-compliant file-serving handler.
6
+ *
7
+ * Framework-agnostic: works with Next.js App Router, Hono, SvelteKit, Remix,
8
+ * Cloudflare Workers, Bun.serve, Deno.serve, or any runtime that uses the
9
+ * standard Request/Response API.
10
+ *
11
+ * Handles: 200 (full), 206 (partial), 304 (not modified), 412 (precondition
12
+ * failed), 416 (range not satisfiable), and HEAD (headers only).
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { serveObject } from "partial-content/web";
17
+ * import { s3Store } from "partial-content/s3";
18
+ *
19
+ * const store = s3Store({ client, bucket: "documents" });
20
+ *
21
+ * export const GET = serveObject(store, {
22
+ * key: (req) => req.url.split("/").pop()!,
23
+ * disposition: "inline",
24
+ * });
25
+ * export const HEAD = GET;
26
+ * ```
27
+ *
28
+ * @packageDocumentation
29
+ */
30
+ import { type ObjectStore } from "./index.js";
31
+ export type { CancelSignal, ObjectStore, ObjectMetadata, ObjectStream, ParsedRange, ETagSource, } from "./index.js";
32
+ /**
33
+ * The request surface the orchestrator actually consumes. A standard fetch
34
+ * `Request` satisfies it structurally; server adapters may pass a
35
+ * lightweight view instead, avoiding per-request construction of fetch
36
+ * primitives (an undici `Request` + `Headers` pair costs a measurable
37
+ * fraction of a small-file serve).
38
+ */
39
+ export interface ServableRequest {
40
+ /** HTTP method. GET and HEAD are served; anything else gets a 405. */
41
+ method: string;
42
+ /** Case-insensitive header lookup: a `Headers` object or any equivalent view. */
43
+ headers: {
44
+ get(name: string): string | null;
45
+ };
46
+ /** Client-disconnect signal, forwarded to the storage backend. */
47
+ signal?: AbortSignal;
48
+ }
49
+ /** Metadata resolved by the consumer before the handler runs. */
50
+ export interface ServeContext {
51
+ /** Storage key (path within the bucket). */
52
+ key: string;
53
+ /** Override MIME type. When omitted, defaults to "application/octet-stream". */
54
+ mime?: string;
55
+ /** Filename for Content-Disposition. When omitted, no disposition header is set. */
56
+ filename?: string;
57
+ /**
58
+ * Per-request Cache-Control, overriding {@link ServeObjectOptions.cacheControl}.
59
+ * Lets one handler serve mixed cacheability (immutable content-addressed
60
+ * blobs next to private user uploads) without instantiating N handlers.
61
+ * Used verbatim: the `immutable` option is NOT appended to it.
62
+ */
63
+ cacheControl?: string;
64
+ }
65
+ export interface ServeObjectOptions {
66
+ /**
67
+ * Content-Disposition strategy.
68
+ * - `"inline"` -- render in the browser (PDF, images, video)
69
+ * - `"attachment"` -- force download
70
+ * - A function mapping MIME to disposition type (for per-type policy)
71
+ *
72
+ * @default "attachment"
73
+ */
74
+ disposition?: "inline" | "attachment" | ((mime: string) => "inline" | "attachment");
75
+ /**
76
+ * Cache-Control value for 200/206 responses.
77
+ * @default "private, no-cache"
78
+ */
79
+ cacheControl?: string;
80
+ /**
81
+ * When true, appends `immutable` to the Cache-Control directive.
82
+ * Use for content-addressed storage where the key contains a hash
83
+ * (e.g. `<sha256>.pdf`) and the resource will never change.
84
+ *
85
+ * @default false
86
+ */
87
+ immutable?: boolean;
88
+ /**
89
+ * Security headers applied to every response that carries a body (200, 206).
90
+ * Receives the MIME type so the policy can vary per format (e.g. relaxed
91
+ * CSP for PDF.js, strict sandbox for images).
92
+ *
93
+ * @default No extra security headers.
94
+ */
95
+ securityHeaders?: (mime: string) => Record<string, string>;
96
+ /**
97
+ * Cross-Origin-Resource-Policy value for success responses.
98
+ *
99
+ * Controls which origins can embed this resource:
100
+ * - `"same-origin"` -- only your origin (most secure)
101
+ * - `"same-site"` -- same registrable domain
102
+ * - `"cross-origin"` -- any origin (for public CDN assets)
103
+ *
104
+ * Required for pages with `Cross-Origin-Embedder-Policy: require-corp`.
105
+ * When omitted, no CORP header is set (caller decides).
106
+ */
107
+ crossOriginResourcePolicy?: "same-origin" | "same-site" | "cross-origin";
108
+ /**
109
+ * Timing-Allow-Origin value for success responses.
110
+ *
111
+ * Allows cross-origin pages to read Server-Timing and Resource Timing data
112
+ * via the PerformanceObserver API. Without this, browser security policy
113
+ * zeros out timing metrics for cross-origin resources.
114
+ *
115
+ * Set to `"*"` for public assets, or a specific origin for private APIs.
116
+ */
117
+ timingAllowOrigin?: string;
118
+ /**
119
+ * When true, emits a `Server-Timing` header with storage and evaluation
120
+ * latency metrics. Also calls `onTiming` if provided.
121
+ *
122
+ * Caution: timing data may leak internal architecture details.
123
+ * Enable only when you control the deployment environment.
124
+ *
125
+ * @default false
126
+ */
127
+ timing?: boolean;
128
+ /**
129
+ * Timing callback for observability.
130
+ *
131
+ * Called with structured timing data for every request. Use to ship
132
+ * metrics to your RUM/APM backend.
133
+ */
134
+ onTiming?: (metrics: {
135
+ storeMs: number;
136
+ evaluateMs: number;
137
+ totalMs: number;
138
+ }) => void;
139
+ /**
140
+ * Fallback filename used by buildContentDisposition when no filename is
141
+ * provided and the disposition needs an ASCII fallback.
142
+ *
143
+ * @default "download"
144
+ */
145
+ fallbackFilename?: string;
146
+ /**
147
+ * Error callback for observability.
148
+ *
149
+ * Called when the storage backend throws during HEAD or GET operations.
150
+ * Use this to log errors with request IDs, correlation tokens, and
151
+ * structured metadata for production monitoring.
152
+ *
153
+ * The error is NOT exposed to the client (the response body is generic).
154
+ *
155
+ * @param error - The original error from the storage backend
156
+ * @param context - Additional context about the failed operation
157
+ * (`audit` = the consumer's own onServe hook threw; the response was
158
+ * still served, the hook failure is surfaced here instead of crashing).
159
+ */
160
+ onError?: (error: unknown, context: {
161
+ key: string;
162
+ operation: "head" | "get" | "audit";
163
+ }) => void;
164
+ /**
165
+ * Audit callback for compliance logging (SOC 2 CC7.2, ISO 27001 A.8.15).
166
+ *
167
+ * Called on every response that grants access (200, 206, 304, and 302
168
+ * signed-URL redirects) with structured metadata suitable for audit trail
169
+ * ingestion. Not called on errors (use `onError` for those).
170
+ *
171
+ * @example
172
+ * ```ts
173
+ * onServe: (event) => logger.info({ ...event }, "file.served")
174
+ * ```
175
+ */
176
+ onServe?: (event: ServeAuditEvent) => void;
177
+ /**
178
+ * Transfer-completion callback for true egress accounting and truncation
179
+ * detection.
180
+ *
181
+ * `onServe` fires when headers are committed and reports bytes *granted*
182
+ * (the response Content-Length). This fires once when the response body
183
+ * reaches its terminal state and reports bytes *actually transferred*
184
+ * through it, plus whether it drained fully or the client disconnected
185
+ * early. Use it for egress billing (`bytesTransferred`) or abandonment
186
+ * analytics (`completed === false`).
187
+ *
188
+ * Zero-cost when unset: the body is returned untouched, so byte bodies keep
189
+ * the runtime's static-body fast path. When set, the body is routed through
190
+ * a counting stream (byte bodies are wrapped too, so measurement is uniform
191
+ * across stores) -- a deliberate cost you opt into for the metering.
192
+ *
193
+ * Fires only for 200/206 GET responses (a body was served). Never fires for
194
+ * HEAD, 304, 302, 412, 416, or error responses. A throwing callback cannot
195
+ * corrupt the transfer: its error is routed to `onError` (operation
196
+ * `"audit"`).
197
+ *
198
+ * @example
199
+ * ```ts
200
+ * onTransfer: (e) => {
201
+ * meter.recordEgress(e.key, e.bytesTransferred);
202
+ * if (!e.completed) log.info({ ...e }, "download.abandoned");
203
+ * }
204
+ * ```
205
+ */
206
+ onTransfer?: (event: TransferEvent) => void;
207
+ /**
208
+ * Maximum number of distinct (coalesced) byte ranges to serve as
209
+ * multipart/byteranges before a multi-range request degrades to a full 200.
210
+ *
211
+ * A range-amplification defense: a client sending thousands of tiny or
212
+ * overlapping ranges would otherwise force a large multipart response.
213
+ * Overlapping/adjacent ranges are coalesced first, and if the result still
214
+ * exceeds this cap (or already covers the whole object) the full 200 is
215
+ * served instead. Matches the intent of nginx `max_ranges` and Go's
216
+ * sum-of-ranges check.
217
+ *
218
+ * @default 50
219
+ */
220
+ maxRanges?: number;
221
+ /**
222
+ * When true, appends `; charset=utf-8` to textual Content-Type values
223
+ * (text/*, application/json, application/xml, etc.) if not already present.
224
+ *
225
+ * Prevents UTF-7 encoding-sniffing XSS in legacy browsers that probe for
226
+ * a charset declaration and fall back to auto-detection when none is found.
227
+ *
228
+ * @default true
229
+ */
230
+ enforceCharset?: boolean;
231
+ }
232
+ /** Structured audit event for compliance logging. */
233
+ export interface ServeAuditEvent {
234
+ /** Storage key that was served. */
235
+ key: string;
236
+ /**
237
+ * Request method. Distinguishes a HEAD metadata probe (no bytes transferred)
238
+ * from a GET, which otherwise both surface as `status: 200, bytesServed: 0`
239
+ * for an empty object.
240
+ */
241
+ method: "GET" | "HEAD";
242
+ /**
243
+ * HTTP status code. 302 = access granted via signed-URL redirect.
244
+ * 412/416 = access DENIED (failed precondition / unsatisfiable range):
245
+ * emitted so compliance trails capture denials, not only grants -- a 412
246
+ * is an optimistic-concurrency conflict signal auditors ask for.
247
+ */
248
+ status: 200 | 206 | 302 | 304 | 412 | 416;
249
+ /** MIME type of the served content. */
250
+ mime: string;
251
+ /**
252
+ * Body bytes GRANTED, not confirmed transferred: the body's
253
+ * Content-Length on a 200/206 GET (the event fires when headers are
254
+ * committed, before the stream drains, so a client disconnect can
255
+ * receive fewer bytes). 0 for HEAD, 304, 302, 412, and 416. Treat as
256
+ * access volume, never as exfiltration volume.
257
+ */
258
+ bytesServed: number;
259
+ /** Range start (inclusive), present only on 206. */
260
+ rangeStart?: number;
261
+ /** Range end (inclusive), present only on 206. */
262
+ rangeEnd?: number;
263
+ /** ETag of the served representation, if available. */
264
+ etag?: string;
265
+ }
266
+ /** Structured transfer-completion event ({@link ServeObjectOptions.onTransfer}). */
267
+ export interface TransferEvent {
268
+ /** Storage key that was served. */
269
+ key: string;
270
+ /** Always GET: only GET responses carry a body to transfer. */
271
+ method: "GET";
272
+ /** HTTP status of the served body. */
273
+ status: 200 | 206;
274
+ /**
275
+ * Bytes GRANTED: the response body's Content-Length (206 range span or 200
276
+ * full size). Compare against {@link bytesTransferred} to detect truncation.
277
+ */
278
+ bytesExpected: number;
279
+ /**
280
+ * Bytes ACTUALLY read through the response body before it reached its
281
+ * terminal state. Equals {@link bytesExpected} on a fully-drained transfer;
282
+ * less when the client disconnected or cancelled early.
283
+ */
284
+ bytesTransferred: number;
285
+ /**
286
+ * `true` if the body drained completely, `false` if it was cancelled /
287
+ * the client disconnected before the last byte. The honest signal for
288
+ * egress billing and abandonment analytics.
289
+ */
290
+ completed: boolean;
291
+ /** Range start (inclusive), present only on 206. */
292
+ rangeStart?: number;
293
+ /** Range end (inclusive), present only on 206. */
294
+ rangeEnd?: number;
295
+ }
296
+ /**
297
+ * Serve an object from an {@link ObjectStore} with full RFC 7232/7233 support.
298
+ *
299
+ * Returns a function that takes a standard `Request` plus {@link ServeContext}
300
+ * and returns a `Response`. This is framework-agnostic: it works with Next.js
301
+ * App Router, Hono, SvelteKit, Remix, or any runtime that uses the Fetch API.
302
+ *
303
+ * The evaluation chain:
304
+ * 1. HEAD -> get metadata (ETag, Last-Modified, size)
305
+ * 2. Preconditions (If-Match / If-Unmodified-Since) -> 412
306
+ * 3. Freshness (If-None-Match / If-Modified-Since) -> 304
307
+ * 4. If-Range validation -> honor or ignore Range
308
+ * 5. Range parsing -> 206 or 416
309
+ * 6. Stream bytes -> 200 or 206
310
+ *
311
+ * TOCTOU guard: ETag/Last-Modified from the GET response are preferred over
312
+ * HEAD values. If the GET response omits Content-Range for a range request,
313
+ * the handler degrades to 200 (never emits a lying 206).
314
+ */
315
+ export interface RawResponseParts {
316
+ status: number;
317
+ /** Reason phrase (e.g. "Partial Content", "Client Closed Request"). */
318
+ statusText: string;
319
+ headers: Record<string, string>;
320
+ /**
321
+ * `null` for bodyless statuses (HEAD, 304, 412, 416, 302, 405).
322
+ * `<ArrayBuffer>`-backed so `new Response(parts.body)` compiles under DOM lib.
323
+ */
324
+ body: ReadableStream<Uint8Array<ArrayBuffer>> | Uint8Array<ArrayBuffer> | null;
325
+ }
326
+ export declare function serveObject(store: ObjectStore, opts?: ServeObjectOptions): (req: ServableRequest, ctx: ServeContext) => Promise<Response>;
327
+ /**
328
+ * The engine behind serveObject, exposed for server adapters: identical
329
+ * protocol behavior, but the result is RawResponseParts instead of a
330
+ * Response. Use this when your server writes status/headers/body natively
331
+ * (the bundled node adapter does) -- constructing a Response + Headers pair
332
+ * per request just to immediately deconstruct them is measurable overhead
333
+ * on hot paths.
334
+ */
335
+ export declare function serveObjectRaw(store: ObjectStore, opts?: ServeObjectOptions): (req: ServableRequest, ctx: ServeContext) => Promise<RawResponseParts>;
336
+ //# sourceMappingURL=web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"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"}