@kaito-http/core 4.0.0-beta.1 → 4.0.0-beta.10

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.
@@ -24,33 +24,101 @@ __export(cors_exports, {
24
24
  experimental_createOriginMatcher: () => experimental_createOriginMatcher
25
25
  });
26
26
  module.exports = __toCommonJS(cors_exports);
27
- function experimental_createOriginMatcher(origins) {
27
+ function experimental_createOriginMatcher(originIterator) {
28
+ const origins = Array.from(originIterator);
28
29
  if (origins.length === 0) {
29
30
  return () => false;
30
31
  }
32
+ const escapedCharsRegex = /[.+?^${}()|[\]\\]/g;
31
33
  const source = origins.map((origin) => {
32
- if (origin.startsWith("*.")) {
33
- const escapedDomain = origin.slice(2).replace(/[.+?^${}()|[\]\\]/g, "\\$&");
34
- return `^(?:https?://)[^.]+\\.${escapedDomain}$`;
34
+ if (origin.includes("://*.")) {
35
+ const parts = origin.split("://");
36
+ if (parts.length !== 2) {
37
+ throw new Error(`Invalid origin pattern: ${origin}. Must include protocol (e.g., https://*.example.com)`);
38
+ }
39
+ const [protocol, rest] = parts;
40
+ const domain = rest.slice(2).replace(escapedCharsRegex, "\\$&");
41
+ const pattern = `^${protocol.replace(escapedCharsRegex, "\\$&")}:\\/\\/[^.]+\\.${domain}$`;
42
+ return pattern;
35
43
  } else {
36
- const escapedOrigin = origin.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
37
- return `^${escapedOrigin}$`;
44
+ const pattern = `^${origin.replace(escapedCharsRegex, "\\$&")}$`;
45
+ return pattern;
38
46
  }
39
47
  }).join("|");
40
48
  const regex = new RegExp(source);
41
49
  return (origin) => regex.test(origin);
42
50
  }
43
- function experimental_createCORSTransform(origins) {
44
- const matcher = experimental_createOriginMatcher(origins);
45
- return (request, response) => {
46
- const origin = request.headers.get("Origin");
47
- if (origin && matcher(origin)) {
48
- response.headers.set("Access-Control-Allow-Origin", origin);
49
- response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
50
- response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
51
- response.headers.set("Access-Control-Max-Age", "86400");
52
- response.headers.set("Access-Control-Allow-Credentials", "true");
53
- }
51
+ function experimental_createCORSTransform(originsIterator) {
52
+ let allowedOrigins = new Set(originsIterator);
53
+ let matcher = experimental_createOriginMatcher(allowedOrigins);
54
+ const updateMatcher = () => {
55
+ matcher = experimental_createOriginMatcher(allowedOrigins);
56
+ };
57
+ return {
58
+ /**
59
+ * Handle OPTIONS requests in Kaito's `before()` hook
60
+ *
61
+ * @param request - The request object
62
+ * @returns A 204 response
63
+ */
64
+ before: (request) => {
65
+ if (request.method === "OPTIONS") {
66
+ return new Response(null, { status: 204 });
67
+ }
68
+ },
69
+ /**
70
+ * Apply CORS headers to the response if origin matches.
71
+ *
72
+ * @param request - The request object
73
+ * @param response - The response object
74
+ */
75
+ transform: (request, response) => {
76
+ const origin = request.headers.get("Origin");
77
+ if (origin && matcher(origin)) {
78
+ response.headers.set("Access-Control-Allow-Origin", origin);
79
+ response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
80
+ response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
81
+ response.headers.set("Access-Control-Max-Age", "86400");
82
+ response.headers.set("Access-Control-Allow-Credentials", "true");
83
+ }
84
+ },
85
+ /**
86
+ * Replace all allowed origins with a new set.
87
+ *
88
+ * @param newOrigins - The new set of allowed origins
89
+ */
90
+ setOrigins: (newOrigins) => {
91
+ allowedOrigins = new Set(newOrigins);
92
+ updateMatcher();
93
+ },
94
+ /**
95
+ * Add one or more origins to the allowed list.
96
+ *
97
+ * @param origins - The origins to add
98
+ */
99
+ addOrigins: (...origins) => {
100
+ for (const origin of origins) {
101
+ allowedOrigins.add(origin);
102
+ }
103
+ updateMatcher();
104
+ },
105
+ /**
106
+ * Remove one or more origins from the allowed list.
107
+ *
108
+ * @param origins - The origins to remove
109
+ */
110
+ removeOrigins: (...origins) => {
111
+ for (const origin of origins) {
112
+ allowedOrigins.delete(origin);
113
+ }
114
+ updateMatcher();
115
+ },
116
+ /**
117
+ * Clones the current set of allowed origins and returns it
118
+ *
119
+ * @returns A set of allowed origins
120
+ */
121
+ getOrigins: () => new Set(allowedOrigins)
54
122
  };
55
123
  }
56
124
  // Annotate the CommonJS export names for ESM import in node:
@@ -1,55 +1,151 @@
1
1
  /**
2
2
  * Creates a function that matches origins against a predefined set of patterns, supporting wildcards.
3
- * The matcher handles both exact matches and wildcard subdomain patterns (e.g., '*.example.com').
3
+ * The matcher handles both exact matches and wildcard subdomain patterns.
4
4
  *
5
5
  * **⚠️ This API is experimental and may change or even be removed in the future. ⚠️**
6
6
  *
7
- * @param origins Array of origin patterns to match against.
8
- * Patterns can be exact origins (e.g., 'https://example.com') or wildcard patterns (e.g., '*.example.com') that match subdomains.
7
+ * @param originIterator Array of origin patterns to match against.
8
+ * Each pattern MUST include the protocol (http:// or https://).
9
+ * Two types of patterns are supported:
10
+ * 1. Exact matches (e.g., 'https://example.com') - matches only the exact domain with exact protocol
11
+ * 2. Wildcard subdomain patterns (e.g., 'https://*.example.com') - matches ONLY subdomains with exact protocol
12
+ *
13
+ * Important matching rules:
14
+ * - Protocol is always matched exactly - if you need both HTTP and HTTPS, include both patterns
15
+ * - Wildcard patterns (e.g., 'https://*.example.com') will ONLY match subdomains, NOT the root domain
16
+ * - To match both subdomains AND the root domain, include both patterns:
17
+ * ['https://*.example.com', 'https://example.com']
18
+ *
9
19
  * @returns A function that tests if an origin matches any of the patterns
10
20
  *
11
21
  * @example
12
22
  * ```typescript
13
23
  * const allowedOrigins = [
14
- * 'https://example.com',
15
- * '*.trusted-domain.com' // Won't match https://evil-domain.com, only subdomains
24
+ * // Exact matches - protocol required
25
+ * 'https://example.com', // matches only https://example.com
26
+ * 'http://example.com', // matches only http://example.com
27
+ *
28
+ * // Wildcard subdomain matches - protocol required
29
+ * 'https://*.example.com', // matches https://app.example.com, https://api.example.com
30
+ * // does NOT match https://example.com
31
+ *
32
+ * // To match both HTTP and HTTPS, include both
33
+ * 'https://*.staging.com', // matches https://app.staging.com
34
+ * 'http://*.staging.com', // matches http://app.staging.com
35
+ *
36
+ * // To match both subdomains and root domain, include both
37
+ * 'https://*.production.com', // matches https://app.production.com
38
+ * 'https://production.com', // matches https://production.com
16
39
  * ];
17
40
  *
18
41
  * const matcher = createOriginMatcher(allowedOrigins);
19
42
  *
20
- * // Exact match
21
- * console.log(matcher('https://example.com')); // true
22
- * console.log(matcher('http://example.com')); // false
43
+ * // Exact matches
44
+ * matcher('https://example.com'); // true
45
+ * matcher('http://example.com'); // true
23
46
  *
24
- * // Wildcard subdomain matches
25
- * console.log(matcher('https://app.trusted-domain.com')); // true
26
- * console.log(matcher('https://staging.trusted-domain.com')); // true
27
- * console.log(matcher('https://trusted-domain.com')); // false, because it's not a subdomain
28
- * console.log(matcher('https://evil-domain.com')); // false
47
+ * // Subdomain matches (protocol specific)
48
+ * matcher('https://app.example.com'); // true
49
+ * matcher('http://app.example.com'); // false - wrong protocol
50
+ *
51
+ * // Root domain with wildcard pattern
52
+ * matcher('https://example.com'); // false - wildcards don't match root
53
+ * matcher('https://production.com'); // true - matched by exact pattern
29
54
  * ```
30
55
  */
31
- declare function experimental_createOriginMatcher(origins: string[]): (origin: string) => boolean;
56
+ declare function experimental_createOriginMatcher(originIterator: Iterable<string>): (origin: string) => boolean;
32
57
  /**
33
- * Create a function to apply CORS headers with sane defaults for most apps.
58
+ * Create a CORS handler with sane defaults for most apps.
34
59
  *
35
60
  * **⚠️ This API is experimental and may change or even be removed in the future. ⚠️**
36
61
  *
37
- * @param options Options object
38
- * @returns A function that will mutate the Response object by applying the CORS headers
62
+ * @param originsIterator Array of allowed origin patterns. Each pattern must include protocol (http:// or https://).
63
+ * Supports both exact matches and wildcard subdomain patterns. See {@link experimental_createOriginMatcher}
64
+ * for detailed pattern matching rules.
65
+ *
66
+ * @returns An object containing:
67
+ * - `before`: A handler for OPTIONS requests that returns a 204 response
68
+ * - `transform`: A function that applies CORS headers to the response if origin matches
69
+ * - `setOrigins`: A function to replace all allowed origins with a new set
70
+ * - `appendOrigin`: A function to add a new origin to the allowed list
71
+ * - `removeOrigin`: A function to remove an origin from the allowed list
72
+ * - `getOrigins`: A function that returns the current list of allowed origins
73
+ *
39
74
  * @example
40
75
  * ```ts
41
- * const cors = createCORSHandler({
42
- * origins: ['https://example.com', "*.allows-subdomains.com", "http://localhost:3000"],
43
- * });
76
+ * const corsHandler = experimental_createCORSTransform([
77
+ * // Exact matches
78
+ * 'https://example.com',
79
+ * 'http://localhost:3000',
80
+ *
81
+ * // Wildcard subdomain matches
82
+ * 'https://*.myapp.com', // matches https://dashboard.myapp.com
83
+ * 'http://*.myapp.com', // matches http://dashboard.myapp.com
44
84
  *
45
- * const handler = createKaitoHandler({
46
- * // ...
47
- * transform: async (request, response) => {
48
- * cors(request, response);
49
- * }
85
+ * // Match both subdomain and root domain
86
+ * 'https://*.staging.com', // matches https://app.staging.com
87
+ * 'https://staging.com' // matches https://staging.com
88
+ * ]);
89
+ *
90
+ * const router = create({
91
+ * // Handle preflight requests
92
+ * before: corsHandler.before,
93
+ *
94
+ * // Or expanded
95
+ * before: (request) => {
96
+ * const res = cors.before(request);
97
+ * if (res) return res;
98
+ * },
99
+ *
100
+ * // Apply CORS headers to all responses
101
+ * transform: corsHandler.transform,
50
102
  * });
103
+ *
104
+ * // Manage origins dynamically
105
+ * corsHandler.appendOrigin('https://newdomain.com');
106
+ * corsHandler.removeOrigin('http://localhost:3000');
107
+ * corsHandler.setOrigins(['https://completely-new-domain.com']);
51
108
  * ```
52
109
  */
53
- declare function experimental_createCORSTransform(origins: string[]): (request: Request, response: Response) => void;
110
+ declare function experimental_createCORSTransform(originsIterator: Iterable<string>): {
111
+ /**
112
+ * Handle OPTIONS requests in Kaito's `before()` hook
113
+ *
114
+ * @param request - The request object
115
+ * @returns A 204 response
116
+ */
117
+ before: (request: Request) => Response | undefined;
118
+ /**
119
+ * Apply CORS headers to the response if origin matches.
120
+ *
121
+ * @param request - The request object
122
+ * @param response - The response object
123
+ */
124
+ transform: (request: Request, response: Response) => void;
125
+ /**
126
+ * Replace all allowed origins with a new set.
127
+ *
128
+ * @param newOrigins - The new set of allowed origins
129
+ */
130
+ setOrigins: (newOrigins: Iterable<string>) => void;
131
+ /**
132
+ * Add one or more origins to the allowed list.
133
+ *
134
+ * @param origins - The origins to add
135
+ */
136
+ addOrigins: (...origins: string[]) => void;
137
+ /**
138
+ * Remove one or more origins from the allowed list.
139
+ *
140
+ * @param origins - The origins to remove
141
+ */
142
+ removeOrigins: (...origins: string[]) => void;
143
+ /**
144
+ * Clones the current set of allowed origins and returns it
145
+ *
146
+ * @returns A set of allowed origins
147
+ */
148
+ getOrigins: () => Set<string>;
149
+ };
54
150
 
55
151
  export { experimental_createCORSTransform, experimental_createOriginMatcher };
@@ -1,55 +1,151 @@
1
1
  /**
2
2
  * Creates a function that matches origins against a predefined set of patterns, supporting wildcards.
3
- * The matcher handles both exact matches and wildcard subdomain patterns (e.g., '*.example.com').
3
+ * The matcher handles both exact matches and wildcard subdomain patterns.
4
4
  *
5
5
  * **⚠️ This API is experimental and may change or even be removed in the future. ⚠️**
6
6
  *
7
- * @param origins Array of origin patterns to match against.
8
- * Patterns can be exact origins (e.g., 'https://example.com') or wildcard patterns (e.g., '*.example.com') that match subdomains.
7
+ * @param originIterator Array of origin patterns to match against.
8
+ * Each pattern MUST include the protocol (http:// or https://).
9
+ * Two types of patterns are supported:
10
+ * 1. Exact matches (e.g., 'https://example.com') - matches only the exact domain with exact protocol
11
+ * 2. Wildcard subdomain patterns (e.g., 'https://*.example.com') - matches ONLY subdomains with exact protocol
12
+ *
13
+ * Important matching rules:
14
+ * - Protocol is always matched exactly - if you need both HTTP and HTTPS, include both patterns
15
+ * - Wildcard patterns (e.g., 'https://*.example.com') will ONLY match subdomains, NOT the root domain
16
+ * - To match both subdomains AND the root domain, include both patterns:
17
+ * ['https://*.example.com', 'https://example.com']
18
+ *
9
19
  * @returns A function that tests if an origin matches any of the patterns
10
20
  *
11
21
  * @example
12
22
  * ```typescript
13
23
  * const allowedOrigins = [
14
- * 'https://example.com',
15
- * '*.trusted-domain.com' // Won't match https://evil-domain.com, only subdomains
24
+ * // Exact matches - protocol required
25
+ * 'https://example.com', // matches only https://example.com
26
+ * 'http://example.com', // matches only http://example.com
27
+ *
28
+ * // Wildcard subdomain matches - protocol required
29
+ * 'https://*.example.com', // matches https://app.example.com, https://api.example.com
30
+ * // does NOT match https://example.com
31
+ *
32
+ * // To match both HTTP and HTTPS, include both
33
+ * 'https://*.staging.com', // matches https://app.staging.com
34
+ * 'http://*.staging.com', // matches http://app.staging.com
35
+ *
36
+ * // To match both subdomains and root domain, include both
37
+ * 'https://*.production.com', // matches https://app.production.com
38
+ * 'https://production.com', // matches https://production.com
16
39
  * ];
17
40
  *
18
41
  * const matcher = createOriginMatcher(allowedOrigins);
19
42
  *
20
- * // Exact match
21
- * console.log(matcher('https://example.com')); // true
22
- * console.log(matcher('http://example.com')); // false
43
+ * // Exact matches
44
+ * matcher('https://example.com'); // true
45
+ * matcher('http://example.com'); // true
23
46
  *
24
- * // Wildcard subdomain matches
25
- * console.log(matcher('https://app.trusted-domain.com')); // true
26
- * console.log(matcher('https://staging.trusted-domain.com')); // true
27
- * console.log(matcher('https://trusted-domain.com')); // false, because it's not a subdomain
28
- * console.log(matcher('https://evil-domain.com')); // false
47
+ * // Subdomain matches (protocol specific)
48
+ * matcher('https://app.example.com'); // true
49
+ * matcher('http://app.example.com'); // false - wrong protocol
50
+ *
51
+ * // Root domain with wildcard pattern
52
+ * matcher('https://example.com'); // false - wildcards don't match root
53
+ * matcher('https://production.com'); // true - matched by exact pattern
29
54
  * ```
30
55
  */
31
- declare function experimental_createOriginMatcher(origins: string[]): (origin: string) => boolean;
56
+ declare function experimental_createOriginMatcher(originIterator: Iterable<string>): (origin: string) => boolean;
32
57
  /**
33
- * Create a function to apply CORS headers with sane defaults for most apps.
58
+ * Create a CORS handler with sane defaults for most apps.
34
59
  *
35
60
  * **⚠️ This API is experimental and may change or even be removed in the future. ⚠️**
36
61
  *
37
- * @param options Options object
38
- * @returns A function that will mutate the Response object by applying the CORS headers
62
+ * @param originsIterator Array of allowed origin patterns. Each pattern must include protocol (http:// or https://).
63
+ * Supports both exact matches and wildcard subdomain patterns. See {@link experimental_createOriginMatcher}
64
+ * for detailed pattern matching rules.
65
+ *
66
+ * @returns An object containing:
67
+ * - `before`: A handler for OPTIONS requests that returns a 204 response
68
+ * - `transform`: A function that applies CORS headers to the response if origin matches
69
+ * - `setOrigins`: A function to replace all allowed origins with a new set
70
+ * - `appendOrigin`: A function to add a new origin to the allowed list
71
+ * - `removeOrigin`: A function to remove an origin from the allowed list
72
+ * - `getOrigins`: A function that returns the current list of allowed origins
73
+ *
39
74
  * @example
40
75
  * ```ts
41
- * const cors = createCORSHandler({
42
- * origins: ['https://example.com', "*.allows-subdomains.com", "http://localhost:3000"],
43
- * });
76
+ * const corsHandler = experimental_createCORSTransform([
77
+ * // Exact matches
78
+ * 'https://example.com',
79
+ * 'http://localhost:3000',
80
+ *
81
+ * // Wildcard subdomain matches
82
+ * 'https://*.myapp.com', // matches https://dashboard.myapp.com
83
+ * 'http://*.myapp.com', // matches http://dashboard.myapp.com
44
84
  *
45
- * const handler = createKaitoHandler({
46
- * // ...
47
- * transform: async (request, response) => {
48
- * cors(request, response);
49
- * }
85
+ * // Match both subdomain and root domain
86
+ * 'https://*.staging.com', // matches https://app.staging.com
87
+ * 'https://staging.com' // matches https://staging.com
88
+ * ]);
89
+ *
90
+ * const router = create({
91
+ * // Handle preflight requests
92
+ * before: corsHandler.before,
93
+ *
94
+ * // Or expanded
95
+ * before: (request) => {
96
+ * const res = cors.before(request);
97
+ * if (res) return res;
98
+ * },
99
+ *
100
+ * // Apply CORS headers to all responses
101
+ * transform: corsHandler.transform,
50
102
  * });
103
+ *
104
+ * // Manage origins dynamically
105
+ * corsHandler.appendOrigin('https://newdomain.com');
106
+ * corsHandler.removeOrigin('http://localhost:3000');
107
+ * corsHandler.setOrigins(['https://completely-new-domain.com']);
51
108
  * ```
52
109
  */
53
- declare function experimental_createCORSTransform(origins: string[]): (request: Request, response: Response) => void;
110
+ declare function experimental_createCORSTransform(originsIterator: Iterable<string>): {
111
+ /**
112
+ * Handle OPTIONS requests in Kaito's `before()` hook
113
+ *
114
+ * @param request - The request object
115
+ * @returns A 204 response
116
+ */
117
+ before: (request: Request) => Response | undefined;
118
+ /**
119
+ * Apply CORS headers to the response if origin matches.
120
+ *
121
+ * @param request - The request object
122
+ * @param response - The response object
123
+ */
124
+ transform: (request: Request, response: Response) => void;
125
+ /**
126
+ * Replace all allowed origins with a new set.
127
+ *
128
+ * @param newOrigins - The new set of allowed origins
129
+ */
130
+ setOrigins: (newOrigins: Iterable<string>) => void;
131
+ /**
132
+ * Add one or more origins to the allowed list.
133
+ *
134
+ * @param origins - The origins to add
135
+ */
136
+ addOrigins: (...origins: string[]) => void;
137
+ /**
138
+ * Remove one or more origins from the allowed list.
139
+ *
140
+ * @param origins - The origins to remove
141
+ */
142
+ removeOrigins: (...origins: string[]) => void;
143
+ /**
144
+ * Clones the current set of allowed origins and returns it
145
+ *
146
+ * @returns A set of allowed origins
147
+ */
148
+ getOrigins: () => Set<string>;
149
+ };
54
150
 
55
151
  export { experimental_createCORSTransform, experimental_createOriginMatcher };
package/dist/cors/cors.js CHANGED
@@ -1,31 +1,99 @@
1
1
  // src/cors/cors.ts
2
- function experimental_createOriginMatcher(origins) {
2
+ function experimental_createOriginMatcher(originIterator) {
3
+ const origins = Array.from(originIterator);
3
4
  if (origins.length === 0) {
4
5
  return () => false;
5
6
  }
7
+ const escapedCharsRegex = /[.+?^${}()|[\]\\]/g;
6
8
  const source = origins.map((origin) => {
7
- if (origin.startsWith("*.")) {
8
- const escapedDomain = origin.slice(2).replace(/[.+?^${}()|[\]\\]/g, "\\$&");
9
- return `^(?:https?://)[^.]+\\.${escapedDomain}$`;
9
+ if (origin.includes("://*.")) {
10
+ const parts = origin.split("://");
11
+ if (parts.length !== 2) {
12
+ throw new Error(`Invalid origin pattern: ${origin}. Must include protocol (e.g., https://*.example.com)`);
13
+ }
14
+ const [protocol, rest] = parts;
15
+ const domain = rest.slice(2).replace(escapedCharsRegex, "\\$&");
16
+ const pattern = `^${protocol.replace(escapedCharsRegex, "\\$&")}:\\/\\/[^.]+\\.${domain}$`;
17
+ return pattern;
10
18
  } else {
11
- const escapedOrigin = origin.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
12
- return `^${escapedOrigin}$`;
19
+ const pattern = `^${origin.replace(escapedCharsRegex, "\\$&")}$`;
20
+ return pattern;
13
21
  }
14
22
  }).join("|");
15
23
  const regex = new RegExp(source);
16
24
  return (origin) => regex.test(origin);
17
25
  }
18
- function experimental_createCORSTransform(origins) {
19
- const matcher = experimental_createOriginMatcher(origins);
20
- return (request, response) => {
21
- const origin = request.headers.get("Origin");
22
- if (origin && matcher(origin)) {
23
- response.headers.set("Access-Control-Allow-Origin", origin);
24
- response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
25
- response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
26
- response.headers.set("Access-Control-Max-Age", "86400");
27
- response.headers.set("Access-Control-Allow-Credentials", "true");
28
- }
26
+ function experimental_createCORSTransform(originsIterator) {
27
+ let allowedOrigins = new Set(originsIterator);
28
+ let matcher = experimental_createOriginMatcher(allowedOrigins);
29
+ const updateMatcher = () => {
30
+ matcher = experimental_createOriginMatcher(allowedOrigins);
31
+ };
32
+ return {
33
+ /**
34
+ * Handle OPTIONS requests in Kaito's `before()` hook
35
+ *
36
+ * @param request - The request object
37
+ * @returns A 204 response
38
+ */
39
+ before: (request) => {
40
+ if (request.method === "OPTIONS") {
41
+ return new Response(null, { status: 204 });
42
+ }
43
+ },
44
+ /**
45
+ * Apply CORS headers to the response if origin matches.
46
+ *
47
+ * @param request - The request object
48
+ * @param response - The response object
49
+ */
50
+ transform: (request, response) => {
51
+ const origin = request.headers.get("Origin");
52
+ if (origin && matcher(origin)) {
53
+ response.headers.set("Access-Control-Allow-Origin", origin);
54
+ response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
55
+ response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
56
+ response.headers.set("Access-Control-Max-Age", "86400");
57
+ response.headers.set("Access-Control-Allow-Credentials", "true");
58
+ }
59
+ },
60
+ /**
61
+ * Replace all allowed origins with a new set.
62
+ *
63
+ * @param newOrigins - The new set of allowed origins
64
+ */
65
+ setOrigins: (newOrigins) => {
66
+ allowedOrigins = new Set(newOrigins);
67
+ updateMatcher();
68
+ },
69
+ /**
70
+ * Add one or more origins to the allowed list.
71
+ *
72
+ * @param origins - The origins to add
73
+ */
74
+ addOrigins: (...origins) => {
75
+ for (const origin of origins) {
76
+ allowedOrigins.add(origin);
77
+ }
78
+ updateMatcher();
79
+ },
80
+ /**
81
+ * Remove one or more origins from the allowed list.
82
+ *
83
+ * @param origins - The origins to remove
84
+ */
85
+ removeOrigins: (...origins) => {
86
+ for (const origin of origins) {
87
+ allowedOrigins.delete(origin);
88
+ }
89
+ updateMatcher();
90
+ },
91
+ /**
92
+ * Clones the current set of allowed origins and returns it
93
+ *
94
+ * @returns A set of allowed origins
95
+ */
96
+ getOrigins: () => new Set(allowedOrigins)
29
97
  };
30
98
  }
31
99
  export {
package/dist/index.cjs CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  KaitoError: () => KaitoError,
24
+ KaitoHead: () => KaitoHead,
24
25
  KaitoRequest: () => KaitoRequest,
25
26
  Router: () => Router,
26
27
  WrappedError: () => WrappedError,
@@ -32,6 +33,7 @@ module.exports = __toCommonJS(index_exports);
32
33
  // src/router/router.ts
33
34
  var import_zod = require("zod");
34
35
  var import_zod_openapi = require("zod-openapi");
36
+ var import_extend = require("zod-openapi/extend");
35
37
 
36
38
  // src/error.ts
37
39
  var WrappedError = class _WrappedError extends Error {
@@ -148,7 +150,8 @@ var Router = class _Router {
148
150
  static create = (config) => new _Router({
149
151
  through: async (context) => context,
150
152
  routes: /* @__PURE__ */ new Set(),
151
- config
153
+ config,
154
+ paramsSchema: null
152
155
  });
153
156
  constructor(state) {
154
157
  this.state = state;
@@ -161,17 +164,24 @@ var Router = class _Router {
161
164
  ...typeof route === "object" ? route : { run: route },
162
165
  method,
163
166
  path,
164
- through: this.state.through
167
+ router: this
165
168
  };
166
169
  return new _Router({
167
170
  ...this.state,
168
171
  routes: /* @__PURE__ */ new Set([...this.state.routes, merged])
169
172
  });
170
173
  };
174
+ params = (spec) => new _Router({
175
+ ...this.state,
176
+ paramsSchema: import_zod.z.object(spec)
177
+ });
171
178
  merge = (pathPrefix, other) => {
172
179
  const newRoutes = [...other.state.routes].map((route) => ({
173
180
  ...route,
174
- path: `${pathPrefix}${route.path}`
181
+ // handle pathPrefix = / & route.path = / case causing //
182
+ // we intentionally are replacing on the joining path and not the pathPrefix, in case of
183
+ // /named -> merged to -> / causing /named/ not /named
184
+ path: `${pathPrefix}${route.path === "/" ? "" : route.path}`
175
185
  }));
176
186
  return new _Router({
177
187
  ...this.state,
@@ -228,7 +238,7 @@ var Router = class _Router {
228
238
  const handle = async (req) => {
229
239
  const url = new URL(req.url);
230
240
  const method = req.method;
231
- const { route, params } = findRoute(method, url.pathname);
241
+ const { route, params: rawParams } = findRoute(method, url.pathname);
232
242
  if (!route) {
233
243
  const body = {
234
244
  success: false,
@@ -242,7 +252,11 @@ var Router = class _Router {
242
252
  try {
243
253
  const body = route.body ? await route.body.parseAsync(await req.json()) : void 0;
244
254
  const query = route.fastQuerySchema ? await route.fastQuerySchema.parseAsync(url.searchParams) : {};
245
- const ctx = await route.through(await this.state.config.getContext?.(request, head) ?? null);
255
+ const params = route.router.state.paramsSchema ? route.router.state.paramsSchema.parse(rawParams) : rawParams;
256
+ const ctx = await route.router.state.through(
257
+ await this.state.config.getContext?.(request, head) ?? null,
258
+ params
259
+ );
246
260
  const result = await route.run({
247
261
  ctx,
248
262
  body,
@@ -329,23 +343,35 @@ var Router = class _Router {
329
343
  const paths = {};
330
344
  for (const route of this.state.routes) {
331
345
  const path = route.path;
346
+ if (!route.openapi) {
347
+ continue;
348
+ }
332
349
  const pathWithColonParamsReplaceWithCurlyBraces = path.replace(/:(\w+)/g, "{$1}");
333
350
  if (!paths[pathWithColonParamsReplaceWithCurlyBraces]) {
334
351
  paths[pathWithColonParamsReplaceWithCurlyBraces] = {};
335
352
  }
353
+ const content = route.openapi.body.type === "json" ? {
354
+ "application/json": {
355
+ schema: import_zod.z.object({
356
+ success: import_zod.z.literal(true).openapi({
357
+ type: "boolean",
358
+ enum: [true]
359
+ // Need this as zod-openapi doesn't properly work with literals
360
+ }),
361
+ data: route.openapi.body.schema
362
+ })
363
+ }
364
+ } : {
365
+ "text/event-stream": {
366
+ schema: route.openapi.body.schema
367
+ }
368
+ };
336
369
  const item = {
337
370
  description: route.openapi?.description ?? "Successful response",
338
371
  responses: {
339
372
  200: {
340
373
  description: route.openapi?.description ?? "Successful response",
341
- ...route.openapi ? {
342
- content: {
343
- [{
344
- json: "application/json",
345
- sse: "text/event-stream"
346
- }[route.openapi.body.type]]: { schema: route.openapi?.body.schema }
347
- }
348
- } : {}
374
+ content
349
375
  }
350
376
  }
351
377
  };
@@ -383,10 +409,10 @@ var Router = class _Router {
383
409
  };
384
410
  })
385
411
  });
386
- return this.add("GET", "/openapi.json", async () => Response.json(doc));
412
+ return this.get("/openapi.json", () => Response.json(doc));
387
413
  };
388
- method = (method) => (path, route) => {
389
- return this.add(method, path, route);
414
+ method = (method) => {
415
+ return (path, route) => this.add(method, path, route);
390
416
  };
391
417
  get = this.method("GET");
392
418
  post = this.method("POST");
@@ -398,18 +424,19 @@ var Router = class _Router {
398
424
  through = (through) => {
399
425
  return new _Router({
400
426
  ...this.state,
401
- through: async (context) => await through(await this.state.through(context))
427
+ through: async (context, params) => await through(await this.state.through(context, params), params)
402
428
  });
403
429
  };
404
430
  };
405
431
 
406
432
  // src/create.ts
407
433
  function create(config = {}) {
408
- return () => Router.create(config);
434
+ return Router.create(config);
409
435
  }
410
436
  // Annotate the CommonJS export names for ESM import in node:
411
437
  0 && (module.exports = {
412
438
  KaitoError,
439
+ KaitoHead,
413
440
  KaitoRequest,
414
441
  Router,
415
442
  WrappedError,
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { z } from 'zod';
1
+ import { z, ZodTypeDef } from 'zod';
2
2
  import { KaitoSSEResponse } from './stream/stream.cjs';
3
3
 
4
4
  declare class WrappedError<T> extends Error {
@@ -27,8 +27,6 @@ declare class KaitoRequest {
27
27
  get request(): Request;
28
28
  }
29
29
 
30
- type KaitoMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS' | 'TRACE';
31
-
32
30
  /**
33
31
  * This class is merely a wrapper around a `Headers` object and a status code.
34
32
  * It's used while the router is executing a route to store any mutations to the status
@@ -91,6 +89,10 @@ type APIResponse<T> = ErroredAPIResponse | SuccessfulAPIResponse<T>;
91
89
  type AnyResponse = APIResponse<unknown>;
92
90
  type MakeOptional<T, K extends keyof T> = T extends T ? Omit<T, K> & Partial<Pick<T, K>> : never;
93
91
  type MaybePromise<T> = T | Promise<T>;
92
+ type KaitoMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
93
+ type NotReadonly<T> = {
94
+ -readonly [K in keyof T]: T[K];
95
+ };
94
96
  type ExtractRouteParams<T extends string> = string extends T ? Record<string, string> : T extends `${string}:${infer Param}/${infer Rest}` ? {
95
97
  [k in Param | keyof ExtractRouteParams<Rest>]: string;
96
98
  } : T extends `${string}:${infer Param}` ? {
@@ -109,16 +111,16 @@ type ExtractRouteParams<T extends string> = string extends T ? Record<string, st
109
111
  */
110
112
  type GetContext<Result> = (req: KaitoRequest, head: KaitoHead) => MaybePromise<Result>;
111
113
 
112
- type RouteRunData<Path extends string, Context, QueryOutput, BodyOutput> = {
114
+ type RouteRunData<Params, Context, QueryOutput, BodyOutput> = {
113
115
  ctx: Context;
114
116
  body: BodyOutput;
115
117
  query: QueryOutput;
116
- params: ExtractRouteParams<Path>;
118
+ params: Params;
117
119
  };
118
120
  type AnyQuery = {
119
121
  [key in string]: any;
120
122
  };
121
- type Through<From, To> = (context: From) => Promise<To>;
123
+ type Through<From, To, RequiredParams extends Record<string, unknown>> = (context: From, params: RequiredParams) => Promise<To>;
122
124
  type SSEOutputSpec<Result> = {
123
125
  type: 'sse';
124
126
  schema: z.Schema<Result>;
@@ -133,8 +135,7 @@ type OutputSpec<Result> = {
133
135
  description?: string;
134
136
  body: NoInfer<Result extends KaitoSSEResponse<infer R> ? SSEOutputSpec<R> : JSONOutputSpec<Result>>;
135
137
  };
136
- type Route<ContextTo, Result, Path extends string, Method extends KaitoMethod, Query, Body> = {
137
- through: Through<unknown, ContextTo>;
138
+ type Route<ContextTo, Result, Path extends string, AdditionalParams extends Record<string, unknown>, Method extends KaitoMethod, Query, Body> = {
138
139
  body?: z.Schema<Body>;
139
140
  query?: {
140
141
  [Key in keyof Query]: z.Schema<Query[Key]>;
@@ -142,35 +143,40 @@ type Route<ContextTo, Result, Path extends string, Method extends KaitoMethod, Q
142
143
  path: Path;
143
144
  method: Method;
144
145
  openapi?: OutputSpec<NoInfer<Result>>;
145
- run(data: RouteRunData<Path, ContextTo, Query, Body>): Promise<Result> | Result;
146
+ router: Router<unknown, ContextTo, AdditionalParams, AnyRoute>;
147
+ run(data: RouteRunData<ExtractRouteParams<Path> & AdditionalParams, ContextTo, Query, Body>): Promise<Result> | Result;
146
148
  };
147
- type AnyRoute = Route<any, any, any, any, any, any>;
149
+ type AnyRoute = Route<any, any, any, any, any, any, any>;
148
150
 
149
- type PrefixRoutesPathInner<R extends AnyRoute, Prefix extends `/${string}`> = R extends Route<infer ContextTo, infer Result, infer Path, infer Method, infer Query, infer BodyOutput> ? Route<ContextTo, Result, `${Prefix}${Path}`, Method, Query, BodyOutput> : never;
151
+ type PrefixRoutesPathInner<R extends AnyRoute, Prefix extends `/${string}`> = R extends Route<infer ContextTo, infer Result, infer Path, infer AdditionalParams, infer Method, infer Query, infer BodyOutput> ? Route<ContextTo, Result, `${Prefix}${Path extends '/' ? '' : Path}`, AdditionalParams, Method, Query, BodyOutput> : never;
150
152
  type PrefixRoutesPath<Prefix extends `/${string}`, R extends AnyRoute> = R extends R ? PrefixRoutesPathInner<R, Prefix> : never;
151
- type RouterState<ContextFrom, ContextTo, Routes extends AnyRoute> = {
153
+ type RouterState<ContextFrom, ContextTo, RequiredParams extends Record<string, unknown>, Routes extends AnyRoute> = {
152
154
  routes: Set<Routes>;
153
- through: (context: unknown) => Promise<ContextTo>;
155
+ through: (context: unknown, params: RequiredParams) => Promise<ContextTo>;
154
156
  config: KaitoConfig<ContextFrom>;
157
+ paramsSchema: z.Schema<RequiredParams> | null;
155
158
  };
156
159
  /**
157
160
  * Accepts a router instance, and returns a union of all the routes in the router
158
161
  *
159
162
  * @example
160
163
  * ```ts
161
- * const app = router().get('/', () => 'Hello, world!');
164
+ * const app = router.get('/', () => 'Hello, world!');
162
165
  *
163
166
  * type Routes = InferRoutes<typeof app>;
164
167
  * ```
165
168
  */
166
- type InferRoutes<R extends Router<any, any, any>> = R extends Router<any, any, infer R extends AnyRoute> ? R : never;
167
- declare class Router<ContextFrom, ContextTo, R extends AnyRoute> {
169
+ type InferRoutes<R extends Router<any, any, any, any>> = R extends Router<any, any, any, infer R extends AnyRoute> ? R : never;
170
+ declare class Router<ContextFrom, ContextTo, RequiredParams extends Record<string, unknown>, R extends AnyRoute> {
168
171
  private readonly state;
169
- static create: <Context>(config: KaitoConfig<Context>) => Router<Context, Context, never>;
170
- constructor(state: RouterState<ContextFrom, ContextTo, R>);
172
+ static create: <Context>(config: KaitoConfig<Context>) => Router<Context, Context, {}, never>;
173
+ protected constructor(state: RouterState<ContextFrom, ContextTo, RequiredParams, R>);
171
174
  get routes(): Set<R>;
172
175
  private add;
173
- readonly merge: <PathPrefix extends `/${string}`, OtherRoutes extends AnyRoute>(pathPrefix: PathPrefix, other: Router<ContextFrom, unknown, OtherRoutes>) => Router<ContextFrom, ContextTo, Extract<R | PrefixRoutesPath<PathPrefix, Extract<OtherRoutes, AnyRoute>>, AnyRoute>>;
176
+ params: this extends Router<infer ContextFrom, infer ContextTo, infer Params extends Record<string, unknown>, infer R extends AnyRoute> ? [keyof Params] extends [never] ? <NextParams extends Record<string, unknown> = {}>(spec: {
177
+ [Key in keyof NextParams]: z.ZodType<NextParams[Key], ZodTypeDef, string>;
178
+ }) => Router<ContextFrom, ContextTo, NextParams, R> : 'You cannot define params() on a router that has already had params defined, as routes that already consume params can break.' : never;
179
+ readonly merge: <PathPrefix extends `/${string}`, NextRequiredParams extends Record<string, unknown>, OtherRoutes extends AnyRoute>(pathPrefix: keyof NextRequiredParams extends keyof ExtractRouteParams<PathPrefix> | keyof RequiredParams ? PathPrefix : `Missing ${Exclude<Extract<keyof NextRequiredParams, string>, keyof RequiredParams>}${string}`, other: Router<ContextFrom, unknown, NextRequiredParams, OtherRoutes>) => Router<ContextFrom, ContextTo, RequiredParams, Extract<R | PrefixRoutesPath<PathPrefix, Extract<OtherRoutes, AnyRoute>>, AnyRoute>>;
174
180
  protected static getFindRoute: <R_1>(routes: Map<KaitoMethod, Map<string, R_1>>) => (method: KaitoMethod, path: string) => {
175
181
  route?: never;
176
182
  params?: never;
@@ -187,16 +193,16 @@ declare class Router<ContextFrom, ContextTo, R extends AnyRoute> {
187
193
  description?: string;
188
194
  };
189
195
  servers?: Partial<Record<(`https://` | `http://`) | ({} & string), string>>;
190
- }) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Response, "/openapi.json", "GET", AnyQuery, unknown>>;
196
+ }) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Response, "/openapi.json", RequiredParams, "GET", {}, never>>;
191
197
  private readonly method;
192
- get: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<Path, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, "GET", Query, Body>, "body" | "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, "GET", Query, Body>>;
193
- post: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<Path, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, "POST", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, "POST", Query, Body>>;
194
- put: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<Path, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, "PUT", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, "PUT", Query, Body>>;
195
- patch: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<Path, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, "PATCH", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, "PATCH", Query, Body>>;
196
- delete: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<Path, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, "DELETE", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, "DELETE", Query, Body>>;
197
- head: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<Path, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, "HEAD", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, "HEAD", Query, Body>>;
198
- options: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<Path, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, "OPTIONS", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, "OPTIONS", Query, Body>>;
199
- through: <NextContext>(through: (context: ContextTo) => MaybePromise<NextContext>) => Router<ContextFrom, NextContext, R>;
198
+ get: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "GET", Query, Body>, "body" | "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "GET", Query, Body>>;
199
+ post: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "POST", Query, Body>, "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "POST", Query, Body>>;
200
+ put: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "PUT", Query, Body>, "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "PUT", Query, Body>>;
201
+ patch: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "PATCH", Query, Body>, "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "PATCH", Query, Body>>;
202
+ delete: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "DELETE", Query, Body>, "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "DELETE", Query, Body>>;
203
+ head: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "HEAD", Query, Body>, "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "HEAD", Query, Body>>;
204
+ options: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "OPTIONS", Query, Body>, "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "OPTIONS", Query, Body>>;
205
+ through: <NextContext>(through: (context: ContextTo, params: RequiredParams) => MaybePromise<NextContext>) => Router<ContextFrom, NextContext, RequiredParams, R>;
200
206
  }
201
207
 
202
208
  type KaitoConfig<ContextFrom> = {
@@ -265,6 +271,6 @@ type KaitoConfig<ContextFrom> = {
265
271
  * @param config - The configuration for the router
266
272
  * @returns A new Kaito router
267
273
  */
268
- declare function create<Context = null>(config?: KaitoConfig<Context>): () => Router<Context, Context, never>;
274
+ declare function create<Context = null>(config?: KaitoConfig<Context>): Router<Context, Context, {}, never>;
269
275
 
270
- export { type APIResponse, type AnyQuery, type AnyResponse, type AnyRoute, type ErroredAPIResponse, type ExtractRouteParams, type GetContext, type InferRoutes, type JSONOutputSpec, type KaitoConfig, KaitoError, type KaitoMethod, KaitoRequest, type MakeOptional, type MaybePromise, type OutputSpec, type Route, type RouteRunData, Router, type RouterState, type SSEOutputSpec, type SuccessfulAPIResponse, type Through, WrappedError, create, isNodeLikeDev };
276
+ export { type APIResponse, type AnyQuery, type AnyResponse, type AnyRoute, type ErroredAPIResponse, type ExtractRouteParams, type GetContext, type InferRoutes, type JSONOutputSpec, type KaitoConfig, KaitoError, KaitoHead, type KaitoMethod, KaitoRequest, type MakeOptional, type MaybePromise, type NotReadonly, type OutputSpec, type Route, type RouteRunData, Router, type RouterState, type SSEOutputSpec, type SuccessfulAPIResponse, type Through, WrappedError, create, isNodeLikeDev };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { z } from 'zod';
1
+ import { z, ZodTypeDef } from 'zod';
2
2
  import { KaitoSSEResponse } from './stream/stream.js';
3
3
 
4
4
  declare class WrappedError<T> extends Error {
@@ -27,8 +27,6 @@ declare class KaitoRequest {
27
27
  get request(): Request;
28
28
  }
29
29
 
30
- type KaitoMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS' | 'TRACE';
31
-
32
30
  /**
33
31
  * This class is merely a wrapper around a `Headers` object and a status code.
34
32
  * It's used while the router is executing a route to store any mutations to the status
@@ -91,6 +89,10 @@ type APIResponse<T> = ErroredAPIResponse | SuccessfulAPIResponse<T>;
91
89
  type AnyResponse = APIResponse<unknown>;
92
90
  type MakeOptional<T, K extends keyof T> = T extends T ? Omit<T, K> & Partial<Pick<T, K>> : never;
93
91
  type MaybePromise<T> = T | Promise<T>;
92
+ type KaitoMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
93
+ type NotReadonly<T> = {
94
+ -readonly [K in keyof T]: T[K];
95
+ };
94
96
  type ExtractRouteParams<T extends string> = string extends T ? Record<string, string> : T extends `${string}:${infer Param}/${infer Rest}` ? {
95
97
  [k in Param | keyof ExtractRouteParams<Rest>]: string;
96
98
  } : T extends `${string}:${infer Param}` ? {
@@ -109,16 +111,16 @@ type ExtractRouteParams<T extends string> = string extends T ? Record<string, st
109
111
  */
110
112
  type GetContext<Result> = (req: KaitoRequest, head: KaitoHead) => MaybePromise<Result>;
111
113
 
112
- type RouteRunData<Path extends string, Context, QueryOutput, BodyOutput> = {
114
+ type RouteRunData<Params, Context, QueryOutput, BodyOutput> = {
113
115
  ctx: Context;
114
116
  body: BodyOutput;
115
117
  query: QueryOutput;
116
- params: ExtractRouteParams<Path>;
118
+ params: Params;
117
119
  };
118
120
  type AnyQuery = {
119
121
  [key in string]: any;
120
122
  };
121
- type Through<From, To> = (context: From) => Promise<To>;
123
+ type Through<From, To, RequiredParams extends Record<string, unknown>> = (context: From, params: RequiredParams) => Promise<To>;
122
124
  type SSEOutputSpec<Result> = {
123
125
  type: 'sse';
124
126
  schema: z.Schema<Result>;
@@ -133,8 +135,7 @@ type OutputSpec<Result> = {
133
135
  description?: string;
134
136
  body: NoInfer<Result extends KaitoSSEResponse<infer R> ? SSEOutputSpec<R> : JSONOutputSpec<Result>>;
135
137
  };
136
- type Route<ContextTo, Result, Path extends string, Method extends KaitoMethod, Query, Body> = {
137
- through: Through<unknown, ContextTo>;
138
+ type Route<ContextTo, Result, Path extends string, AdditionalParams extends Record<string, unknown>, Method extends KaitoMethod, Query, Body> = {
138
139
  body?: z.Schema<Body>;
139
140
  query?: {
140
141
  [Key in keyof Query]: z.Schema<Query[Key]>;
@@ -142,35 +143,40 @@ type Route<ContextTo, Result, Path extends string, Method extends KaitoMethod, Q
142
143
  path: Path;
143
144
  method: Method;
144
145
  openapi?: OutputSpec<NoInfer<Result>>;
145
- run(data: RouteRunData<Path, ContextTo, Query, Body>): Promise<Result> | Result;
146
+ router: Router<unknown, ContextTo, AdditionalParams, AnyRoute>;
147
+ run(data: RouteRunData<ExtractRouteParams<Path> & AdditionalParams, ContextTo, Query, Body>): Promise<Result> | Result;
146
148
  };
147
- type AnyRoute = Route<any, any, any, any, any, any>;
149
+ type AnyRoute = Route<any, any, any, any, any, any, any>;
148
150
 
149
- type PrefixRoutesPathInner<R extends AnyRoute, Prefix extends `/${string}`> = R extends Route<infer ContextTo, infer Result, infer Path, infer Method, infer Query, infer BodyOutput> ? Route<ContextTo, Result, `${Prefix}${Path}`, Method, Query, BodyOutput> : never;
151
+ type PrefixRoutesPathInner<R extends AnyRoute, Prefix extends `/${string}`> = R extends Route<infer ContextTo, infer Result, infer Path, infer AdditionalParams, infer Method, infer Query, infer BodyOutput> ? Route<ContextTo, Result, `${Prefix}${Path extends '/' ? '' : Path}`, AdditionalParams, Method, Query, BodyOutput> : never;
150
152
  type PrefixRoutesPath<Prefix extends `/${string}`, R extends AnyRoute> = R extends R ? PrefixRoutesPathInner<R, Prefix> : never;
151
- type RouterState<ContextFrom, ContextTo, Routes extends AnyRoute> = {
153
+ type RouterState<ContextFrom, ContextTo, RequiredParams extends Record<string, unknown>, Routes extends AnyRoute> = {
152
154
  routes: Set<Routes>;
153
- through: (context: unknown) => Promise<ContextTo>;
155
+ through: (context: unknown, params: RequiredParams) => Promise<ContextTo>;
154
156
  config: KaitoConfig<ContextFrom>;
157
+ paramsSchema: z.Schema<RequiredParams> | null;
155
158
  };
156
159
  /**
157
160
  * Accepts a router instance, and returns a union of all the routes in the router
158
161
  *
159
162
  * @example
160
163
  * ```ts
161
- * const app = router().get('/', () => 'Hello, world!');
164
+ * const app = router.get('/', () => 'Hello, world!');
162
165
  *
163
166
  * type Routes = InferRoutes<typeof app>;
164
167
  * ```
165
168
  */
166
- type InferRoutes<R extends Router<any, any, any>> = R extends Router<any, any, infer R extends AnyRoute> ? R : never;
167
- declare class Router<ContextFrom, ContextTo, R extends AnyRoute> {
169
+ type InferRoutes<R extends Router<any, any, any, any>> = R extends Router<any, any, any, infer R extends AnyRoute> ? R : never;
170
+ declare class Router<ContextFrom, ContextTo, RequiredParams extends Record<string, unknown>, R extends AnyRoute> {
168
171
  private readonly state;
169
- static create: <Context>(config: KaitoConfig<Context>) => Router<Context, Context, never>;
170
- constructor(state: RouterState<ContextFrom, ContextTo, R>);
172
+ static create: <Context>(config: KaitoConfig<Context>) => Router<Context, Context, {}, never>;
173
+ protected constructor(state: RouterState<ContextFrom, ContextTo, RequiredParams, R>);
171
174
  get routes(): Set<R>;
172
175
  private add;
173
- readonly merge: <PathPrefix extends `/${string}`, OtherRoutes extends AnyRoute>(pathPrefix: PathPrefix, other: Router<ContextFrom, unknown, OtherRoutes>) => Router<ContextFrom, ContextTo, Extract<R | PrefixRoutesPath<PathPrefix, Extract<OtherRoutes, AnyRoute>>, AnyRoute>>;
176
+ params: this extends Router<infer ContextFrom, infer ContextTo, infer Params extends Record<string, unknown>, infer R extends AnyRoute> ? [keyof Params] extends [never] ? <NextParams extends Record<string, unknown> = {}>(spec: {
177
+ [Key in keyof NextParams]: z.ZodType<NextParams[Key], ZodTypeDef, string>;
178
+ }) => Router<ContextFrom, ContextTo, NextParams, R> : 'You cannot define params() on a router that has already had params defined, as routes that already consume params can break.' : never;
179
+ readonly merge: <PathPrefix extends `/${string}`, NextRequiredParams extends Record<string, unknown>, OtherRoutes extends AnyRoute>(pathPrefix: keyof NextRequiredParams extends keyof ExtractRouteParams<PathPrefix> | keyof RequiredParams ? PathPrefix : `Missing ${Exclude<Extract<keyof NextRequiredParams, string>, keyof RequiredParams>}${string}`, other: Router<ContextFrom, unknown, NextRequiredParams, OtherRoutes>) => Router<ContextFrom, ContextTo, RequiredParams, Extract<R | PrefixRoutesPath<PathPrefix, Extract<OtherRoutes, AnyRoute>>, AnyRoute>>;
174
180
  protected static getFindRoute: <R_1>(routes: Map<KaitoMethod, Map<string, R_1>>) => (method: KaitoMethod, path: string) => {
175
181
  route?: never;
176
182
  params?: never;
@@ -187,16 +193,16 @@ declare class Router<ContextFrom, ContextTo, R extends AnyRoute> {
187
193
  description?: string;
188
194
  };
189
195
  servers?: Partial<Record<(`https://` | `http://`) | ({} & string), string>>;
190
- }) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Response, "/openapi.json", "GET", AnyQuery, unknown>>;
196
+ }) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Response, "/openapi.json", RequiredParams, "GET", {}, never>>;
191
197
  private readonly method;
192
- get: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<Path, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, "GET", Query, Body>, "body" | "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, "GET", Query, Body>>;
193
- post: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<Path, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, "POST", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, "POST", Query, Body>>;
194
- put: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<Path, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, "PUT", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, "PUT", Query, Body>>;
195
- patch: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<Path, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, "PATCH", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, "PATCH", Query, Body>>;
196
- delete: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<Path, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, "DELETE", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, "DELETE", Query, Body>>;
197
- head: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<Path, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, "HEAD", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, "HEAD", Query, Body>>;
198
- options: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<Path, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, "OPTIONS", Query, Body>, "path" | "method" | "through">) => Router<ContextFrom, ContextTo, R | Route<ContextTo, Result, Path, "OPTIONS", Query, Body>>;
199
- through: <NextContext>(through: (context: ContextTo) => MaybePromise<NextContext>) => Router<ContextFrom, NextContext, R>;
198
+ get: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "GET", Query, Body>, "body" | "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "GET", Query, Body>>;
199
+ post: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "POST", Query, Body>, "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "POST", Query, Body>>;
200
+ put: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "PUT", Query, Body>, "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "PUT", Query, Body>>;
201
+ patch: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "PATCH", Query, Body>, "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "PATCH", Query, Body>>;
202
+ delete: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "DELETE", Query, Body>, "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "DELETE", Query, Body>>;
203
+ head: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "HEAD", Query, Body>, "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "HEAD", Query, Body>>;
204
+ options: <Result, Path extends string, Query extends AnyQuery = {}, Body = never>(path: Path, route: ((data: RouteRunData<ExtractRouteParams<Path> & RequiredParams, ContextTo, Query, Body>) => Result | Promise<Result>) | Omit<Route<ContextTo, Result, Path, RequiredParams, "OPTIONS", Query, Body>, "path" | "method" | "router">) => Router<ContextFrom, ContextTo, RequiredParams, R | Route<ContextTo, Result, Path, RequiredParams, "OPTIONS", Query, Body>>;
205
+ through: <NextContext>(through: (context: ContextTo, params: RequiredParams) => MaybePromise<NextContext>) => Router<ContextFrom, NextContext, RequiredParams, R>;
200
206
  }
201
207
 
202
208
  type KaitoConfig<ContextFrom> = {
@@ -265,6 +271,6 @@ type KaitoConfig<ContextFrom> = {
265
271
  * @param config - The configuration for the router
266
272
  * @returns A new Kaito router
267
273
  */
268
- declare function create<Context = null>(config?: KaitoConfig<Context>): () => Router<Context, Context, never>;
274
+ declare function create<Context = null>(config?: KaitoConfig<Context>): Router<Context, Context, {}, never>;
269
275
 
270
- export { type APIResponse, type AnyQuery, type AnyResponse, type AnyRoute, type ErroredAPIResponse, type ExtractRouteParams, type GetContext, type InferRoutes, type JSONOutputSpec, type KaitoConfig, KaitoError, type KaitoMethod, KaitoRequest, type MakeOptional, type MaybePromise, type OutputSpec, type Route, type RouteRunData, Router, type RouterState, type SSEOutputSpec, type SuccessfulAPIResponse, type Through, WrappedError, create, isNodeLikeDev };
276
+ export { type APIResponse, type AnyQuery, type AnyResponse, type AnyRoute, type ErroredAPIResponse, type ExtractRouteParams, type GetContext, type InferRoutes, type JSONOutputSpec, type KaitoConfig, KaitoError, KaitoHead, type KaitoMethod, KaitoRequest, type MakeOptional, type MaybePromise, type NotReadonly, type OutputSpec, type Route, type RouteRunData, Router, type RouterState, type SSEOutputSpec, type SuccessfulAPIResponse, type Through, WrappedError, create, isNodeLikeDev };
package/dist/index.js CHANGED
@@ -1,6 +1,9 @@
1
1
  // src/router/router.ts
2
2
  import { z } from "zod";
3
- import { createDocument } from "zod-openapi";
3
+ import {
4
+ createDocument
5
+ } from "zod-openapi";
6
+ import "zod-openapi/extend";
4
7
 
5
8
  // src/error.ts
6
9
  var WrappedError = class _WrappedError extends Error {
@@ -117,7 +120,8 @@ var Router = class _Router {
117
120
  static create = (config) => new _Router({
118
121
  through: async (context) => context,
119
122
  routes: /* @__PURE__ */ new Set(),
120
- config
123
+ config,
124
+ paramsSchema: null
121
125
  });
122
126
  constructor(state) {
123
127
  this.state = state;
@@ -130,17 +134,24 @@ var Router = class _Router {
130
134
  ...typeof route === "object" ? route : { run: route },
131
135
  method,
132
136
  path,
133
- through: this.state.through
137
+ router: this
134
138
  };
135
139
  return new _Router({
136
140
  ...this.state,
137
141
  routes: /* @__PURE__ */ new Set([...this.state.routes, merged])
138
142
  });
139
143
  };
144
+ params = (spec) => new _Router({
145
+ ...this.state,
146
+ paramsSchema: z.object(spec)
147
+ });
140
148
  merge = (pathPrefix, other) => {
141
149
  const newRoutes = [...other.state.routes].map((route) => ({
142
150
  ...route,
143
- path: `${pathPrefix}${route.path}`
151
+ // handle pathPrefix = / & route.path = / case causing //
152
+ // we intentionally are replacing on the joining path and not the pathPrefix, in case of
153
+ // /named -> merged to -> / causing /named/ not /named
154
+ path: `${pathPrefix}${route.path === "/" ? "" : route.path}`
144
155
  }));
145
156
  return new _Router({
146
157
  ...this.state,
@@ -197,7 +208,7 @@ var Router = class _Router {
197
208
  const handle = async (req) => {
198
209
  const url = new URL(req.url);
199
210
  const method = req.method;
200
- const { route, params } = findRoute(method, url.pathname);
211
+ const { route, params: rawParams } = findRoute(method, url.pathname);
201
212
  if (!route) {
202
213
  const body = {
203
214
  success: false,
@@ -211,7 +222,11 @@ var Router = class _Router {
211
222
  try {
212
223
  const body = route.body ? await route.body.parseAsync(await req.json()) : void 0;
213
224
  const query = route.fastQuerySchema ? await route.fastQuerySchema.parseAsync(url.searchParams) : {};
214
- const ctx = await route.through(await this.state.config.getContext?.(request, head) ?? null);
225
+ const params = route.router.state.paramsSchema ? route.router.state.paramsSchema.parse(rawParams) : rawParams;
226
+ const ctx = await route.router.state.through(
227
+ await this.state.config.getContext?.(request, head) ?? null,
228
+ params
229
+ );
215
230
  const result = await route.run({
216
231
  ctx,
217
232
  body,
@@ -298,23 +313,35 @@ var Router = class _Router {
298
313
  const paths = {};
299
314
  for (const route of this.state.routes) {
300
315
  const path = route.path;
316
+ if (!route.openapi) {
317
+ continue;
318
+ }
301
319
  const pathWithColonParamsReplaceWithCurlyBraces = path.replace(/:(\w+)/g, "{$1}");
302
320
  if (!paths[pathWithColonParamsReplaceWithCurlyBraces]) {
303
321
  paths[pathWithColonParamsReplaceWithCurlyBraces] = {};
304
322
  }
323
+ const content = route.openapi.body.type === "json" ? {
324
+ "application/json": {
325
+ schema: z.object({
326
+ success: z.literal(true).openapi({
327
+ type: "boolean",
328
+ enum: [true]
329
+ // Need this as zod-openapi doesn't properly work with literals
330
+ }),
331
+ data: route.openapi.body.schema
332
+ })
333
+ }
334
+ } : {
335
+ "text/event-stream": {
336
+ schema: route.openapi.body.schema
337
+ }
338
+ };
305
339
  const item = {
306
340
  description: route.openapi?.description ?? "Successful response",
307
341
  responses: {
308
342
  200: {
309
343
  description: route.openapi?.description ?? "Successful response",
310
- ...route.openapi ? {
311
- content: {
312
- [{
313
- json: "application/json",
314
- sse: "text/event-stream"
315
- }[route.openapi.body.type]]: { schema: route.openapi?.body.schema }
316
- }
317
- } : {}
344
+ content
318
345
  }
319
346
  }
320
347
  };
@@ -352,10 +379,10 @@ var Router = class _Router {
352
379
  };
353
380
  })
354
381
  });
355
- return this.add("GET", "/openapi.json", async () => Response.json(doc));
382
+ return this.get("/openapi.json", () => Response.json(doc));
356
383
  };
357
- method = (method) => (path, route) => {
358
- return this.add(method, path, route);
384
+ method = (method) => {
385
+ return (path, route) => this.add(method, path, route);
359
386
  };
360
387
  get = this.method("GET");
361
388
  post = this.method("POST");
@@ -367,17 +394,18 @@ var Router = class _Router {
367
394
  through = (through) => {
368
395
  return new _Router({
369
396
  ...this.state,
370
- through: async (context) => await through(await this.state.through(context))
397
+ through: async (context, params) => await through(await this.state.through(context, params), params)
371
398
  });
372
399
  };
373
400
  };
374
401
 
375
402
  // src/create.ts
376
403
  function create(config = {}) {
377
- return () => Router.create(config);
404
+ return Router.create(config);
378
405
  }
379
406
  export {
380
407
  KaitoError,
408
+ KaitoHead,
381
409
  KaitoRequest,
382
410
  Router,
383
411
  WrappedError,
@@ -38,6 +38,9 @@ var KaitoSSEResponse = class extends Response {
38
38
  headers
39
39
  });
40
40
  }
41
+ get [Symbol.toStringTag]() {
42
+ return "KaitoSSEResponse";
43
+ }
41
44
  };
42
45
  function sseEventToString(event) {
43
46
  let result = "";
@@ -1,5 +1,6 @@
1
1
  declare class KaitoSSEResponse<_T> extends Response {
2
2
  constructor(body: ReadableStream<string>, init?: ResponseInit);
3
+ get [Symbol.toStringTag](): string;
3
4
  }
4
5
  type SSEEvent<T, E extends string> = ({
5
6
  data: T;
@@ -1,5 +1,6 @@
1
1
  declare class KaitoSSEResponse<_T> extends Response {
2
2
  constructor(body: ReadableStream<string>, init?: ResponseInit);
3
+ get [Symbol.toStringTag](): string;
3
4
  }
4
5
  type SSEEvent<T, E extends string> = ({
5
6
  data: T;
@@ -10,6 +10,9 @@ var KaitoSSEResponse = class extends Response {
10
10
  headers
11
11
  });
12
12
  }
13
+ get [Symbol.toStringTag]() {
14
+ return "KaitoSSEResponse";
15
+ }
13
16
  };
14
17
  function sseEventToString(event) {
15
18
  let result = "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaito-http/core",
3
- "version": "4.0.0-beta.1",
3
+ "version": "4.0.0-beta.10",
4
4
  "author": "Alistair Smith <hi@alistair.sh>",
5
5
  "repository": "https://github.com/kaito-http/kaito",
6
6
  "devDependencies": {