UNPKG

37 kBJavaScriptView Raw
1// src/logger.ts
2import { trace } from "@opentelemetry/api";
3import { SeverityNumber, logs } from "@opentelemetry/api-logs";
4
5// package.json
6var name = "@neoaren/comet";
7var version = "3.4.1";
8
9// src/logger.ts
10var otelLogger = logs.getLogger(name, version);
11function convert(...data) {
12 return data.map((entry) => {
13 if (typeof entry === "string") return entry;
14 if (entry === void 0) return "undefined";
15 return JSON.stringify(entry, null, 2);
16 }).join(", ");
17}
18var logger = {
19 trace: (...body) => otelLogger.emit({ severityNumber: SeverityNumber.TRACE, body: convert(body) }),
20 debug: (...body) => otelLogger.emit({ severityNumber: SeverityNumber.DEBUG, body: convert(body) }),
21 info: (...body) => otelLogger.emit({ severityNumber: SeverityNumber.INFO, body: convert(body) }),
22 log: (...body) => otelLogger.emit({ severityNumber: SeverityNumber.INFO, body: convert(body) }),
23 warn: (...body) => otelLogger.emit({ severityNumber: SeverityNumber.WARN, body: convert(body) }),
24 error: (...body) => otelLogger.emit({ severityNumber: SeverityNumber.ERROR, body: convert(body) })
25};
26function recordException(exception) {
27 if (exception instanceof Error || typeof exception === "string") {
28 trace.getActiveSpan()?.recordException(exception);
29 } else if (typeof exception === "object" && exception !== null && "toString" in exception) {
30 trace.getActiveSpan()?.recordException(exception.toString());
31 }
32}
33
34// src/cookies.ts
35var defaultCookiesOptions = {
36 decode: decodeURIComponent,
37 encode: encodeURIComponent,
38 limit: 64
39};
40var SameSite = /* @__PURE__ */ ((SameSite2) => {
41 SameSite2["Strict"] = "Strict";
42 SameSite2["Lax"] = "Lax";
43 SameSite2["None"] = "None";
44 return SameSite2;
45})(SameSite || {});
46var Cookies = class _Cookies {
47 data = /* @__PURE__ */ new Map();
48 get [Symbol.toStringTag]() {
49 return `Cookies(${this.data.size})`;
50 }
51 // Get a cookie by name
52 get(name2) {
53 return this.data.get(name2)?.value;
54 }
55 // Check if a cookie exists
56 has(name2) {
57 return this.data.has(name2);
58 }
59 // Set a cookie with a name, a value and optional metadata
60 set(name2, value, meta) {
61 this.data.set(name2, { name: name2, value, ...meta ? { meta } : {} });
62 }
63 // Delete a cookie
64 delete(name2) {
65 this.data.delete(name2);
66 }
67 // Returns an array that contains the key-value pairs for each cookie
68 entries() {
69 return [...this.data.entries()].map(([name2, cookie]) => [name2, cookie.value]);
70 }
71 // Returns an array that contains the keys for each cookie
72 keys() {
73 return [...this.data.keys()];
74 }
75 // Returns an array that contains the values for each cookie
76 values() {
77 return [...this.data.values()].map((cookie) => cookie.value);
78 }
79 // Parse cookies from headers
80 static async parse(headers, options) {
81 const allOptions = this.getAllOptions(options);
82 const cookies = new _Cookies();
83 const pairs = headers.get("Cookie")?.split(";", allOptions.limit) ?? [];
84 for (const pair of pairs) {
85 let [name2, value] = pair.split("=", 2).map((component) => component.trim());
86 if (!name2 || !value) {
87 recordException(`[Comet] Failed to parse malformatted cookie "${pair}".`);
88 continue;
89 }
90 if (value.startsWith('"') && value.endsWith('"')) value = value.slice(1, -1);
91 try {
92 if (allOptions.decode !== null) name2 = await allOptions.decode(name2);
93 if (allOptions.decode !== null) value = await allOptions.decode(value);
94 cookies.set(name2, value);
95 } catch (error) {
96 recordException(`[Comet] Failed to decode cookie "${pair}".`);
97 recordException(error);
98 }
99 }
100 return cookies;
101 }
102 // Serialize cookies to headers
103 static async serialize(cookies, headers, options) {
104 const allOptions = this.getAllOptions(options);
105 for (const cookie of cookies.data.values()) {
106 const serialized = [];
107 try {
108 const name2 = allOptions.encode === null ? cookie.name : await allOptions.encode(cookie.name);
109 const value = allOptions.encode === null ? cookie.value : await allOptions.encode(cookie.value);
110 serialized.push(`${name2}=${value}`);
111 } catch (error) {
112 recordException(`[Comet] Failed to encode cookie "${cookie.name}".`);
113 recordException(error);
114 continue;
115 }
116 if (cookie.meta?.domain) serialized.push(`Domain=${cookie.meta.domain}`);
117 if (cookie.meta?.expires) serialized.push(`Expires=${new Date(cookie.meta.expires).toUTCString()}`);
118 if (cookie.meta?.httpOnly) serialized.push("HttpOnly");
119 if (cookie.meta?.maxAge) serialized.push(`Max-Age=${cookie.meta.maxAge}`);
120 if (cookie.meta?.path) serialized.push(`Path=${cookie.meta.path}`);
121 if (cookie.meta?.sameSite) serialized.push(`SameSite=${cookie.meta.sameSite}`);
122 if (cookie.meta?.secure) serialized.push("Secure");
123 headers.append("Set-Cookie", serialized.join("; "));
124 }
125 }
126 // Get all options with fallback to default options
127 static getAllOptions(options) {
128 return { ...defaultCookiesOptions, ...options };
129 }
130};
131
132// src/middleware.ts
133var NextData = class {
134 // @ts-expect-error data could use better typing
135 constructor(data = {}) {
136 this.data = data;
137 }
138};
139var next = (extension) => new NextData(extension);
140function middleware(options, handler) {
141 const _options = typeof options === "object" ? options : {};
142 const _handler = typeof options === "function" ? options : handler;
143 if (!_handler) throw new Error("[Comet] A middleware received no handler argument.");
144 return {
145 ..._options,
146 handler: async (input) => {
147 const nextData = await _handler(input);
148 if (nextData instanceof NextData) Object.assign(input.event, nextData.data);
149 }
150 };
151}
152
153// src/types.ts
154var Method = /* @__PURE__ */ ((Method2) => {
155 Method2["ALL"] = "ALL";
156 Method2["GET"] = "GET";
157 Method2["HEAD"] = "HEAD";
158 Method2["POST"] = "POST";
159 Method2["PUT"] = "PUT";
160 Method2["DELETE"] = "DELETE";
161 Method2["CONNECT"] = "CONNECT";
162 Method2["OPTIONS"] = "OPTIONS";
163 Method2["TRACE"] = "TRACE";
164 Method2["PATCH"] = "PATCH";
165 return Method2;
166})(Method || {});
167var ALL = "ALL" /* ALL */;
168var GET = "GET" /* GET */;
169var HEAD = "HEAD" /* HEAD */;
170var POST = "POST" /* POST */;
171var PUT = "PUT" /* PUT */;
172var DELETE = "DELETE" /* DELETE */;
173var CONNECT = "CONNECT" /* CONNECT */;
174var OPTIONS = "OPTIONS" /* OPTIONS */;
175var TRACE = "TRACE" /* TRACE */;
176var PATCH = "PATCH" /* PATCH */;
177
178// src/cors.ts
179var defaultCorsOptions = {
180 credentials: false,
181 exposedHeaders: [],
182 headers: [],
183 maxAge: 86400,
184 methods: [],
185 origins: []
186};
187function getAllOptions(options) {
188 const allOptions = { ...defaultCorsOptions, ...options };
189 const allowedOrigins = parseListValue(allOptions.origins);
190 const allowedHeaders = parseListValue(allOptions.headers);
191 const allowedMethods = parseListValue(allOptions.methods);
192 const exposedHeaders = parseListValue(allOptions.exposedHeaders);
193 const { credentials: allowCredentials, maxAge } = allOptions;
194 return { allowedOrigins, allowedHeaders, allowedMethods, exposedHeaders, allowCredentials, maxAge };
195}
196function parseListValue(value) {
197 return Array.isArray(value) ? value : value.split(",").map((s) => s.trim());
198}
199var cors = (options) => middleware({
200 name: "CORS"
201}, ({ event }) => {
202 const { allowedOrigins, exposedHeaders, allowCredentials } = getAllOptions(options);
203 const origin = event.headers.get("origin");
204 if (allowedOrigins.includes("*")) {
205 event.reply.headers.set("access-control-allow-origin", "*");
206 } else if (origin && (allowedOrigins.includes(origin) || allowedOrigins.some((allowed) => allowed.startsWith("https://*.") && origin.endsWith(allowed.slice(9))))) {
207 event.reply.headers.set("access-control-allow-origin", origin);
208 event.reply.headers.append("vary", "origin");
209 }
210 if (allowCredentials) event.reply.headers.set("access-control-allow-credentials", "true");
211 if (exposedHeaders.length > 0) event.reply.headers.set("access-control-expose-headers", exposedHeaders.join(","));
212 return event.next();
213});
214var preflightHandler = (router, options) => middleware({
215 name: "Preflight handler"
216}, ({ event }) => {
217 if (event.method !== "OPTIONS" /* OPTIONS */) return event.next();
218 const { allowedHeaders, allowedMethods, maxAge } = getAllOptions(options);
219 const requestMethod = event.headers.get("access-control-request-method") ?? void 0;
220 const route = router.find(event.pathname, requestMethod, void 0, true);
221 if (!route) return event.reply.notFound();
222 if (allowedHeaders.length > 0) event.reply.headers.set("access-control-allow-headers", allowedHeaders.join(","));
223 if (allowedMethods.length > 0) event.reply.headers.set("access-control-allow-methods", allowedMethods.join(","));
224 event.reply.headers.set("access-control-max-age", maxAge.toString());
225 event.reply.headers.set("content-length", "0");
226 return event.reply.noContent();
227});
228
229// src/data.ts
230import { trace as trace2 } from "@opentelemetry/api";
231var Data = class _Data {
232 constructor(request, method, pathname, hostname, headers, cookies, query, params, body, _raw, server2) {
233 this.request = request;
234 this.method = method;
235 this.pathname = pathname;
236 this.hostname = hostname;
237 this.headers = headers;
238 this.cookies = cookies;
239 this.query = query;
240 this.params = params;
241 this.body = body;
242 this._raw = _raw;
243 this.server = server2;
244 }
245 static async fromRequest(request, options, serverName) {
246 const url = new URL(request.url);
247 const { raw, body } = await this.parseRequestBody(request);
248 trace2.getActiveSpan()?.addEvent("convert request to data", {
249 method: request.method,
250 pathname: url.pathname,
251 hostname: url.hostname,
252 protocol: url.protocol,
253 params: url.search
254 });
255 return new _Data(
256 request,
257 request.method.toUpperCase(),
258 url.pathname,
259 url.hostname.toLowerCase(),
260 request.headers,
261 await Cookies.parse(request.headers, options.cookies),
262 Object.fromEntries(url.searchParams.entries()),
263 {},
264 body,
265 raw,
266 { name: serverName }
267 );
268 }
269 static async parseRequestBody(request) {
270 const contentType = request.headers.get("content-type")?.split(";")[0];
271 trace2.getActiveSpan()?.addEvent("parse request body", {
272 contentType: request.headers.get("content-type") ?? void 0,
273 parsedContentType: contentType
274 });
275 switch (contentType) {
276 case "application/json": {
277 const text = await request.text();
278 return { raw: text, body: JSON.parse(text) };
279 }
280 case "multipart/form-data": {
281 const formData = await request.formData();
282 const body = {};
283 for (const [key, value] of formData.entries()) body[key] = value;
284 return { body };
285 }
286 case "application/x-www-form-urlencoded": {
287 const text = await request.text();
288 const entries = text.split("&").map((x) => x.split("=").map(decodeURIComponent));
289 return { body: Object.fromEntries(entries) };
290 }
291 default:
292 return { body: void 0 };
293 }
294 }
295};
296
297// src/reply.ts
298import { trace as trace3 } from "@opentelemetry/api";
299var Status = /* @__PURE__ */ ((Status2) => {
300 Status2["Continue"] = "continue";
301 Status2["SwitchingProtocols"] = "switchingProtocols";
302 Status2["Processing"] = "processing";
303 Status2["Ok"] = "ok";
304 Status2["Created"] = "created";
305 Status2["Accepted"] = "accepted";
306 Status2["NonAuthoritativeInformation"] = "nonAuthoritativeInformation";
307 Status2["NoContent"] = "noContent";
308 Status2["ResetContent"] = "resetContent";
309 Status2["PartialContent"] = "partialContent";
310 Status2["MultiStatus"] = "multiStatus";
311 Status2["MultipleChoices"] = "multipleChoices";
312 Status2["MovedPermanently"] = "movedPermanently";
313 Status2["MovedTemporarily"] = "movedTemporarily";
314 Status2["SeeOther"] = "seeOther";
315 Status2["NotModified"] = "notModified";
316 Status2["UseProxy"] = "useProxy";
317 Status2["TemporaryRedirect"] = "temporaryRedirect";
318 Status2["PermanentRedirect"] = "permanentRedirect";
319 Status2["BadRequest"] = "badRequest";
320 Status2["Unauthorized"] = "unauthorized";
321 Status2["PaymentRequired"] = "paymentRequired";
322 Status2["Forbidden"] = "forbidden";
323 Status2["NotFound"] = "notFound";
324 Status2["MethodNotAllowed"] = "methodNotAllowed";
325 Status2["NotAcceptable"] = "notAcceptable";
326 Status2["ProxyAuthenticationRequired"] = "proxyAuthenticationRequired";
327 Status2["RequestTimeout"] = "requestTimeout";
328 Status2["Conflict"] = "conflict";
329 Status2["Gone"] = "gone";
330 Status2["LengthRequired"] = "lengthRequired";
331 Status2["PreconditionFailed"] = "preconditionFailed";
332 Status2["RequestTooLong"] = "requestTooLong";
333 Status2["RequestUriTooLong"] = "requestUriTooLong";
334 Status2["UnsupportedMediaType"] = "unsupportedMediaType";
335 Status2["RequestedRangeNotSatisfiable"] = "requestedRangeNotSatisfiable";
336 Status2["ExpectationFailed"] = "expectationFailed";
337 Status2["ImATeapot"] = "imATeapot";
338 Status2["InsufficientSpaceOnResource"] = "insufficientSpaceOnResource";
339 Status2["MethodFailure"] = "methodFailure";
340 Status2["MisdirectedRequest"] = "misdirectedRequest";
341 Status2["UnprocessableEntity"] = "unprocessableEntity";
342 Status2["FailedDependency"] = "failedDependency";
343 Status2["PreconditionRequired"] = "preconditionRequired";
344 Status2["TooManyRequests"] = "tooManyRequests";
345 Status2["RequestHeaderFieldsTooLarge"] = "requestHeaderFieldsTooLarge";
346 Status2["UnavailableForLegalReasons"] = "unavailableForLegalReasons";
347 Status2["InternalServerError"] = "internalServerError";
348 Status2["NotImplemented"] = "notImplemented";
349 Status2["BadGateway"] = "badGateway";
350 Status2["ServiceUnavailable"] = "serviceUnavailable";
351 Status2["GatewayTimeout"] = "gatewayTimeout";
352 Status2["HttpVersionNotSupported"] = "httpVersionNotSupported";
353 Status2["InsufficientStorage"] = "insufficientStorage";
354 Status2["NetworkAuthenticationRequired"] = "networkAuthenticationRequired";
355 return Status2;
356})(Status || {});
357var Reply = class {
358 // Reply data for regular replies
359 body;
360 status = Number.NaN;
361 headers = new Headers();
362 cookies = new Cookies();
363 // Reply data for raw replies
364 _raw;
365 // The date the reply was sent
366 sent;
367 // Convert a reply to a standard response
368 static async toResponse(reply, options) {
369 if (!reply.sent) {
370 recordException("[Comet] No reply was sent for this event.");
371 return new Response(null, { status: 500 });
372 }
373 if (reply._raw !== void 0) {
374 trace3.getActiveSpan()?.addEvent("return raw response");
375 return reply._raw;
376 }
377 const status = reply.status;
378 const headers = reply.headers;
379 await Cookies.serialize(reply.cookies, reply.headers, options.cookies);
380 if (reply.body instanceof WebSocket) {
381 trace3.getActiveSpan()?.addEvent("return websocket response");
382 return new Response(null, { status, headers, webSocket: reply.body });
383 }
384 if (reply.body instanceof ReadableStream) {
385 trace3.getActiveSpan()?.addEvent("return streamed response");
386 return new Response(reply.body, { status, headers });
387 }
388 let body = null;
389 if (reply.body) {
390 headers.set("content-type", "application/json");
391 body = options.dev ? JSON.stringify(reply.body, null, 2) : JSON.stringify(reply.body);
392 }
393 trace3.getActiveSpan()?.addEvent("convert response");
394 return new Response(body, { status, headers });
395 }
396 // Send a regular reply
397 send(status, body) {
398 if (this.sent) {
399 recordException("[Comet] Cannot send a reply after one has already been sent.");
400 return this;
401 }
402 this.status = status;
403 this.body = body;
404 this.sent = /* @__PURE__ */ new Date();
405 trace3.getActiveSpan()?.addEvent("send reply", {
406 status,
407 sent: +this.sent
408 });
409 return this;
410 }
411 // Send a raw reply
412 raw(response) {
413 if (this.sent) {
414 recordException("[Comet] Cannot send a reply after one has already been sent.");
415 return this;
416 }
417 this._raw = response;
418 this.sent = /* @__PURE__ */ new Date();
419 trace3.getActiveSpan()?.addEvent("raw reply", {
420 sent: +this.sent
421 });
422 return this;
423 }
424 // Send a custom HTTP response
425 custom(status, body) {
426 return this.send(status, body);
427 }
428 // Send an HTTP `100 Continue` informational response
429 continue(body) {
430 return this.send(100, body);
431 }
432 // Send an HTTP `101 Switching Protocols` informational response
433 switchingProtocols(body) {
434 return this.send(101, body);
435 }
436 // Send an HTTP `102 Processing` informational response
437 processing(body) {
438 return this.send(102, body);
439 }
440 // Send an HTTP `200 OK` successful response
441 ok(body) {
442 return this.send(200, body);
443 }
444 // Send an HTTP `201 Created` successful response
445 created(body) {
446 return this.send(201, body);
447 }
448 // Send an HTTP `202 Accepted` successful response
449 accepted(body) {
450 return this.send(202, body);
451 }
452 // Send an HTTP `203 Non-Authoritative Information` successful response
453 nonAuthoritativeInformation(body) {
454 return this.send(203, body);
455 }
456 // Send an HTTP `204 No Content` successful response
457 noContent(body) {
458 return this.send(204, body);
459 }
460 // Send an HTTP `205 Reset Content` successful response
461 resetContent(body) {
462 return this.send(205, body);
463 }
464 // Send an HTTP `206 Partial Content` successful response
465 partialContent(body) {
466 return this.send(206, body);
467 }
468 // Send an HTTP `207 Multi-Status` successful response
469 multiStatus(body) {
470 return this.send(207, body);
471 }
472 // Send an HTTP `300 Multiple Choices` redirection response
473 multipleChoices(body) {
474 return this.send(300, body);
475 }
476 // Send an HTTP `301 Moved Permanently` redirection response
477 movedPermanently(body) {
478 return this.send(301, body);
479 }
480 // Send an HTTP `302 Moved Temporarily` redirection response
481 movedTemporarily(body) {
482 return this.send(302, body);
483 }
484 // Send an HTTP `303 See Other` redirection response
485 seeOther(body) {
486 return this.send(303, body);
487 }
488 // Send an HTTP `304 Not Modified` redirection response
489 notModified(body) {
490 return this.send(304, body);
491 }
492 // Send an HTTP `305 Use Proxy` redirection response
493 useProxy(body) {
494 return this.send(305, body);
495 }
496 // Send an HTTP `307 Temporary Redirect` redirection response
497 temporaryRedirect(body) {
498 return this.send(307, body);
499 }
500 // Send an HTTP `308 Permanent Redirect` redirection response
501 permanentRedirect(body) {
502 return this.send(308, body);
503 }
504 // Send an HTTP `400 Bad Request` client error response
505 badRequest(body) {
506 return this.send(400, body);
507 }
508 // Send an HTTP `401 Unauthorized` client error response
509 unauthorized(body) {
510 return this.send(401, body);
511 }
512 // Send an HTTP `402 Payment Required` client error response
513 paymentRequired(body) {
514 return this.send(402, body);
515 }
516 // Send an HTTP `403 Forbidden` client error response
517 forbidden(body) {
518 return this.send(403, body);
519 }
520 // Send an HTTP `404 Not Found` client error response
521 notFound(body) {
522 return this.send(404, body);
523 }
524 // Send an HTTP `405 Method Not Allowed` client error response
525 methodNotAllowed(body) {
526 return this.send(405, body);
527 }
528 // Send an HTTP `406 Not Acceptable` client error response
529 notAcceptable(body) {
530 return this.send(406, body);
531 }
532 // Send an HTTP `407 Proxy Authentication Required` client error response
533 proxyAuthenticationRequired(body) {
534 return this.send(407, body);
535 }
536 // Send an HTTP `408 Request Timeout` client error response
537 requestTimeout(body) {
538 return this.send(408, body);
539 }
540 // Send an HTTP `409 Conflict` client error response
541 conflict(body) {
542 return this.send(409, body);
543 }
544 // Send an HTTP `410 Gone` client error response
545 gone(body) {
546 return this.send(410, body);
547 }
548 // Send an HTTP `411 Length Required` client error response
549 lengthRequired(body) {
550 return this.send(411, body);
551 }
552 // Send an HTTP `412 Precondition Failed` client error response
553 preconditionFailed(body) {
554 return this.send(412, body);
555 }
556 // Send an HTTP `413 Request Too Long` client error response
557 requestTooLong(body) {
558 return this.send(413, body);
559 }
560 // Send an HTTP `414 Request URI Too Long` client error response
561 requestUriTooLong(body) {
562 return this.send(414, body);
563 }
564 // Send an HTTP `415 Unsupported Media Type` client error response
565 unsupportedMediaType(body) {
566 return this.send(415, body);
567 }
568 // Send an HTTP `416 Requested Range Not Satisfiable` client error response
569 requestedRangeNotSatisfiable(body) {
570 return this.send(416, body);
571 }
572 // Send an HTTP `417 Expectation Failed` client error response
573 expectationFailed(body) {
574 return this.send(417, body);
575 }
576 // Send an HTTP `418 I'm a teapot` client error response
577 imATeapot(body) {
578 return this.send(418, body);
579 }
580 // Send an HTTP `419 Insufficient Space On Resource` client error response
581 insufficientSpaceOnResource(body) {
582 return this.send(419, body);
583 }
584 // Send an HTTP `420 Method Failure` client error response
585 methodFailure(body) {
586 return this.send(420, body);
587 }
588 // Send an HTTP `421 Misdirected Request` client error response
589 misdirectedRequest(body) {
590 return this.send(421, body);
591 }
592 // Send an HTTP `422 Unprocessable Entity` client error response
593 unprocessableEntity(body) {
594 return this.send(422, body);
595 }
596 // Send an HTTP `424 Failed Dependency` client error response
597 failedDependency(body) {
598 return this.send(424, body);
599 }
600 // Send an HTTP `428 Precondition Required` client error response
601 preconditionRequired(body) {
602 return this.send(428, body);
603 }
604 // Send an HTTP `429 Too Many Requests` client error response
605 tooManyRequests(body) {
606 return this.send(429, body);
607 }
608 // Send an HTTP `431 Request Header Fields Too Large` client error response
609 requestHeaderFieldsTooLarge(body) {
610 return this.send(431, body);
611 }
612 // Send an HTTP `451 Unavailable For Legal Reasons` client error response
613 unavailableForLegalReasons(body) {
614 return this.send(451, body);
615 }
616 // Send an HTTP `500 Internal Server Error` server error response
617 internalServerError(body) {
618 return this.send(500, body);
619 }
620 // Send an HTTP `501 Not Implemented` server error response
621 notImplemented(body) {
622 return this.send(501, body);
623 }
624 // Send an HTTP `502 Bad Gateway` server error response
625 badGateway(body) {
626 return this.send(502, body);
627 }
628 // Send an HTTP `503 Service Unavailable` server error response
629 serviceUnavailable(body) {
630 return this.send(503, body);
631 }
632 // Send an HTTP `504 Gateway Timeout` server error response
633 gatewayTimeout(body) {
634 return this.send(504, body);
635 }
636 // Send an HTTP `505 Http Version Not Supported` server error response
637 httpVersionNotSupported(body) {
638 return this.send(505, body);
639 }
640 // Send an HTTP `507 Insufficient Storage` server error response
641 insufficientStorage(body) {
642 return this.send(507, body);
643 }
644 // Send an HTTP `511 Network Authentication Required` server error response
645 networkAuthenticationRequired(body) {
646 return this.send(511, body);
647 }
648};
649
650// src/utils.ts
651var BASE_URL = "https://comet";
652function isValidPathname(value) {
653 if (typeof value !== "string") return false;
654 try {
655 new URLPattern(value, BASE_URL);
656 return true;
657 } catch {
658 return false;
659 }
660}
661function isValidCompatibilityDate(value) {
662 return typeof value === "string" && !Number.isNaN(new Date(value).valueOf());
663}
664function comparePathnames(check, against) {
665 if (!against || against === "*") return true;
666 return new URLPattern(against, BASE_URL).test(check, BASE_URL);
667}
668function compareMethods(check, against) {
669 if (!against || against === "ALL" /* ALL */) return true;
670 return check === against;
671}
672function compareCompatibilityDates(check, against) {
673 if (!against) return true;
674 if (!check) return false;
675 return new Date(check) >= new Date(against);
676}
677function getPathnameParameters(pathname, template) {
678 const result = new URLPattern(template, BASE_URL).exec(pathname, BASE_URL);
679 return result?.pathname?.groups ?? {};
680}
681
682// src/router.ts
683var Router = class {
684 // Take router options
685 constructor(options) {
686 this.options = options;
687 }
688 // Registry of routes
689 routes = [];
690 ready = true;
691 // Register a new route
692 register = (options, handler) => {
693 const _register = (inputMethod) => {
694 const pathname = `${this.options.prefix ?? ""}${options.pathname ?? "*"}`;
695 const method2 = inputMethod ?? "ALL" /* ALL */;
696 const compatibilityDate = options.compatibilityDate;
697 const name2 = options.name ?? `${method2} ${pathname}${compatibilityDate ? ` (${compatibilityDate})` : ""}`;
698 if (!isValidPathname(pathname)) {
699 recordException(`[Comet] Failed to set up route '${name2}' due to an invalid pathname.`);
700 return;
701 }
702 if (options.compatibilityDate !== void 0 && !isValidCompatibilityDate(options.compatibilityDate)) {
703 recordException(`[Comet] Failed to set up route '${name2}' due to an invalid compatibility date.`);
704 return;
705 }
706 const schemas = { body: options.body, params: options.params, query: options.query };
707 this.routes.push({ ...options, pathname, method: method2, name: name2, handler, schemas });
708 };
709 const { method } = options;
710 if (Array.isArray(method)) {
711 for (const each of method) _register(each);
712 } else {
713 _register(method);
714 }
715 this.ready = false;
716 };
717 // Find a route on a server by pathname, method and compatibility date
718 find = (pathname, method, compatibilityDate, ignoreCompatibilityDate) => {
719 for (const route of this.routes) {
720 const doPathnamesMatch = comparePathnames(pathname, route.pathname);
721 if (!doPathnamesMatch) continue;
722 const doMethodsMatch = compareMethods(method, route.method);
723 if (!doMethodsMatch) continue;
724 if (ignoreCompatibilityDate) return route;
725 const doCompatibilityDatesMatch = compareCompatibilityDates(compatibilityDate, route.compatibilityDate);
726 if (doCompatibilityDatesMatch) return route;
727 }
728 };
729 // Initialize router by sorting the routes by compatibility date in descending order to ensure the correct functioning of the find algorithm
730 init = () => {
731 if (this.ready) return;
732 this.routes.sort((a, b) => {
733 if (a.pathname !== b.pathname || a.method !== b.method) return 0;
734 return compareCompatibilityDates(a.compatibilityDate, b.compatibilityDate) ? -1 : 1;
735 });
736 this.ready = true;
737 };
738 getRoutes = () => this.routes;
739};
740
741// src/schemaValidation.ts
742import { trace as trace4 } from "@opentelemetry/api";
743var schemaValidation = (route) => middleware({
744 name: "Schema validation"
745}, async ({ event }) => {
746 const { body: bodySchema, params: paramsSchema, query: querySchema } = route.schemas;
747 const paramsResult = paramsSchema?.safeParse(event.params);
748 trace4.getActiveSpan()?.addEvent("params schema parse", {
749 success: paramsResult?.success,
750 errors: paramsResult?.success ? void 0 : paramsResult?.error.issues.map((issue) => issue.message)
751 });
752 const queryResult = querySchema?.safeParse(event.query);
753 trace4.getActiveSpan()?.addEvent("query schema parse", {
754 success: queryResult?.success,
755 errors: paramsResult?.success ? void 0 : paramsResult?.error.issues.map((issue) => issue.message)
756 });
757 const bodyResult = bodySchema?.safeParse(event.body);
758 trace4.getActiveSpan()?.addEvent("body schema parse", {
759 success: bodyResult?.success,
760 errors: paramsResult?.success ? void 0 : paramsResult?.error.issues.map((issue) => issue.message)
761 });
762 const errors = {};
763 if (paramsResult?.success === false) errors.params = paramsResult.error.issues;
764 if (queryResult?.success === false) errors.query = queryResult.error.issues;
765 if (bodyResult?.success === false) errors.body = bodyResult.error.issues;
766 if (errors.body || errors.params || errors.query) {
767 return event.reply.badRequest({ success: false, errors });
768 }
769 if (paramsResult?.success) event.params = paramsResult.data;
770 if (queryResult?.success) event.query = queryResult.data;
771 if (bodyResult?.success) event.body = bodyResult.data;
772 return event.next();
773});
774
775// src/server.ts
776import { SpanKind, trace as trace5 } from "@opentelemetry/api";
777var Server = class {
778 constructor(options = {}) {
779 this.options = options;
780 this.router = new Router(options);
781 this.route = this.router.register;
782 }
783 router;
784 route;
785 handler = async (request, env, ctxOrState) => {
786 return trace5.getTracer(name, version).startActiveSpan("Comet Handler", {
787 kind: SpanKind.SERVER,
788 attributes: {
789 name: this.options.name
790 }
791 }, async (span) => {
792 try {
793 this.router.init();
794 const data = await Data.fromRequest(request, this.options, this.options.name);
795 const reply = new Reply();
796 const isDurableObject = "id" in ctxOrState;
797 const event = {
798 ...data,
799 reply,
800 next,
801 isDurableObject,
802 ...isDurableObject ? { state: ctxOrState } : { ctx: ctxOrState }
803 };
804 const input = { event, env, logger };
805 span.setAttribute("comet.server.durable_object", isDurableObject);
806 if (this.options.before) {
807 for (const mw of this.options.before) {
808 await trace5.getTracer(name, version).startActiveSpan(
809 `Comet Middleware${mw.name ? ` ${mw.name}` : ""}`,
810 {
811 attributes: {
812 "comet.mw.name": mw.name,
813 "comet.mw.type": "global-before"
814 }
815 },
816 async (span2) => {
817 await mw.handler(input);
818 span2.end();
819 }
820 );
821 if (event.reply.sent) break;
822 }
823 }
824 if (!event.reply.sent) {
825 await trace5.getTracer(name, version).startActiveSpan(
826 "Comet CORS Middleware",
827 {
828 attributes: {
829 "comet.mw.name": "CORS",
830 "comet.mw.type": "global-before",
831 "comet.mw.cors.origin": event.headers.get("origin") ?? void 0,
832 "comet.mw.cors.method": event.method
833 }
834 },
835 async (span2) => {
836 await cors(this.options.cors).handler(input);
837 span2.end();
838 }
839 );
840 }
841 if (!event.reply.sent) {
842 await trace5.getTracer(name, version).startActiveSpan(
843 "Comet Routing",
844 {
845 attributes: {
846 "comet.routing.compatibility_date": event.headers.get("x-compatibility-date") ?? void 0,
847 "comet.routing.pathname": event.pathname,
848 "comet.routing.method": event.method
849 }
850 },
851 async (span2) => {
852 const compatibilityDate = event.headers.get("x-compatibility-date") ?? void 0;
853 if (compatibilityDate && new Date(compatibilityDate) > /* @__PURE__ */ new Date() && !this.options.dev) {
854 event.reply.badRequest({ message: "Invalid compatibility date" });
855 } else {
856 const route = this.router.find(event.pathname, event.method, compatibilityDate);
857 if (!route) {
858 if (event.method === "OPTIONS" /* OPTIONS */) {
859 await preflightHandler(this.router, this.options.cors).handler(input);
860 } else {
861 event.reply.notFound();
862 }
863 } else {
864 event.params = getPathnameParameters(event.pathname, route.pathname);
865 if (!event.reply.sent) schemaValidation(route).handler(input);
866 if (route.before) {
867 for (const mw of route.before) {
868 await trace5.getTracer(name, version).startActiveSpan(
869 `Comet Middleware${mw.name ? ` ${mw.name}` : ""}`,
870 {
871 attributes: {
872 "comet.mw.name": mw.name,
873 "comet.mw.type": "local-before"
874 }
875 },
876 async (span3) => {
877 await mw.handler(input);
878 span3.end();
879 }
880 );
881 if (event.reply.sent) break;
882 }
883 }
884 if (!event.reply.sent) {
885 await trace5.getTracer(name, version).startActiveSpan(
886 "Comet Main Handler",
887 {
888 attributes: {
889 "comet.route.name": route.name,
890 "comet.route.pathname": route.pathname,
891 "comet.route.compatibility_date": route.compatibilityDate,
892 "comet.route.has_body_schema": !!route.schemas.body,
893 "comet.route.has_query_schema": !!route.schemas.query,
894 "comet.route.has_params_schema": !!route.schemas.params,
895 "comet.route.method": route.method
896 }
897 },
898 async (span3) => {
899 await route.handler(input);
900 span3.end();
901 }
902 );
903 }
904 if (route.after) {
905 if (isDurableObject) {
906 for (const mw of route.after) {
907 await trace5.getTracer(name, version).startActiveSpan(
908 `Comet Middleware${mw.name ? ` ${mw.name}` : ""}`,
909 {
910 attributes: {
911 "comet.mw.name": mw.name,
912 "comet.mw.type": "local-after"
913 }
914 },
915 async (span3) => {
916 await mw.handler(input);
917 span3.end();
918 }
919 );
920 }
921 } else {
922 ctxOrState.waitUntil(Promise.allSettled(route.after.map(async (mw) => {
923 const span3 = trace5.getTracer(name, version).startSpan(`Comet Middleware${mw.name ? ` ${mw.name}` : ""}`, {
924 attributes: {
925 "comet.mw.name": mw.name,
926 "comet.mw.type": "local-after"
927 }
928 });
929 await mw.handler(input);
930 span3.end();
931 })));
932 }
933 }
934 }
935 }
936 span2.end();
937 }
938 );
939 }
940 if (this.options.after) {
941 if (isDurableObject) {
942 for (const mw of this.options.after) {
943 await trace5.getTracer(name, version).startActiveSpan(
944 `Comet Middleware${mw.name ? ` ${mw.name}` : ""}`,
945 {
946 attributes: {
947 "comet.mw.name": mw.name,
948 "comet.mw.type": "global-after"
949 }
950 },
951 async (span2) => {
952 await mw.handler(input);
953 span2.end();
954 }
955 );
956 }
957 } else {
958 ctxOrState.waitUntil(Promise.allSettled(this.options.after.map(async (mw) => {
959 const span2 = trace5.getTracer(name, version).startSpan(`Comet Middleware${mw.name ? ` ${mw.name}` : ""}`, {
960 attributes: {
961 "comet.mw.name": mw.name,
962 "comet.mw.type": "global-after"
963 }
964 });
965 await mw.handler(input);
966 span2.end();
967 })));
968 }
969 }
970 span.end();
971 return await Reply.toResponse(event.reply, this.options);
972 } catch (error) {
973 recordException("[Comet] Failed to handle request.");
974 recordException(error);
975 span.end();
976 return new Response(null, { status: 500 });
977 }
978 });
979 };
980 static getRouter(server2) {
981 return server2.router;
982 }
983};
984function server(options) {
985 return new Server(options);
986}
987export {
988 ALL,
989 BASE_URL,
990 CONNECT,
991 Cookies,
992 DELETE,
993 Data,
994 GET,
995 HEAD,
996 Method,
997 OPTIONS,
998 PATCH,
999 POST,
1000 PUT,
1001 Reply,
1002 Router,
1003 SameSite,
1004 Server,
1005 Status,
1006 TRACE,
1007 compareCompatibilityDates,
1008 compareMethods,
1009 comparePathnames,
1010 cors,
1011 defaultCookiesOptions,
1012 getPathnameParameters,
1013 isValidCompatibilityDate,
1014 isValidPathname,
1015 logger,
1016 middleware,
1017 next,
1018 preflightHandler,
1019 recordException,
1020 schemaValidation,
1021 server
1022};