@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.
- package/dist/cors/cors.cjs +85 -17
- package/dist/cors/cors.d.cts +122 -26
- package/dist/cors/cors.d.ts +122 -26
- package/dist/cors/cors.js +85 -17
- package/dist/index.cjs +45 -18
- package/dist/index.d.cts +36 -30
- package/dist/index.d.ts +36 -30
- package/dist/index.js +47 -19
- package/dist/stream/stream.cjs +3 -0
- package/dist/stream/stream.d.cts +1 -0
- package/dist/stream/stream.d.ts +1 -0
- package/dist/stream/stream.js +3 -0
- package/package.json +1 -1
package/dist/cors/cors.cjs
CHANGED
|
@@ -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(
|
|
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.
|
|
33
|
-
const
|
|
34
|
-
|
|
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
|
|
37
|
-
return
|
|
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(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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:
|
package/dist/cors/cors.d.cts
CHANGED
|
@@ -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
|
|
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
|
|
8
|
-
*
|
|
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
|
-
*
|
|
15
|
-
* '
|
|
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
|
|
21
|
-
*
|
|
22
|
-
*
|
|
43
|
+
* // Exact matches
|
|
44
|
+
* matcher('https://example.com'); // true
|
|
45
|
+
* matcher('http://example.com'); // true
|
|
23
46
|
*
|
|
24
|
-
* //
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
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(
|
|
56
|
+
declare function experimental_createOriginMatcher(originIterator: Iterable<string>): (origin: string) => boolean;
|
|
32
57
|
/**
|
|
33
|
-
* Create a
|
|
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
|
|
38
|
-
*
|
|
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
|
|
42
|
-
*
|
|
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
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
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(
|
|
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.d.ts
CHANGED
|
@@ -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
|
|
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
|
|
8
|
-
*
|
|
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
|
-
*
|
|
15
|
-
* '
|
|
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
|
|
21
|
-
*
|
|
22
|
-
*
|
|
43
|
+
* // Exact matches
|
|
44
|
+
* matcher('https://example.com'); // true
|
|
45
|
+
* matcher('http://example.com'); // true
|
|
23
46
|
*
|
|
24
|
-
* //
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
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(
|
|
56
|
+
declare function experimental_createOriginMatcher(originIterator: Iterable<string>): (origin: string) => boolean;
|
|
32
57
|
/**
|
|
33
|
-
* Create a
|
|
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
|
|
38
|
-
*
|
|
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
|
|
42
|
-
*
|
|
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
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
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(
|
|
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(
|
|
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.
|
|
8
|
-
const
|
|
9
|
-
|
|
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
|
|
12
|
-
return
|
|
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(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
412
|
+
return this.get("/openapi.json", () => Response.json(doc));
|
|
387
413
|
};
|
|
388
|
-
method = (method) =>
|
|
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
|
|
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<
|
|
114
|
+
type RouteRunData<Params, Context, QueryOutput, BodyOutput> = {
|
|
113
115
|
ctx: Context;
|
|
114
116
|
body: BodyOutput;
|
|
115
117
|
query: QueryOutput;
|
|
116
|
-
params:
|
|
118
|
+
params: Params;
|
|
117
119
|
};
|
|
118
120
|
type AnyQuery = {
|
|
119
121
|
[key in string]: any;
|
|
120
122
|
};
|
|
121
|
-
type Through<From, 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
|
-
|
|
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
|
|
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
|
-
|
|
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",
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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>):
|
|
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<
|
|
114
|
+
type RouteRunData<Params, Context, QueryOutput, BodyOutput> = {
|
|
113
115
|
ctx: Context;
|
|
114
116
|
body: BodyOutput;
|
|
115
117
|
query: QueryOutput;
|
|
116
|
-
params:
|
|
118
|
+
params: Params;
|
|
117
119
|
};
|
|
118
120
|
type AnyQuery = {
|
|
119
121
|
[key in string]: any;
|
|
120
122
|
};
|
|
121
|
-
type Through<From, 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
|
-
|
|
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
|
|
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
|
-
|
|
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",
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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" | "
|
|
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>):
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
382
|
+
return this.get("/openapi.json", () => Response.json(doc));
|
|
356
383
|
};
|
|
357
|
-
method = (method) =>
|
|
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
|
|
404
|
+
return Router.create(config);
|
|
378
405
|
}
|
|
379
406
|
export {
|
|
380
407
|
KaitoError,
|
|
408
|
+
KaitoHead,
|
|
381
409
|
KaitoRequest,
|
|
382
410
|
Router,
|
|
383
411
|
WrappedError,
|
package/dist/stream/stream.cjs
CHANGED
package/dist/stream/stream.d.cts
CHANGED
package/dist/stream/stream.d.ts
CHANGED
package/dist/stream/stream.js
CHANGED