@sdxc/http 0.0.0-pre.1

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.
@@ -0,0 +1,53 @@
1
+ /**
2
+ * `If-Match` preconditions for writes: the check that a client is updating the
3
+ * version of a resource it actually read. The outcome is a `Result`, so turning a
4
+ * failed precondition into a `412` stays the caller's decision.
5
+ *
6
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
7
+ * @copyright Sergio Xalambrí 2026
8
+ */
9
+ import type { Result } from "@sdxc/result";
10
+ /**
11
+ * The current validator a write is checked against.
12
+ */
13
+ export interface PreconditionOptions {
14
+ /**
15
+ * The resource's current entity tag, as `etag()` produces it. A weak tag never
16
+ * satisfies an `If-Match`, because equivalent content is not the same version.
17
+ */
18
+ etag: string;
19
+ }
20
+ /**
21
+ * The client's `If-Match` did not name the version the resource is currently at,
22
+ * which means the write would overwrite a change the client never saw.
23
+ *
24
+ * Delivered inside a `Failure` for the caller to inspect and answer with a `412`.
25
+ *
26
+ * @example
27
+ * if (isFailure(result)) return preconditionFailed({ error: result.error.message });
28
+ */
29
+ export declare class PreconditionFailedError extends Error {
30
+ /** The validator the resource is actually at, for logs and diagnostics. */
31
+ readonly etag: string;
32
+ /**
33
+ * @param etag - The resource's current entity tag.
34
+ */
35
+ constructor(etag: string);
36
+ }
37
+ /**
38
+ * Checks a write request's `If-Match` against the resource's current validator.
39
+ *
40
+ * An absent `If-Match` or a `*` always matches; every other value is compared
41
+ * strongly, so a weak tag never satisfies it, binding writes to a version read.
42
+ *
43
+ * @param request - The incoming write request.
44
+ * @param options - The resource's current entity tag.
45
+ * @returns The satisfied validator, or a `PreconditionFailedError` to answer with a `412`.
46
+ *
47
+ * @example
48
+ * let checked = precondition(request, { etag: current });
49
+ * if (isFailure(checked)) return preconditionFailed("<h1>Precondition Failed</h1>");
50
+ * @example
51
+ * precondition(request, { etag: '"abc"' }); // success('"abc"') when If-Match is absent
52
+ */
53
+ export declare function precondition(request: Request, options: PreconditionOptions): Result<string, PreconditionFailedError>;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * `If-Match` preconditions for writes: the check that a client is updating the
3
+ * version of a resource it actually read. The outcome is a `Result`, so turning a
4
+ * failed precondition into a `412` stays the caller's decision.
5
+ *
6
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
7
+ * @copyright Sergio Xalambrí 2026
8
+ */
9
+ import { failure, success } from "@sdxc/result";
10
+ import { IfMatch } from "remix/headers";
11
+ /**
12
+ * The client's `If-Match` did not name the version the resource is currently at,
13
+ * which means the write would overwrite a change the client never saw.
14
+ *
15
+ * Delivered inside a `Failure` for the caller to inspect and answer with a `412`.
16
+ *
17
+ * @example
18
+ * if (isFailure(result)) return preconditionFailed({ error: result.error.message });
19
+ */
20
+ export class PreconditionFailedError extends Error {
21
+ /** The validator the resource is actually at, for logs and diagnostics. */
22
+ etag;
23
+ /**
24
+ * @param etag - The resource's current entity tag.
25
+ */
26
+ constructor(etag) {
27
+ super(`Precondition failed: resource is at ${etag}`);
28
+ this.name = "PreconditionFailedError";
29
+ this.etag = etag;
30
+ }
31
+ }
32
+ /**
33
+ * Checks a write request's `If-Match` against the resource's current validator.
34
+ *
35
+ * An absent `If-Match` or a `*` always matches; every other value is compared
36
+ * strongly, so a weak tag never satisfies it, binding writes to a version read.
37
+ *
38
+ * @param request - The incoming write request.
39
+ * @param options - The resource's current entity tag.
40
+ * @returns The satisfied validator, or a `PreconditionFailedError` to answer with a `412`.
41
+ *
42
+ * @example
43
+ * let checked = precondition(request, { etag: current });
44
+ * if (isFailure(checked)) return preconditionFailed("<h1>Precondition Failed</h1>");
45
+ * @example
46
+ * precondition(request, { etag: '"abc"' }); // success('"abc"') when If-Match is absent
47
+ */
48
+ export function precondition(request, options) {
49
+ let ifMatch = IfMatch.from(request.headers.get("If-Match"));
50
+ if (!ifMatch.matches(options.etag))
51
+ return failure(new PreconditionFailedError(options.etag));
52
+ return success(options.etag);
53
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Merging of `Vary` header names, so a response that negotiates on one dimension
3
+ * can add a second without discarding the first. Header names are the cache key,
4
+ * which is why this merges instead of replacing.
5
+ *
6
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
7
+ * @copyright Sergio Xalambrí 2026
8
+ */
9
+ /**
10
+ * Adds request header names to a response's `Vary`, keeping whatever is
11
+ * there. Each header multiplies the cache variants stored for the URL, so
12
+ * varying on `Cookie` disables shared caching; prefer `Policies.private()`.
13
+ *
14
+ * @param headers - Response headers to merge into; mutated in place.
15
+ * @param names - Request header names the response depends on.
16
+ * @returns The same `Headers` object, for chaining.
17
+ *
18
+ * @example
19
+ * vary(headers, ["Accept-Language", "Cookie"]);
20
+ * @example
21
+ * let headers = new Headers({ Vary: "Accept-Encoding" });
22
+ * vary(headers, "Accept-Language").get("Vary"); // "accept-encoding, accept-language"
23
+ */
24
+ export declare function vary(headers: Headers, names: string | string[]): Headers;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Merging of `Vary` header names, so a response that negotiates on one dimension
3
+ * can add a second without discarding the first. Header names are the cache key,
4
+ * which is why this merges instead of replacing.
5
+ *
6
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
7
+ * @copyright Sergio Xalambrí 2026
8
+ */
9
+ import { Vary } from "remix/headers";
10
+ /**
11
+ * Adds request header names to a response's `Vary`, keeping whatever is
12
+ * there. Each header multiplies the cache variants stored for the URL, so
13
+ * varying on `Cookie` disables shared caching; prefer `Policies.private()`.
14
+ *
15
+ * @param headers - Response headers to merge into; mutated in place.
16
+ * @param names - Request header names the response depends on.
17
+ * @returns The same `Headers` object, for chaining.
18
+ *
19
+ * @example
20
+ * vary(headers, ["Accept-Language", "Cookie"]);
21
+ * @example
22
+ * let headers = new Headers({ Vary: "Accept-Encoding" });
23
+ * vary(headers, "Accept-Language").get("Vary"); // "accept-encoding, accept-language"
24
+ */
25
+ export function vary(headers, names) {
26
+ let value = Vary.from(headers.get("Vary"));
27
+ for (let name of typeof names === "string" ? [names] : names)
28
+ value.add(name);
29
+ if (value.size === 0)
30
+ return headers;
31
+ headers.set("Vary", value.toString());
32
+ return headers;
33
+ }
@@ -0,0 +1,312 @@
1
+ /**
2
+ * MIME type constants for HTTP `Content-Type` header values.
3
+ *
4
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
5
+ * @copyright Sergio Xalambrí 2026
6
+ */
7
+ /**
8
+ * Used for simple text responses without any formatting.
9
+ * @example
10
+ * headers.set("Content-Type", Text);
11
+ * @example
12
+ * return new Response("Hello", { headers: { "Content-Type": Text } });
13
+ */
14
+ export declare const Text: "text/plain";
15
+ /**
16
+ * Used for web pages and HTML document responses.
17
+ * @example
18
+ * headers.set("Content-Type", HTML);
19
+ * @example
20
+ * return new Response("<h1>Hello</h1>", { headers: { "Content-Type": HTML } });
21
+ */
22
+ export declare const HTML: "text/html";
23
+ /**
24
+ * Used for stylesheet responses.
25
+ * @example
26
+ * headers.set("Content-Type", CSS);
27
+ * @example
28
+ * return new Response("body { color: red; }", { headers: { "Content-Type": CSS } });
29
+ */
30
+ export declare const CSS: "text/css";
31
+ /**
32
+ * Used for JavaScript file responses.
33
+ * @example
34
+ * headers.set("Content-Type", JavaScript);
35
+ * @example
36
+ * return new Response("console.log('hi')", { headers: { "Content-Type": JavaScript } });
37
+ */
38
+ export declare const JavaScript: "text/javascript";
39
+ /**
40
+ * Used for comma-separated values data exports.
41
+ * @example
42
+ * headers.set("Content-Type", CSV);
43
+ * @example
44
+ * return new Response("name,age\nJohn,30", { headers: { "Content-Type": CSV } });
45
+ */
46
+ export declare const CSV: "text/csv";
47
+ /**
48
+ * Used for XML documents intended for human readability.
49
+ * @example
50
+ * headers.set("Content-Type", XML);
51
+ * @example
52
+ * return new Response("<root><item/></root>", { headers: { "Content-Type": XML } });
53
+ */
54
+ export declare const XML: "text/xml";
55
+ /**
56
+ * Used for Markdown-formatted text responses.
57
+ * @example
58
+ * headers.set("Content-Type", Markdown);
59
+ * @example
60
+ * return new Response("# Hello", { headers: { "Content-Type": Markdown } });
61
+ */
62
+ export declare const Markdown: "text/markdown";
63
+ /**
64
+ * Used for API responses and requests with structured data.
65
+ * Named `Json` (not `JSON`) to avoid shadowing the global `JSON` object.
66
+ * @example
67
+ * headers.set("Content-Type", Json);
68
+ * @example
69
+ * if (contentType === Json) { parseJson(body); }
70
+ */
71
+ export declare const Json: "application/json";
72
+ /**
73
+ * Used for newline-delimited JSON streaming responses.
74
+ * @example
75
+ * headers.set("Content-Type", JSONLines);
76
+ * @example
77
+ * return new Response('{"a":1}\n{"b":2}', { headers: { "Content-Type": JSONLines } });
78
+ */
79
+ export declare const JSONLines: "application/jsonl";
80
+ /**
81
+ * PDF content type.
82
+ * Used for PDF document responses.
83
+ * @example
84
+ * headers.set("Content-Type", PDF);
85
+ * @example
86
+ * return new Response(pdfBuffer, { headers: { "Content-Type": PDF } });
87
+ */
88
+ export declare const PDF: "application/pdf";
89
+ /**
90
+ * ZIP archive content type.
91
+ * Used for compressed archive file responses.
92
+ * @example
93
+ * headers.set("Content-Type", ZIP);
94
+ * @example
95
+ * return new Response(zipBuffer, { headers: { "Content-Type": ZIP } });
96
+ */
97
+ export declare const ZIP: "application/zip";
98
+ /**
99
+ * GZip compressed content type.
100
+ * Used for gzip-compressed file responses.
101
+ * @example
102
+ * headers.set("Content-Type", GZip);
103
+ * @example
104
+ * return new Response(gzipBuffer, { headers: { "Content-Type": GZip } });
105
+ */
106
+ export declare const GZip: "application/gzip";
107
+ /**
108
+ * Multipart form data content type.
109
+ * Used for form submissions with file uploads.
110
+ * @example
111
+ * if (contentType.startsWith(FormData)) { parseFormData(body); }
112
+ * @example
113
+ * headers.set("Content-Type", FormData);
114
+ */
115
+ export declare const FormData: "multipart/form-data";
116
+ /**
117
+ * URL-encoded form content type.
118
+ * Used for simple form submissions without files.
119
+ * @example
120
+ * headers.set("Content-Type", FormURLEncoded);
121
+ * @example
122
+ * if (contentType === FormURLEncoded) { parseURLEncoded(body); }
123
+ */
124
+ export declare const FormURLEncoded: "application/x-www-form-urlencoded";
125
+ /**
126
+ * Binary octet stream content type.
127
+ * Used for arbitrary binary data when type is unknown.
128
+ * @example
129
+ * headers.set("Content-Type", OctetStream);
130
+ * @example
131
+ * return new Response(binaryData, { headers: { "Content-Type": OctetStream } });
132
+ */
133
+ export declare const OctetStream: "application/octet-stream";
134
+ /**
135
+ * Used for XML data in API responses and machine processing.
136
+ * @example
137
+ * headers.set("Content-Type", ApplicationXML);
138
+ * @example
139
+ * return new Response(xmlData, { headers: { "Content-Type": ApplicationXML } });
140
+ */
141
+ export declare const ApplicationXML: "application/xml";
142
+ /**
143
+ * PNG image content type.
144
+ * Used for lossless raster image responses.
145
+ * @example
146
+ * headers.set("Content-Type", PNG);
147
+ * @example
148
+ * return new Response(pngBuffer, { headers: { "Content-Type": PNG } });
149
+ */
150
+ export declare const PNG: "image/png";
151
+ /**
152
+ * JPEG image content type.
153
+ * Used for lossy compressed photo responses.
154
+ * @example
155
+ * headers.set("Content-Type", JPEG);
156
+ * @example
157
+ * return new Response(jpegBuffer, { headers: { "Content-Type": JPEG } });
158
+ */
159
+ export declare const JPEG: "image/jpeg";
160
+ /**
161
+ * GIF image content type.
162
+ * Used for animated or simple graphics responses.
163
+ * @example
164
+ * headers.set("Content-Type", GIF);
165
+ * @example
166
+ * return new Response(gifBuffer, { headers: { "Content-Type": GIF } });
167
+ */
168
+ export declare const GIF: "image/gif";
169
+ /**
170
+ * WebP image content type.
171
+ * Used for modern lossy/lossless image responses.
172
+ * @example
173
+ * headers.set("Content-Type", WebP);
174
+ * @example
175
+ * return new Response(webpBuffer, { headers: { "Content-Type": WebP } });
176
+ */
177
+ export declare const WebP: "image/webp";
178
+ /**
179
+ * SVG image content type.
180
+ * Used for vector graphics responses.
181
+ * @example
182
+ * headers.set("Content-Type", SVG);
183
+ * @example
184
+ * return new Response(svgString, { headers: { "Content-Type": SVG } });
185
+ */
186
+ export declare const SVG: "image/svg+xml";
187
+ /**
188
+ * ICO image content type.
189
+ * Used for favicon and icon file responses.
190
+ * @example
191
+ * headers.set("Content-Type", ICO);
192
+ * @example
193
+ * return new Response(icoBuffer, { headers: { "Content-Type": ICO } });
194
+ */
195
+ export declare const ICO: "image/x-icon";
196
+ /**
197
+ * AVIF image content type.
198
+ * Used for highly compressed modern image responses.
199
+ * @example
200
+ * headers.set("Content-Type", AVIF);
201
+ * @example
202
+ * return new Response(avifBuffer, { headers: { "Content-Type": AVIF } });
203
+ */
204
+ export declare const AVIF: "image/avif";
205
+ /**
206
+ * MP3 audio content type.
207
+ * Used for MPEG audio file responses.
208
+ * @example
209
+ * headers.set("Content-Type", MP3);
210
+ * @example
211
+ * return new Response(mp3Buffer, { headers: { "Content-Type": MP3 } });
212
+ */
213
+ export declare const MP3: "audio/mpeg";
214
+ /**
215
+ * WAV audio content type.
216
+ * Used for uncompressed audio file responses.
217
+ * @example
218
+ * headers.set("Content-Type", WAV);
219
+ * @example
220
+ * return new Response(wavBuffer, { headers: { "Content-Type": WAV } });
221
+ */
222
+ export declare const WAV: "audio/wav";
223
+ /**
224
+ * OGG audio content type.
225
+ * Used for Ogg Vorbis audio file responses.
226
+ * @example
227
+ * headers.set("Content-Type", OGG);
228
+ * @example
229
+ * return new Response(oggBuffer, { headers: { "Content-Type": OGG } });
230
+ */
231
+ export declare const OGG: "audio/ogg";
232
+ /**
233
+ * WebM audio content type.
234
+ * Used for WebM audio-only file responses.
235
+ * @example
236
+ * headers.set("Content-Type", WebMAudio);
237
+ * @example
238
+ * return new Response(webmAudioBuffer, { headers: { "Content-Type": WebMAudio } });
239
+ */
240
+ export declare const WebMAudio: "audio/webm";
241
+ /**
242
+ * MP4 video content type.
243
+ * Used for MPEG-4 video file responses.
244
+ * @example
245
+ * headers.set("Content-Type", MP4);
246
+ * @example
247
+ * return new Response(mp4Buffer, { headers: { "Content-Type": MP4 } });
248
+ */
249
+ export declare const MP4: "video/mp4";
250
+ /**
251
+ * WebM video content type.
252
+ * Used for WebM video file responses.
253
+ * @example
254
+ * headers.set("Content-Type", WebMVideo);
255
+ * @example
256
+ * return new Response(webmVideoBuffer, { headers: { "Content-Type": WebMVideo } });
257
+ */
258
+ export declare const WebMVideo: "video/webm";
259
+ /**
260
+ * WOFF font content type.
261
+ * Used for Web Open Font Format file responses.
262
+ * @example
263
+ * headers.set("Content-Type", WOFF);
264
+ * @example
265
+ * return new Response(woffBuffer, { headers: { "Content-Type": WOFF } });
266
+ */
267
+ export declare const WOFF: "font/woff";
268
+ /**
269
+ * WOFF2 font content type.
270
+ * Used for Web Open Font Format 2.0 file responses.
271
+ * @example
272
+ * headers.set("Content-Type", WOFF2);
273
+ * @example
274
+ * return new Response(woff2Buffer, { headers: { "Content-Type": WOFF2 } });
275
+ */
276
+ export declare const WOFF2: "font/woff2";
277
+ /**
278
+ * TrueType font content type.
279
+ * Used for TTF font file responses.
280
+ * @example
281
+ * headers.set("Content-Type", TTF);
282
+ * @example
283
+ * return new Response(ttfBuffer, { headers: { "Content-Type": TTF } });
284
+ */
285
+ export declare const TTF: "font/ttf";
286
+ /**
287
+ * OpenType font content type.
288
+ * Used for OTF font file responses.
289
+ * @example
290
+ * headers.set("Content-Type", OTF);
291
+ * @example
292
+ * return new Response(otfBuffer, { headers: { "Content-Type": OTF } });
293
+ */
294
+ export declare const OTF: "font/otf";
295
+ /**
296
+ * Server-Sent Events content type.
297
+ * Used for streaming event responses to clients.
298
+ * @example
299
+ * headers.set("Content-Type", EventStream);
300
+ * @example
301
+ * return new Response(stream, { headers: { "Content-Type": EventStream } });
302
+ */
303
+ export declare const EventStream: "text/event-stream";
304
+ /**
305
+ * Newline-delimited JSON content type.
306
+ * Used for streaming JSON objects one per line.
307
+ * @example
308
+ * headers.set("Content-Type", NDJson);
309
+ * @example
310
+ * return new Response(ndjsonStream, { headers: { "Content-Type": NDJson } });
311
+ */
312
+ export declare const NDJson: "application/x-ndjson";