cpeak 2.6.0 → 2.8.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/lib/index.ts CHANGED
@@ -3,46 +3,32 @@ import fs from "node:fs/promises";
3
3
  import { createReadStream } from "node:fs";
4
4
  import { pipeline } from "node:stream/promises";
5
5
 
6
+ import type net from "node:net";
7
+ import type { Readable } from "node:stream";
8
+ import type { Buffer } from "node:buffer";
9
+
10
+ import {
11
+ resolveCompressionOptions,
12
+ compressAndSend
13
+ } from "./internal/compression";
14
+ import { MIME_TYPES } from "./internal/mimeTypes";
15
+ import { Router } from "./internal/router";
16
+ import { frameworkError, ErrorCode } from "./internal/errors";
17
+
18
+ export { frameworkError, ErrorCode };
19
+
6
20
  import type {
7
21
  StringMap,
22
+ CpeakHttpServer,
23
+ CpeakOptions,
8
24
  CpeakRequest,
9
25
  CpeakResponse,
10
26
  Middleware,
11
27
  RouteMiddleware,
12
- Handler,
13
- RoutesMap
28
+ Handler
14
29
  } from "./types";
15
30
 
16
- // A utility function to create an error with a custom stack trace
17
- export function frameworkError(
18
- message: string,
19
- skipFn: Function,
20
- code?: string,
21
- status?: number
22
- ) {
23
- const err = new Error(message) as Error & {
24
- code?: string;
25
- cpeak_err?: boolean;
26
- };
27
- Error.captureStackTrace(err, skipFn);
28
-
29
- err.cpeak_err = true;
30
-
31
- if (code) err.code = code;
32
- if (status) (err as any).status = status;
33
-
34
- return err;
35
- }
36
-
37
- export enum ErrorCode {
38
- MISSING_MIME = "CPEAK_ERR_MISSING_MIME",
39
- FILE_NOT_FOUND = "CPEAK_ERR_FILE_NOT_FOUND",
40
- NOT_A_FILE = "CPEAK_ERR_NOT_A_FILE",
41
- SEND_FILE_FAIL = "CPEAK_ERR_SEND_FILE_FAIL",
42
- INVALID_JSON = "CPEAK_ERR_INVALID_JSON",
43
- PAYLOAD_TOO_LARGE = "CPEAK_ERR_PAYLOAD_TOO_LARGE",
44
- WEAK_SECRET = "CPEAK_ERR_WEAK_SECRET"
45
- }
31
+ import type { ResolvedCompressionConfig } from "./internal/types";
46
32
 
47
33
  export class CpeakIncomingMessage extends http.IncomingMessage {
48
34
  // We define body and params here for better V8 optimization (not changing the shape of the object at runtime)
@@ -73,14 +59,22 @@ export class CpeakIncomingMessage extends http.IncomingMessage {
73
59
  }
74
60
 
75
61
  export class CpeakServerResponse extends http.ServerResponse<CpeakIncomingMessage> {
62
+ // Set per-request from the Cpeak instance. Undefined when compression isn't enabled.
63
+ _compression?: ResolvedCompressionConfig;
64
+
76
65
  // Send a file back to the client
77
- async sendFile(path: string, mime: string) {
66
+ async sendFile(path: string, mime?: string) {
78
67
  if (!mime) {
79
- throw frameworkError(
80
- 'MIME type is missing. Use res.sendFile(path, "mime-type").',
81
- this.sendFile,
82
- ErrorCode.MISSING_MIME
83
- );
68
+ const dotIndex = path.lastIndexOf(".");
69
+ const fileExtension = dotIndex >= 0 ? path.slice(dotIndex + 1) : "";
70
+ mime = MIME_TYPES[fileExtension];
71
+ if (!mime) {
72
+ throw frameworkError(
73
+ `MIME type is missing for "${path}". Pass it as the second argument or register the extension via cpeak({ mimeTypes: { ${fileExtension || "ext"}: "..." } }).`,
74
+ this.sendFile,
75
+ ErrorCode.MISSING_MIME
76
+ );
77
+ }
84
78
  }
85
79
 
86
80
  try {
@@ -93,6 +87,17 @@ export class CpeakServerResponse extends http.ServerResponse<CpeakIncomingMessag
93
87
  );
94
88
  }
95
89
 
90
+ if (this._compression) {
91
+ await compressAndSend(
92
+ this,
93
+ mime,
94
+ createReadStream(path),
95
+ this._compression,
96
+ stat.size
97
+ );
98
+ return;
99
+ }
100
+
96
101
  this.setHeader("Content-Type", mime);
97
102
  this.setHeader("Content-Length", String(stat.size));
98
103
 
@@ -136,43 +141,96 @@ export class CpeakServerResponse extends http.ServerResponse<CpeakIncomingMessag
136
141
  this.end();
137
142
  }
138
143
 
139
- // Send a json data back to the client (for small json data, less than the highWaterMark)
140
- json(data: any) {
141
- // This is only good for bodies that their size is less than the highWaterMark value
144
+ // Send a json data back to the client.
145
+ // This is only good for bodies that their size is less than the highWaterMark value.
146
+ json(data: any): Promise<void> {
147
+ const body = JSON.stringify(data);
148
+ if (this._compression) {
149
+ return compressAndSend(this, "application/json", body, this._compression);
150
+ }
142
151
  this.setHeader("Content-Type", "application/json");
143
- this.end(JSON.stringify(data));
152
+ this.end(body);
153
+ return Promise.resolve();
154
+ }
155
+
156
+ // Explicit compression entry point. A developer can use this in any custom handler to compress arbitrary responses
157
+ compress(
158
+ mime: string,
159
+ body: Buffer | string | Readable,
160
+ size?: number
161
+ ): Promise<void> {
162
+ if (!this._compression) {
163
+ throw frameworkError(
164
+ "compression is not enabled. Pass `compression` to cpeak({ compression: true | { ... } }) to use res.compress.",
165
+ this.compress,
166
+ ErrorCode.COMPRESSION_NOT_ENABLED
167
+ );
168
+ }
169
+ return compressAndSend(this, mime, body, this._compression, size);
144
170
  }
145
171
  }
146
172
 
147
173
  export class Cpeak {
148
- #server: http.Server<typeof CpeakIncomingMessage, typeof CpeakServerResponse>;
149
- #routes: RoutesMap;
174
+ #server: CpeakHttpServer;
175
+ #router: Router;
150
176
  #middleware: Middleware[];
151
177
  #handleErr?: (err: unknown, req: CpeakRequest, res: CpeakResponse) => void;
178
+ #fallback?: Handler;
179
+ #compression?: ResolvedCompressionConfig;
152
180
 
153
- constructor() {
181
+ constructor(options: CpeakOptions = {}) {
154
182
  this.#server = http.createServer({
155
183
  IncomingMessage: CpeakIncomingMessage,
156
184
  ServerResponse: CpeakServerResponse
157
185
  });
158
- this.#routes = {};
186
+ this.#router = new Router();
159
187
  this.#middleware = [];
160
188
 
189
+ // Resolve compression options once at app startup.
190
+ if (options.compression) {
191
+ this.#compression = resolveCompressionOptions(options.compression);
192
+ }
193
+
194
+ // Merge developer-supplied mime types with the defaults once at startup
195
+ if (options.mimeTypes) Object.assign(MIME_TYPES, options.mimeTypes);
196
+
161
197
  this.#server.on(
162
198
  "request",
163
199
  async (req: CpeakRequest, res: CpeakResponse) => {
200
+ res._compression = this.#compression;
201
+
164
202
  // Get the url without the URL parameters (query strings)
165
203
  const qIndex = req.url?.indexOf("?");
166
204
  const urlWithoutQueries =
167
205
  qIndex === -1 ? req.url || "" : req.url?.substring(0, qIndex);
168
206
 
169
- const dispatchError = (error: unknown) => {
207
+ // Routes every error path through the registered handleErr. Awaits
208
+ // handleErr so its own async work (or a rejecting res.json under
209
+ // compression) is caught. If handleErr itself fails, we log and send a
210
+ // bare 500 so the client never gets a hung socket. Returns a Promise
211
+ // that never rejects to avoid unhandled promise rejections in case of errors in handleErr.
212
+ const dispatchError = async (error: unknown) => {
170
213
  if (res.headersSent) {
171
214
  req.socket?.destroy();
172
215
  return;
173
216
  }
174
217
  res.setHeader("Connection", "close");
175
- this.#handleErr?.(error, req, res);
218
+ try {
219
+ await this.#handleErr?.(error, req, res);
220
+ } catch (handlerFailure) {
221
+ console.error(
222
+ "[cpeak] handleErr failed while processing:",
223
+ error,
224
+ "\nReason:",
225
+ handlerFailure
226
+ );
227
+ if (!res.headersSent) {
228
+ try {
229
+ res.statusCode = 500;
230
+ res.end();
231
+ } catch {}
232
+ }
233
+ }
176
234
  };
177
235
 
178
236
  // Run all the specific middleware functions for that router only and then run the handler
@@ -186,29 +244,22 @@ export class Cpeak {
186
244
  // Our exit point...
187
245
  if (index === middleware.length) {
188
246
  // Call the route handler with the modified req and res objects.
189
- // Also handle the promise errors by passing them to the handleErr to save developers from having to manually wrap every handler in try catch.
247
+ // Also handle the promise errors by passing them to handleErr to save developers from having to manually wrap every handler in try/catch.
190
248
  try {
191
- await cb(req, res, dispatchError);
249
+ await cb(req, res);
192
250
  } catch (error) {
193
251
  dispatchError(error);
194
252
  }
195
253
  } else {
196
- // Handle the promise errors by passing them to the handleErr to save developers from having to manually wrap every handler middleware in try catch.
254
+ // Handle the promise errors by passing them to handleErr to save developers from having to manually wrap every route middleware in try/catch.
197
255
  try {
198
- await middleware[index](
199
- req,
200
- res,
201
- // The next function
202
- async (error) => {
203
- // this function only accepts an error argument to be more compatible with NPM modules that are built for express
204
- if (error) {
205
- return dispatchError(error);
206
- }
207
- await runHandler(req, res, middleware, cb, index + 1);
208
- },
209
- // Error handler for a route middleware
210
- dispatchError
211
- );
256
+ await middleware[index](req, res, async (error?: unknown) => {
257
+ // this function only accepts an error argument to be more compatible with NPM modules that are built for express
258
+ if (error) {
259
+ return dispatchError(error);
260
+ }
261
+ await runHandler(req, res, middleware, cb, index + 1);
262
+ });
212
263
  } catch (error) {
213
264
  dispatchError(error);
214
265
  }
@@ -224,32 +275,30 @@ export class Cpeak {
224
275
  ) => {
225
276
  // Our exit point...
226
277
  if (index === middleware.length) {
227
- const routes = this.#routes[req.method?.toLowerCase() || ""];
228
- if (routes && typeof routes[Symbol.iterator] === "function")
229
- for (const route of routes) {
230
- const match = urlWithoutQueries?.match(route.regex);
231
-
232
- if (match) {
233
- // Parse the URL path variables from the matched route (like /users/:id)
234
- const pathVariables = this.#extractPathVariables(
235
- route.path,
236
- match
237
- );
238
-
239
- // We will call this params to be more familiar with other node.js frameworks.
240
- req.params = pathVariables;
241
-
242
- return await runHandler(
243
- req,
244
- res,
245
- route.middleware,
246
- route.cb,
247
- 0
248
- );
249
- }
278
+ const method = req.method?.toLowerCase() || "";
279
+ const found = this.#router.find(method, urlWithoutQueries || "");
280
+
281
+ if (found) {
282
+ req.params = found.params;
283
+ return await runHandler(
284
+ req,
285
+ res,
286
+ found.middleware,
287
+ found.handler,
288
+ 0
289
+ );
290
+ }
291
+
292
+ // If a fallback handler is registered, run it before falling back to the default 404
293
+ if (this.#fallback) {
294
+ try {
295
+ return await this.#fallback(req, res);
296
+ } catch (error) {
297
+ return dispatchError(error);
250
298
  }
299
+ }
251
300
 
252
- // If the requested route dose not exist, return 404
301
+ // If the requested route dose not exist, and developer has not registered the fallback handler, return 404
253
302
  return res
254
303
  .status(404)
255
304
  .json({ error: `Cannot ${req.method} ${urlWithoutQueries}` });
@@ -273,8 +322,6 @@ export class Cpeak {
273
322
  }
274
323
 
275
324
  route(method: string, path: string, ...args: (RouteMiddleware | Handler)[]) {
276
- if (!this.#routes[method]) this.#routes[method] = [];
277
-
278
325
  // The last argument should always be our handler
279
326
  const cb = args.pop() as Handler;
280
327
 
@@ -285,8 +332,7 @@ export class Cpeak {
285
332
  // Rest will be our middleware functions
286
333
  const middleware = args.flat() as RouteMiddleware[];
287
334
 
288
- const regex = this.#pathToRegex(path);
289
- this.#routes[method].push({ path, regex, middleware, cb });
335
+ this.#router.add(method, path, middleware, cb);
290
336
  }
291
337
 
292
338
  beforeEach(cb: Middleware) {
@@ -297,8 +343,24 @@ export class Cpeak {
297
343
  this.#handleErr = cb;
298
344
  }
299
345
 
300
- listen(port: number, cb?: () => void) {
301
- return this.#server.listen(port, cb);
346
+ // This will handle any request that doesn't match any of the routes and middleware functions
347
+ fallback(cb: Handler) {
348
+ if (this.#fallback) {
349
+ throw frameworkError(
350
+ "Fallback handler is already registered. Only one fallback can be set per app.",
351
+ this.fallback,
352
+ ErrorCode.DUPLICATE_FALLBACK
353
+ );
354
+ }
355
+ this.#fallback = cb;
356
+ }
357
+
358
+ // The first 3 listens are just TS overloads for better type inference and editor autocompletion. The last one is the actual implementation.
359
+ listen(port: number, cb?: () => void): CpeakHttpServer;
360
+ listen(port: number, host: string, cb?: () => void): CpeakHttpServer;
361
+ listen(options: net.ListenOptions, cb?: () => void): CpeakHttpServer;
362
+ listen(...args: any[]) {
363
+ return this.#server.listen(...args);
302
364
  }
303
365
 
304
366
  address() {
@@ -306,29 +368,12 @@ export class Cpeak {
306
368
  }
307
369
 
308
370
  close(cb?: (err?: Error) => void) {
309
- this.#server.close(cb);
310
- }
311
-
312
- // ------------------------------
313
- // PRIVATE METHODS:
314
- // ------------------------------
315
- #pathToRegex(path: string) {
316
- const regexString =
317
- "^" + path.replace(/:\w+/g, "([^/]+)").replace(/\*/g, ".*") + "$";
318
-
319
- return new RegExp(regexString);
371
+ return this.#server.close(cb);
320
372
  }
321
373
 
322
- #extractPathVariables(path: string, match: RegExpMatchArray) {
323
- // Extract path url variable values from the matched route
324
- const paramNames = (path.match(/:\w+/g) || []).map((param) =>
325
- param.slice(1)
326
- );
327
- const params: StringMap = {};
328
- paramNames.forEach((name, index) => {
329
- params[name] = match[index + 1];
330
- });
331
- return params;
374
+ // A getter for developers who want to access the underlying http server instance for advanced use cases that aren't covered by Cpeak
375
+ get server() {
376
+ return this.#server;
332
377
  }
333
378
  }
334
379
 
@@ -341,21 +386,30 @@ export {
341
386
  auth,
342
387
  hashPassword,
343
388
  verifyPassword,
344
- cookieParser
389
+ cookieParser,
390
+ cors
345
391
  } from "./utils";
346
- export type { AuthOptions, PbkdfOptions, CookieOptions } from "./utils";
347
392
 
348
393
  export type {
394
+ AuthOptions,
395
+ PbkdfOptions,
396
+ CookieOptions,
397
+ CorsOptions
398
+ } from "./utils/types";
399
+
400
+ export type { CompressionOptions } from "./internal/types";
401
+
402
+ export type {
403
+ CpeakHttpServer,
404
+ CpeakOptions,
349
405
  CpeakRequest,
350
406
  CpeakResponse,
351
407
  Next,
352
- HandleErr,
353
408
  Middleware,
354
409
  RouteMiddleware,
355
- Handler,
356
- RoutesMap
410
+ Handler
357
411
  } from "./types";
358
412
 
359
- export default function cpeak(): Cpeak {
360
- return new Cpeak();
413
+ export default function cpeak(options?: CpeakOptions): Cpeak {
414
+ return new Cpeak(options);
361
415
  }
@@ -0,0 +1,180 @@
1
+ import zlib from "node:zlib";
2
+ import { Readable } from "node:stream";
3
+ import { Buffer } from "node:buffer";
4
+ import { pipeline } from "node:stream/promises";
5
+ import type { Transform } from "node:stream";
6
+ import type { ServerResponse } from "node:http";
7
+ import type { CompressionOptions, ResolvedCompressionConfig } from "./types";
8
+
9
+ type Encoding = "br" | "gzip" | "deflate";
10
+
11
+ const COMPRESSIBLE_TYPE = /text|json|javascript|css|xml|svg/i;
12
+ const NO_TRANSFORM = /(?:^|,)\s*no-transform\s*(?:,|$)/i;
13
+
14
+ // Parse Accept-Encoding and pick a compression algorithm the server supports.
15
+ // Handles q=0 to disable an algorithm. Cpeak preference is fixed: br > gzip > deflate.
16
+ function pickEncoding(header: string): Encoding | null {
17
+ if (!header) return null;
18
+
19
+ const accepted: Record<string, number> = {};
20
+ let wildcard: number | undefined;
21
+
22
+ for (const part of header.split(",")) {
23
+ const [rawName, ...params] = part.trim().split(";");
24
+ const name = rawName.trim().toLowerCase();
25
+ if (!name) continue;
26
+
27
+ let q = 1;
28
+ for (const p of params) {
29
+ const m = p.trim().match(/^q=([\d.]+)$/i);
30
+ if (m) q = Number(m[1]);
31
+ }
32
+ if (Number.isNaN(q)) q = 0;
33
+
34
+ if (name === "*") wildcard = q;
35
+ else accepted[name] = q;
36
+ }
37
+
38
+ const tryPick = (enc: Encoding): boolean => {
39
+ const q = enc in accepted ? accepted[enc] : wildcard;
40
+ return q !== undefined && q > 0;
41
+ };
42
+
43
+ if (tryPick("br")) return "br";
44
+ if (tryPick("gzip")) return "gzip";
45
+ if (tryPick("deflate")) return "deflate";
46
+ return null;
47
+ }
48
+
49
+ // Handling the Vary HTTP header
50
+ function appendVary(res: ServerResponse, value: string) {
51
+ const existing = res.getHeader("Vary");
52
+ if (!existing) return res.setHeader("Vary", value);
53
+ const current = String(existing)
54
+ .split(",")
55
+ .map((s) => s.trim())
56
+ .filter(Boolean);
57
+ if (
58
+ current.includes("*") ||
59
+ current.some((v) => v.toLowerCase() === value.toLowerCase())
60
+ )
61
+ return;
62
+ res.setHeader("Vary", [...current, value].join(", "));
63
+ }
64
+
65
+ // Brotli options. Zlib uses 11 (max), which is really slow for live
66
+ // responses. We go with 4 unless the developer specifies otherwise.
67
+ function brotliOptsFor(config: ResolvedCompressionConfig): zlib.BrotliOptions {
68
+ const userBrotli = config.brotli || {};
69
+ return {
70
+ ...userBrotli,
71
+ params: {
72
+ [zlib.constants.BROTLI_PARAM_QUALITY]: 4,
73
+ ...(userBrotli.params || {})
74
+ }
75
+ };
76
+ }
77
+
78
+ function createCompressorStream(
79
+ encoding: Encoding,
80
+ config: ResolvedCompressionConfig
81
+ ): Transform {
82
+ if (encoding === "br")
83
+ return zlib.createBrotliCompress(brotliOptsFor(config));
84
+ if (encoding === "gzip") return zlib.createGzip(config.gzip);
85
+ return zlib.createDeflate(config.deflate);
86
+ }
87
+
88
+ // Decides what to do with this response
89
+ function negotiate(
90
+ res: ServerResponse,
91
+ mime: string,
92
+ size: number,
93
+ config: ResolvedCompressionConfig
94
+ ): { encoding: Encoding | null; eligible: boolean } {
95
+ // Whether this content type is worth trying to compress at all.
96
+ // Some types are already compressed and don't compress well.
97
+ if (!COMPRESSIBLE_TYPE.test(mime)) return { encoding: null, eligible: false };
98
+
99
+ if (res.req?.method === "HEAD") return { encoding: null, eligible: false };
100
+
101
+ // RFC specification: don't transform responses that ask not to be transformed.
102
+ const cc = res.getHeader("Cache-Control");
103
+ if (cc && NO_TRANSFORM.test(String(cc)))
104
+ return { encoding: null, eligible: false };
105
+
106
+ const existing = res.getHeader("Content-Encoding");
107
+ if (existing && existing !== "identity")
108
+ return { encoding: null, eligible: false };
109
+
110
+ if (size < config.threshold) return { encoding: null, eligible: true };
111
+
112
+ const encoding = pickEncoding(
113
+ String(res.req?.headers["accept-encoding"] || "")
114
+ );
115
+ return { encoding, eligible: true };
116
+ }
117
+
118
+ // Converts into a Readable stream
119
+ function bodyAsReadable(body: Buffer | string | Readable): Readable {
120
+ if (Buffer.isBuffer(body)) return Readable.from([body]);
121
+ if (typeof body === "string") return Readable.from([Buffer.from(body)]);
122
+ return body;
123
+ }
124
+
125
+ // Resolves compression options (or 'true' for defaults) into a
126
+ // complete config. Called once at Cpeak construction.
127
+ export function resolveCompressionOptions(
128
+ input: true | CompressionOptions
129
+ ): ResolvedCompressionConfig {
130
+ const options: CompressionOptions = input === true ? {} : input;
131
+ return {
132
+ threshold: options.threshold ?? 1024,
133
+ brotli: options.brotli ?? {},
134
+ gzip: options.gzip ?? {},
135
+ deflate: options.deflate ?? {}
136
+ };
137
+ }
138
+
139
+ // The final point used by res.compress, res.json, res.sendFile and res.render
140
+ // when compression is enabled by the developer.
141
+ //
142
+ // Compression always goes through createGzip/createBrotliCompress/createDeflate
143
+ // streams which are async and run on libuv's thread pool.
144
+ export async function compressAndSend(
145
+ res: ServerResponse,
146
+ mime: string,
147
+ body: Buffer | string | Readable,
148
+ config: ResolvedCompressionConfig,
149
+ size?: number
150
+ ): Promise<void> {
151
+ res.setHeader("Content-Type", mime);
152
+
153
+ const knownSize: number = Buffer.isBuffer(body)
154
+ ? body.length
155
+ : typeof body === "string"
156
+ ? Buffer.byteLength(body)
157
+ : (size ?? Infinity);
158
+
159
+ const { encoding, eligible } = negotiate(res, mime, knownSize, config);
160
+
161
+ if (!encoding) {
162
+ if (eligible) appendVary(res, "Accept-Encoding");
163
+ if (Buffer.isBuffer(body) || typeof body === "string") {
164
+ res.setHeader("Content-Length", String(knownSize));
165
+ res.end(body);
166
+ return;
167
+ }
168
+ if (size !== undefined) res.setHeader("Content-Length", String(size));
169
+ await pipeline(body, res);
170
+ return;
171
+ }
172
+
173
+ res.setHeader("Content-Encoding", encoding);
174
+ appendVary(res, "Accept-Encoding");
175
+ await pipeline(
176
+ bodyAsReadable(body),
177
+ createCompressorStream(encoding, config),
178
+ res
179
+ );
180
+ }
@@ -0,0 +1,35 @@
1
+ // A utility function to create an error with a custom stack trace
2
+ export function frameworkError(
3
+ message: string,
4
+ skipFn: Function,
5
+ code?: string,
6
+ status?: number
7
+ ) {
8
+ const err = new Error(message) as Error & {
9
+ code?: string;
10
+ cpeak_err?: boolean;
11
+ };
12
+ Error.captureStackTrace(err, skipFn);
13
+
14
+ err.cpeak_err = true;
15
+
16
+ if (code) err.code = code;
17
+ if (status) (err as any).status = status;
18
+
19
+ return err;
20
+ }
21
+
22
+ export enum ErrorCode {
23
+ MISSING_MIME = "CPEAK_ERR_MISSING_MIME",
24
+ FILE_NOT_FOUND = "CPEAK_ERR_FILE_NOT_FOUND",
25
+ NOT_A_FILE = "CPEAK_ERR_NOT_A_FILE",
26
+ SEND_FILE_FAIL = "CPEAK_ERR_SEND_FILE_FAIL",
27
+ INVALID_JSON = "CPEAK_ERR_INVALID_JSON",
28
+ PAYLOAD_TOO_LARGE = "CPEAK_ERR_PAYLOAD_TOO_LARGE",
29
+ WEAK_SECRET = "CPEAK_ERR_WEAK_SECRET",
30
+ COMPRESSION_NOT_ENABLED = "CPEAK_ERR_COMPRESSION_NOT_ENABLED",
31
+ // For router:
32
+ DUPLICATE_ROUTE = "CPEAK_ERR_DUPLICATE_ROUTE",
33
+ INVALID_ROUTE = "CPEAK_ERR_INVALID_ROUTE",
34
+ DUPLICATE_FALLBACK = "CPEAK_ERR_DUPLICATE_FALLBACK"
35
+ }
@@ -0,0 +1,22 @@
1
+ import type { StringMap } from "../types";
2
+
3
+ // Developers can expand this if needed in the cpeak() constructor
4
+ export const MIME_TYPES: StringMap = {
5
+ html: "text/html",
6
+ css: "text/css",
7
+ js: "application/javascript",
8
+ jpg: "image/jpeg",
9
+ jpeg: "image/jpeg",
10
+ png: "image/png",
11
+ svg: "image/svg+xml",
12
+ txt: "text/plain",
13
+ eot: "application/vnd.ms-fontobject",
14
+ otf: "font/otf",
15
+ ttf: "font/ttf",
16
+ woff: "font/woff",
17
+ woff2: "font/woff2",
18
+ gif: "image/gif",
19
+ ico: "image/x-icon",
20
+ json: "application/json",
21
+ webmanifest: "application/manifest+json"
22
+ };