@pikku/core 0.6.20 → 0.6.22
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/CHANGELOG.md +12 -0
- package/dist/channel/channel-handler.d.ts +2 -2
- package/dist/channel/channel-handler.js +49 -26
- package/dist/channel/channel-runner.d.ts +3 -3
- package/dist/channel/channel-runner.js +4 -4
- package/dist/channel/channel.types.d.ts +12 -19
- package/dist/channel/local/local-channel-runner.d.ts +1 -1
- package/dist/channel/local/local-channel-runner.js +14 -8
- package/dist/channel/serverless/serverless-channel-runner.js +12 -7
- package/dist/handle-error.js +8 -6
- package/dist/http/http-route-runner.d.ts +75 -18
- package/dist/http/http-route-runner.js +159 -64
- package/dist/http/http-routes.types.d.ts +24 -9
- package/dist/http/incomingmessage-to-request-convertor.d.ts +1 -0
- package/dist/http/incomingmessage-to-request-convertor.js +1 -0
- package/dist/http/index.d.ts +4 -3
- package/dist/http/index.js +4 -3
- package/dist/http/{pikku-http-abstract-request.d.ts → pikku-fetch-http-request.d.ts} +14 -20
- package/dist/http/pikku-fetch-http-request.js +92 -0
- package/dist/http/pikku-fetch-http-response.d.ts +14 -0
- package/dist/http/pikku-fetch-http-response.js +59 -0
- package/dist/middleware/auth-apikey.js +4 -4
- package/dist/middleware/auth-bearer.js +4 -4
- package/dist/middleware/auth-cookie.js +16 -25
- package/dist/middleware-runner.d.ts +1 -1
- package/dist/pikku-request.d.ts +3 -3
- package/dist/pikku-request.js +5 -5
- package/dist/scheduler/scheduler-runner.d.ts +1 -1
- package/dist/services/user-session-service.d.ts +9 -14
- package/dist/services/user-session-service.js +22 -22
- package/dist/types/core.types.d.ts +1 -1
- package/dist/types/functions.types.d.ts +7 -2
- package/lcov.info +1541 -1413
- package/package.json +3 -4
- package/src/channel/channel-handler.ts +82 -29
- package/src/channel/channel-runner.ts +6 -6
- package/src/channel/channel.types.ts +19 -23
- package/src/channel/local/local-channel-runner.test.ts +68 -23
- package/src/channel/local/local-channel-runner.ts +23 -9
- package/src/channel/serverless/serverless-channel-runner.ts +15 -6
- package/src/handle-error.ts +8 -6
- package/src/http/http-route-runner.test.ts +13 -57
- package/src/http/http-route-runner.ts +189 -92
- package/src/http/http-routes.types.ts +26 -9
- package/src/http/incomingmessage-to-request-convertor.ts +0 -0
- package/src/http/index.ts +4 -5
- package/src/http/{pikku-http-abstract-request.ts → pikku-fetch-http-request.ts} +48 -39
- package/src/http/pikku-fetch-http-response.ts +70 -0
- package/src/middleware/auth-apikey.ts +4 -4
- package/src/middleware/auth-bearer.ts +4 -4
- package/src/middleware/auth-cookie.ts +20 -28
- package/src/middleware-runner.ts +1 -1
- package/src/pikku-request.ts +8 -4
- package/src/scheduler/scheduler-runner.ts +0 -2
- package/src/services/user-session-service.ts +26 -28
- package/src/types/core.types.ts +1 -1
- package/src/types/functions.types.ts +19 -4
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/http/pikku-http-abstract-request.js +0 -76
- package/dist/http/pikku-http-abstract-response.d.ts +0 -58
- package/dist/http/pikku-http-abstract-response.js +0 -54
- package/src/http/pikku-http-abstract-response.ts +0 -79
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
import { verifyPermissions } from '../permissions.js';
|
|
2
2
|
import { match } from 'path-to-regexp';
|
|
3
|
-
import { PikkuHTTPAbstractRequest } from './pikku-http-abstract-request.js';
|
|
4
|
-
import { PikkuHTTPAbstractResponse } from './pikku-http-abstract-response.js';
|
|
5
3
|
import { ForbiddenError, MissingSessionError, NotFoundError, } from '../errors/errors.js';
|
|
6
4
|
import { closeSessionServices } from '../utils.js';
|
|
7
5
|
import { coerceQueryStringToArray, validateSchema } from '../schema.js';
|
|
8
|
-
import {
|
|
6
|
+
import { PikkuUserSessionService } from '../services/user-session-service.js';
|
|
9
7
|
import { runMiddleware } from '../middleware-runner.js';
|
|
10
8
|
import { handleError } from '../handle-error.js';
|
|
11
9
|
import { pikkuState } from '../pikku-state.js';
|
|
10
|
+
import { PikkuFetchHTTPResponse } from './pikku-fetch-http-response.js';
|
|
11
|
+
import { PikkuFetchHTTPRequest } from './pikku-fetch-http-request.js';
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* Registers middleware either globally or for a specific route.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* When a string route pattern is provided along with middleware, the middleware
|
|
16
|
+
* is applied only to that route. Otherwise, if an array is provided, it is treated
|
|
17
|
+
* as global middleware (applied to all routes).
|
|
18
|
+
*
|
|
19
|
+
* @template APIMiddleware The middleware type.
|
|
20
|
+
* @param {APIMiddleware[] | string} routeOrMiddleware - Either a global middleware array or a route pattern string.
|
|
21
|
+
* @param {APIMiddleware[]} [middleware] - The middleware array to apply when a route pattern is specified.
|
|
17
22
|
*/
|
|
18
23
|
export const addMiddleware = (routeOrMiddleware, middleware) => {
|
|
19
24
|
if (typeof routeOrMiddleware === 'string' && middleware) {
|
|
@@ -30,42 +35,56 @@ export const addMiddleware = (routeOrMiddleware, middleware) => {
|
|
|
30
35
|
}
|
|
31
36
|
};
|
|
32
37
|
/**
|
|
33
|
-
*
|
|
38
|
+
* Adds a new route to the global HTTP route registry.
|
|
39
|
+
*
|
|
40
|
+
* The route configuration includes the HTTP method, route path, permissions,
|
|
41
|
+
* middleware, and the handler function that implements the route's logic.
|
|
34
42
|
*
|
|
35
|
-
* @
|
|
43
|
+
* @template In Expected input type.
|
|
44
|
+
* @template Out Expected output type.
|
|
45
|
+
* @template Route Route pattern as a string.
|
|
46
|
+
* @template APIFunction Type for the route handler function.
|
|
47
|
+
* @template APIFunctionSessionless Type for a sessionless handler.
|
|
48
|
+
* @template APIPermission Type representing required permissions.
|
|
49
|
+
* @template APIMiddleware Middleware type to be used with the route.
|
|
50
|
+
* @param {CoreHTTPFunctionRoute<In, Out, Route, APIFunction, APIFunctionSessionless, APIPermission, APIMiddleware>} route - The route configuration object.
|
|
36
51
|
*/
|
|
37
52
|
export const addRoute = (route) => {
|
|
38
53
|
pikkuState('http', 'routes').push(route);
|
|
39
54
|
};
|
|
40
55
|
/**
|
|
41
|
-
*
|
|
56
|
+
* Finds a matching route based on the HTTP method and URL path.
|
|
57
|
+
*
|
|
58
|
+
* Iterates over all registered routes, skipping those with a mismatched method.
|
|
59
|
+
* When a route pattern matches the incoming request path, this function aggregates
|
|
60
|
+
* any global middleware along with route-specific middleware and identifies any input schema.
|
|
42
61
|
*
|
|
43
|
-
* @param {string} requestType - HTTP method (GET, POST
|
|
44
|
-
* @param {string} requestPath - URL path
|
|
45
|
-
* @returns {Object | undefined}
|
|
62
|
+
* @param {string} requestType - The HTTP method (e.g., GET, POST).
|
|
63
|
+
* @param {string} requestPath - The URL path of the incoming request.
|
|
64
|
+
* @returns {Object | undefined} An object with matched route details or undefined if no match.
|
|
46
65
|
*/
|
|
47
66
|
const getMatchingRoute = (requestType, requestPath) => {
|
|
48
67
|
const routes = pikkuState('http', 'routes');
|
|
49
68
|
const middleware = pikkuState('http', 'middleware');
|
|
50
69
|
const routesMeta = pikkuState('http', 'meta');
|
|
51
70
|
for (const route of routes) {
|
|
52
|
-
// Skip routes that don't match the
|
|
71
|
+
// Skip routes that don't match the HTTP method
|
|
53
72
|
if (route.method !== requestType.toLowerCase()) {
|
|
54
73
|
continue;
|
|
55
74
|
}
|
|
56
|
-
//
|
|
75
|
+
// Generate a matching function from the route pattern
|
|
57
76
|
const matchFunc = match(`/${route.route}`.replace(/^\/\//, '/'), {
|
|
58
77
|
decode: decodeURIComponent,
|
|
59
78
|
});
|
|
60
|
-
//
|
|
79
|
+
// Attempt to match the request path
|
|
61
80
|
const matchedPath = matchFunc(requestPath.replace(/^\/\//, '/'));
|
|
62
81
|
if (matchedPath) {
|
|
63
|
-
//
|
|
82
|
+
// Aggregate global and route-specific middleware
|
|
64
83
|
const globalMiddleware = middleware
|
|
65
84
|
.filter((m) => m.route === '*' || new RegExp(m.route).test(route.route))
|
|
66
85
|
.map((m) => m.middleware)
|
|
67
86
|
.flat();
|
|
68
|
-
//
|
|
87
|
+
// Extract associated schema information if available
|
|
69
88
|
const schemaName = routesMeta.find((routeMeta) => routeMeta.method === route.method && routeMeta.route === route.route)?.input;
|
|
70
89
|
return {
|
|
71
90
|
matchedPath,
|
|
@@ -79,47 +98,61 @@ const getMatchingRoute = (requestType, requestPath) => {
|
|
|
79
98
|
return undefined;
|
|
80
99
|
};
|
|
81
100
|
/**
|
|
82
|
-
*
|
|
101
|
+
* Combines the request and response objects into a single HTTP interaction object.
|
|
83
102
|
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
103
|
+
* This utility function creates an object that holds both the HTTP request and response,
|
|
104
|
+
* which simplifies passing these around through middleware and route execution.
|
|
105
|
+
*
|
|
106
|
+
* @param {PikkuHTTPRequest | undefined} request - The HTTP request object.
|
|
107
|
+
* @param {PikkuHTTPResponse | undefined} response - The HTTP response object.
|
|
108
|
+
* @returns {PikkuHTTP | undefined} The combined HTTP interaction object or undefined if none provided.
|
|
87
109
|
*/
|
|
88
110
|
export const createHTTPInteraction = (request, response) => {
|
|
89
111
|
let http = undefined;
|
|
90
|
-
if (request
|
|
91
|
-
response instanceof PikkuHTTPAbstractResponse) {
|
|
112
|
+
if (request || response) {
|
|
92
113
|
http = {};
|
|
93
|
-
if (request
|
|
114
|
+
if (request) {
|
|
94
115
|
http.request = request;
|
|
95
116
|
}
|
|
96
|
-
if (response
|
|
117
|
+
if (response) {
|
|
97
118
|
http.response = response;
|
|
98
119
|
}
|
|
99
120
|
}
|
|
100
121
|
return http;
|
|
101
122
|
};
|
|
102
123
|
/**
|
|
103
|
-
*
|
|
124
|
+
* Validates the input data and executes the route handler with associated middleware.
|
|
125
|
+
*
|
|
126
|
+
* This function is the central execution point for a route. It performs these steps:
|
|
127
|
+
* 1. Sets URL parameters on the request.
|
|
128
|
+
* 2. Validates the user session if required.
|
|
129
|
+
* 3. Creates session-specific services.
|
|
130
|
+
* 4. Validates the incoming data against a schema.
|
|
131
|
+
* 5. Optionally coerces query string values to arrays.
|
|
132
|
+
* 6. Checks route-specific permissions.
|
|
133
|
+
* 7. Executes the route handler.
|
|
134
|
+
* 8. Sends the appropriate response.
|
|
104
135
|
*
|
|
105
|
-
* @param {Object} services -
|
|
106
|
-
* @param {Object} matchedRoute -
|
|
107
|
-
* @param {PikkuHTTP | undefined} http - HTTP interaction object
|
|
108
|
-
* @param {Object} options -
|
|
109
|
-
* @returns {Promise<any>}
|
|
136
|
+
* @param {Object} services - A collection of shared services and utilities.
|
|
137
|
+
* @param {Object} matchedRoute - Contains route details, URL parameters, middleware, and optional schema.
|
|
138
|
+
* @param {PikkuHTTP | undefined} http - The HTTP interaction object.
|
|
139
|
+
* @param {Object} options - Options for route execution (e.g., whether to coerce query strings to arrays).
|
|
140
|
+
* @returns {Promise<any>} An object containing the route handler result and session services (if any).
|
|
141
|
+
* @throws Throws errors like MissingSessionError or ForbiddenError on validation failures.
|
|
110
142
|
*/
|
|
111
143
|
const executeRouteWithMiddleware = async (services, matchedRoute, http, options) => {
|
|
112
144
|
const { matchedPath, params, route, middleware, schemaName } = matchedRoute;
|
|
113
|
-
const { singletonServices, userSessionService,
|
|
145
|
+
const { singletonServices, userSessionService, createSessionServices, skipUserSession, } = services;
|
|
114
146
|
const requiresSession = route.auth !== false;
|
|
115
147
|
let sessionServices;
|
|
116
148
|
let result;
|
|
149
|
+
// Attach URL parameters to the request object
|
|
117
150
|
http?.request?.setParams(params);
|
|
118
151
|
singletonServices.logger.info(`Matched route: ${route.route} | method: ${route.method.toUpperCase()} | auth: ${requiresSession.toString()}`);
|
|
119
|
-
// Main route execution
|
|
152
|
+
// Main route execution logic wrapped for middleware handling
|
|
120
153
|
const runMain = async () => {
|
|
121
154
|
const session = userSessionService.get();
|
|
122
|
-
//
|
|
155
|
+
// Ensure session is available when required
|
|
123
156
|
if (skipUserSession && requiresSession) {
|
|
124
157
|
throw new Error("Can't skip trying to get user session if auth is required");
|
|
125
158
|
}
|
|
@@ -130,54 +163,116 @@ const executeRouteWithMiddleware = async (services, matchedRoute, http, options)
|
|
|
130
163
|
});
|
|
131
164
|
throw new MissingSessionError();
|
|
132
165
|
}
|
|
133
|
-
// Create session services
|
|
134
|
-
sessionServices = await createSessionServices({ ...singletonServices,
|
|
135
|
-
const allServices = {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
166
|
+
// Create session-specific services for handling the request
|
|
167
|
+
sessionServices = await createSessionServices({ ...singletonServices, userSessionService }, { http }, session);
|
|
168
|
+
const allServices = {
|
|
169
|
+
...singletonServices,
|
|
170
|
+
...sessionServices,
|
|
171
|
+
http,
|
|
172
|
+
};
|
|
173
|
+
const data = await http?.request?.data();
|
|
174
|
+
// Validate request data against the defined schema, if any
|
|
175
|
+
try {
|
|
176
|
+
console.log(schemaName, data);
|
|
177
|
+
validateSchema(singletonServices.logger, singletonServices.schemaService, schemaName, data);
|
|
178
|
+
}
|
|
179
|
+
catch (e) {
|
|
180
|
+
// TODO: Implement proper handling for schema validation failures
|
|
181
|
+
}
|
|
182
|
+
// Coerce query string parameters to arrays if specified by the schema
|
|
139
183
|
if (options.coerceToArray && schemaName) {
|
|
140
184
|
coerceQueryStringToArray(schemaName, data);
|
|
141
185
|
}
|
|
142
|
-
//
|
|
186
|
+
// Execute permission checks
|
|
143
187
|
const permissioned = await verifyPermissions(route.permissions, allServices, data, session);
|
|
144
188
|
if (permissioned === false) {
|
|
145
189
|
throw new ForbiddenError('Permission denied');
|
|
146
190
|
}
|
|
147
|
-
//
|
|
191
|
+
// Invoke the actual route handler function
|
|
148
192
|
result = await route.func(allServices, data, session);
|
|
149
|
-
//
|
|
193
|
+
// Respond with either a binary or JSON response based on configuration
|
|
150
194
|
if (route.returnsJSON === false) {
|
|
151
|
-
http?.response?.
|
|
195
|
+
http?.response?.arrayBuffer(result);
|
|
152
196
|
}
|
|
153
197
|
else {
|
|
154
|
-
http?.response?.
|
|
198
|
+
http?.response?.json(result);
|
|
155
199
|
}
|
|
156
|
-
http?.response?.
|
|
157
|
-
|
|
200
|
+
http?.response?.status(200);
|
|
201
|
+
// TODO: Evaluate if the response stream should be explicitly ended.
|
|
202
|
+
// http?.response?.end()
|
|
158
203
|
return result;
|
|
159
204
|
};
|
|
160
|
-
|
|
205
|
+
// Execute middleware, then run the main logic
|
|
206
|
+
await runMiddleware({ ...singletonServices, userSessionService }, { http }, middleware, runMain);
|
|
161
207
|
return sessionServices ? { result, sessionServices } : { result };
|
|
162
208
|
};
|
|
163
209
|
/**
|
|
164
|
-
*
|
|
210
|
+
* Executes an HTTP route for a given Fetch API request.
|
|
211
|
+
*
|
|
212
|
+
* This function wraps the entire lifecycle of handling an HTTP request:
|
|
213
|
+
* - Matching the request to a registered route.
|
|
214
|
+
* - Validating input and session state.
|
|
215
|
+
* - Running middleware and the route handler.
|
|
216
|
+
* - Handling errors and forming the response.
|
|
217
|
+
*
|
|
218
|
+
* @template In Expected input data type.
|
|
219
|
+
* @template Out Expected output data type.
|
|
220
|
+
* @param {Request} request - The native Fetch API Request object.
|
|
221
|
+
* @param {RunRouteOptions & RunRouteParams} params - Additional options including services and session management.
|
|
222
|
+
* @returns {Promise<Response>} A promise that resolves to a Fetch API Response object.
|
|
223
|
+
*/
|
|
224
|
+
export const fetch = async (request, params) => {
|
|
225
|
+
const pikkuResponse = new PikkuFetchHTTPResponse();
|
|
226
|
+
await fetchData(request, pikkuResponse, params);
|
|
227
|
+
return pikkuResponse.toResponse();
|
|
228
|
+
};
|
|
229
|
+
/**
|
|
230
|
+
* Executes an HTTP route using a Pikku-specific request wrapper.
|
|
231
|
+
*
|
|
232
|
+
* This variant accepts either a native Request or a PikkuHTTPRequest object and returns
|
|
233
|
+
* a PikkuFetchHTTPResponse for further manipulation if needed.
|
|
234
|
+
*
|
|
235
|
+
* @template In Expected input data type.
|
|
236
|
+
* @template Out Expected output data type.
|
|
237
|
+
* @param {Request | PikkuHTTPRequest} request - The request object.
|
|
238
|
+
* @param {RunRouteOptions & RunRouteParams} params - Execution options including services and session configuration.
|
|
239
|
+
* @returns {Promise<PikkuFetchHTTPResponse>} A promise that resolves to a PikkuFetchHTTPResponse object.
|
|
240
|
+
*/
|
|
241
|
+
export const pikkuFetch = async (request, params) => {
|
|
242
|
+
const pikkuResponse = new PikkuFetchHTTPResponse();
|
|
243
|
+
await fetchData(request, pikkuResponse, params);
|
|
244
|
+
return pikkuResponse;
|
|
245
|
+
};
|
|
246
|
+
/**
|
|
247
|
+
* Core function to process an HTTP request through route matching, validation,
|
|
248
|
+
* middleware execution, error handling, and session service cleanup.
|
|
249
|
+
*
|
|
250
|
+
* This function does the following:
|
|
251
|
+
* - Wraps the incoming request and response into an HTTP interaction object.
|
|
252
|
+
* - Determines the correct route based on HTTP method and path.
|
|
253
|
+
* - Executes middleware and the route handler.
|
|
254
|
+
* - Catches and handles errors, optionally bubbling them if configured.
|
|
255
|
+
* - Cleans up any session services created during processing.
|
|
165
256
|
*
|
|
166
|
-
* @
|
|
167
|
-
* @
|
|
168
|
-
* @
|
|
257
|
+
* @template In Expected input data type.
|
|
258
|
+
* @template Out Expected output data type.
|
|
259
|
+
* @param {Request | PikkuHTTPRequest} request - The incoming HTTP request.
|
|
260
|
+
* @param {PikkuHTTPResponse} response - The response object to be populated.
|
|
261
|
+
* @param {RunRouteOptions & RunRouteParams} options - Options such as singleton services, session handling, and error configuration.
|
|
262
|
+
* @returns {Promise<Out | void>} The output from the route handler or void if an error occurred.
|
|
169
263
|
*/
|
|
170
|
-
export const
|
|
171
|
-
const
|
|
172
|
-
const userSessionService = new LocalUserSessionService();
|
|
264
|
+
export const fetchData = async (request, response, { singletonServices, createSessionServices, skipUserSession = false, respondWith404 = true, logWarningsForStatusCodes = [], coerceToArray = false, bubbleErrors = false, }) => {
|
|
265
|
+
const userSessionService = new PikkuUserSessionService();
|
|
173
266
|
let sessionServices;
|
|
174
267
|
let result;
|
|
175
|
-
//
|
|
176
|
-
const http = createHTTPInteraction(request, response);
|
|
177
|
-
|
|
268
|
+
// Combine the request and response into one interaction object
|
|
269
|
+
const http = createHTTPInteraction(request instanceof Request ? new PikkuFetchHTTPRequest(request) : request, response);
|
|
270
|
+
const apiType = http.request.method();
|
|
271
|
+
const apiRoute = http.request.path();
|
|
272
|
+
// Locate the matching route based on the HTTP method and path
|
|
178
273
|
const matchedRoute = getMatchingRoute(apiType, apiRoute);
|
|
179
274
|
try {
|
|
180
|
-
//
|
|
275
|
+
// If no route matches, log the occurrence and throw a NotFoundError
|
|
181
276
|
if (!matchedRoute) {
|
|
182
277
|
singletonServices.logger.info({
|
|
183
278
|
message: 'Route not found',
|
|
@@ -186,23 +281,23 @@ export const runHTTPRoute = async ({ singletonServices, request, response, creat
|
|
|
186
281
|
});
|
|
187
282
|
throw new NotFoundError(`Route not found: ${apiRoute}`);
|
|
188
283
|
}
|
|
189
|
-
// Execute route with middleware
|
|
284
|
+
// Execute the matched route along with its middleware and session management
|
|
190
285
|
;
|
|
191
286
|
({ result, sessionServices } = await executeRouteWithMiddleware({
|
|
192
287
|
singletonServices,
|
|
193
288
|
userSessionService,
|
|
194
|
-
context,
|
|
195
289
|
createSessionServices,
|
|
196
290
|
skipUserSession,
|
|
197
291
|
}, matchedRoute, http, { coerceToArray }));
|
|
198
292
|
return result;
|
|
199
293
|
}
|
|
200
294
|
catch (e) {
|
|
201
|
-
// Handle and
|
|
202
|
-
handleError(e, http, context.get('trackingId'),
|
|
295
|
+
// Handle errors and, depending on configuration, bubble them up or respond with an error
|
|
296
|
+
handleError(e, http, '111', // TODO: context.get('trackingId'),
|
|
297
|
+
singletonServices.logger, logWarningsForStatusCodes, respondWith404, bubbleErrors);
|
|
203
298
|
}
|
|
204
299
|
finally {
|
|
205
|
-
// Clean up session services
|
|
300
|
+
// Clean up any session-specific services created during processing
|
|
206
301
|
if (sessionServices) {
|
|
207
302
|
await closeSessionServices(singletonServices.logger, sessionServices);
|
|
208
303
|
}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
+
import { SerializeOptions } from 'cookie';
|
|
1
2
|
import { PikkuError } from '../errors/error-handler.js';
|
|
2
3
|
import { APIDocs, CoreServices, CoreSingletonServices, CoreUserSession, CreateSessionServices, PikkuMiddleware } from '../types/core.types.js';
|
|
3
4
|
import { CoreAPIFunction, CoreAPIFunctionSessionless, CoreAPIPermission } from '../types/functions.types.js';
|
|
4
|
-
import { PikkuRequest } from '../pikku-request.js';
|
|
5
|
-
import { PikkuResponse } from '../pikku-response.js';
|
|
6
|
-
import { PikkuHTTPAbstractRequest } from './pikku-http-abstract-request.js';
|
|
7
|
-
import { PikkuHTTPAbstractResponse } from './pikku-http-abstract-response.js';
|
|
8
5
|
type ExtractRouteParams<S extends string> = S extends `${string}:${infer Param}/${infer Rest}` ? Param | ExtractRouteParams<`/${Rest}`> : S extends `${string}:${infer Param}` ? Param : never;
|
|
9
6
|
export type AssertRouteParams<In, Route extends string> = ExtractRouteParams<Route> extends keyof In ? unknown : ['Error: Route parameters', ExtractRouteParams<Route>, 'not in', keyof In];
|
|
10
7
|
export type RunRouteOptions = Partial<{
|
|
@@ -14,10 +11,8 @@ export type RunRouteOptions = Partial<{
|
|
|
14
11
|
coerceToArray: boolean;
|
|
15
12
|
bubbleErrors: boolean;
|
|
16
13
|
}>;
|
|
17
|
-
export type RunRouteParams
|
|
14
|
+
export type RunRouteParams = {
|
|
18
15
|
singletonServices: CoreSingletonServices;
|
|
19
|
-
request: PikkuRequest<In> | PikkuHTTPAbstractRequest<In>;
|
|
20
|
-
response?: PikkuResponse | PikkuHTTPAbstractResponse | undefined;
|
|
21
16
|
createSessionServices: CreateSessionServices<CoreSingletonServices, CoreServices<CoreSingletonServices>, CoreUserSession>;
|
|
22
17
|
};
|
|
23
18
|
/**
|
|
@@ -44,8 +39,8 @@ export type CoreHTTPFunction = {
|
|
|
44
39
|
* Represents a http interaction within Pikku, including a request and response.
|
|
45
40
|
*/
|
|
46
41
|
export interface PikkuHTTP {
|
|
47
|
-
request?:
|
|
48
|
-
response?:
|
|
42
|
+
request?: PikkuHTTPRequest;
|
|
43
|
+
response?: PikkuHTTPResponse;
|
|
49
44
|
}
|
|
50
45
|
/**
|
|
51
46
|
* Represents request headers as either a record or a function to get headers by name.
|
|
@@ -130,4 +125,24 @@ export type HTTPRouteMiddleware = {
|
|
|
130
125
|
route: string;
|
|
131
126
|
middleware: PikkuMiddleware[];
|
|
132
127
|
};
|
|
128
|
+
export interface PikkuHTTPRequest<In = unknown> {
|
|
129
|
+
method(): HTTPMethod;
|
|
130
|
+
path(): string;
|
|
131
|
+
data(): Promise<In>;
|
|
132
|
+
json(): Promise<unknown>;
|
|
133
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
134
|
+
header(headerName: string): string | null;
|
|
135
|
+
cookie(name?: string): string | null;
|
|
136
|
+
params(): Partial<Record<string, string | string[]>>;
|
|
137
|
+
setParams(params: Record<string, string | string[] | undefined>): void;
|
|
138
|
+
query(): PikkuQuery;
|
|
139
|
+
}
|
|
140
|
+
export interface PikkuHTTPResponse {
|
|
141
|
+
status(code: number): this;
|
|
142
|
+
cookie(name: string, value: string | null, options: SerializeOptions): this;
|
|
143
|
+
header(name: string, value: string | string[]): this;
|
|
144
|
+
arrayBuffer(data: XMLHttpRequestBodyInit): this;
|
|
145
|
+
json(data: unknown): this;
|
|
146
|
+
redirect(location: string, status?: number): this;
|
|
147
|
+
}
|
|
133
148
|
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/http/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export * from './pikku-http-
|
|
2
|
-
export * from './pikku-http-
|
|
1
|
+
export * from './pikku-fetch-http-request.js';
|
|
2
|
+
export * from './pikku-fetch-http-response.js';
|
|
3
|
+
export * from './incomingmessage-to-request-convertor.js';
|
|
3
4
|
export * from './log-http-routes.js';
|
|
4
|
-
export {
|
|
5
|
+
export { fetch, fetchData, addRoute } from './http-route-runner.js';
|
|
5
6
|
export type * from './http-routes.types.js';
|
package/dist/http/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export * from './pikku-http-
|
|
2
|
-
export * from './pikku-http-
|
|
1
|
+
export * from './pikku-fetch-http-request.js';
|
|
2
|
+
export * from './pikku-fetch-http-response.js';
|
|
3
|
+
export * from './incomingmessage-to-request-convertor.js';
|
|
3
4
|
export * from './log-http-routes.js';
|
|
4
|
-
export {
|
|
5
|
+
export { fetch, fetchData, addRoute } from './http-route-runner.js';
|
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { HTTPMethod, PikkuQuery } from './http-routes.types.js';
|
|
1
|
+
import { HTTPMethod, PikkuHTTPRequest, PikkuQuery } from './http-routes.types.js';
|
|
3
2
|
/**
|
|
4
3
|
* Abstract class representing a pikku request.
|
|
5
4
|
* @template In - The type of the request body.
|
|
6
5
|
* @group RequestResponse
|
|
7
6
|
*/
|
|
8
|
-
export declare
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
export declare class PikkuFetchHTTPRequest<In = unknown> implements PikkuHTTPRequest<In> {
|
|
8
|
+
#private;
|
|
9
|
+
private request;
|
|
10
|
+
constructor(request: Request);
|
|
11
|
+
method(): HTTPMethod;
|
|
12
|
+
path(): string;
|
|
13
13
|
/**
|
|
14
14
|
* Retrieves the request body.
|
|
15
15
|
* @returns A promise that resolves to the request body.
|
|
16
16
|
*/
|
|
17
|
-
|
|
17
|
+
json(): Promise<In>;
|
|
18
18
|
/**
|
|
19
19
|
* Retrieves the raw request body as a Buffer.
|
|
20
20
|
* @returns A promise that resolves to the raw request body.
|
|
21
21
|
*/
|
|
22
|
-
|
|
22
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
23
23
|
/**
|
|
24
24
|
* Retrieves the value of a specific header.
|
|
25
25
|
* @param headerName - The name of the header to retrieve.
|
|
26
26
|
* @returns The value of the header, or undefined if the header is not found.
|
|
27
27
|
*/
|
|
28
|
-
|
|
28
|
+
header(headerName: string): string | null;
|
|
29
29
|
/**
|
|
30
30
|
* Retrieves the cookies from the request.
|
|
31
31
|
* @returns An object containing the cookies.
|
|
32
32
|
*/
|
|
33
|
-
|
|
33
|
+
cookie(cookieName: string): string | null;
|
|
34
34
|
/**
|
|
35
35
|
* Retrieves the request parameters.
|
|
36
36
|
* @returns An object containing the request parameters.
|
|
37
37
|
*/
|
|
38
|
-
|
|
38
|
+
params(): Partial<Record<string, string | string[]>>;
|
|
39
39
|
/**
|
|
40
40
|
* Sets the request parameters.
|
|
41
41
|
* @param params - An object containing the request parameters to set.
|
|
@@ -45,16 +45,10 @@ export declare abstract class PikkuHTTPAbstractRequest<In = unknown> extends Pik
|
|
|
45
45
|
* Retrieves the query parameters from the request.
|
|
46
46
|
* @returns An object containing the query parameters.
|
|
47
47
|
*/
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Retrieves the IP address of the client making the request.
|
|
51
|
-
* @returns The IP address of the client.
|
|
52
|
-
* @throws {Error} This method is not implemented and should be overridden by subclasses.
|
|
53
|
-
*/
|
|
54
|
-
getIP(): string;
|
|
48
|
+
query(): PikkuQuery;
|
|
55
49
|
/**
|
|
56
50
|
* Retrieves the combined data from the request, including parameters, query, and body.
|
|
57
51
|
* @returns A promise that resolves to an object containing the combined data.
|
|
58
52
|
*/
|
|
59
|
-
|
|
53
|
+
data(): Promise<In>;
|
|
60
54
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { parse as parseQuery } from 'picoquery';
|
|
2
|
+
import { parse as parseCookie } from 'cookie';
|
|
3
|
+
/**
|
|
4
|
+
* Abstract class representing a pikku request.
|
|
5
|
+
* @template In - The type of the request body.
|
|
6
|
+
* @group RequestResponse
|
|
7
|
+
*/
|
|
8
|
+
export class PikkuFetchHTTPRequest {
|
|
9
|
+
request;
|
|
10
|
+
#cookies;
|
|
11
|
+
#params = {};
|
|
12
|
+
#url;
|
|
13
|
+
constructor(request) {
|
|
14
|
+
this.request = request;
|
|
15
|
+
this.#url = new URL(request.url);
|
|
16
|
+
}
|
|
17
|
+
method() {
|
|
18
|
+
return this.request.method.toLowerCase();
|
|
19
|
+
}
|
|
20
|
+
path() {
|
|
21
|
+
return this.#url.pathname;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Retrieves the request body.
|
|
25
|
+
* @returns A promise that resolves to the request body.
|
|
26
|
+
*/
|
|
27
|
+
json() {
|
|
28
|
+
return this.request.json();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Retrieves the raw request body as a Buffer.
|
|
32
|
+
* @returns A promise that resolves to the raw request body.
|
|
33
|
+
*/
|
|
34
|
+
arrayBuffer() {
|
|
35
|
+
return this.request.arrayBuffer();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Retrieves the value of a specific header.
|
|
39
|
+
* @param headerName - The name of the header to retrieve.
|
|
40
|
+
* @returns The value of the header, or undefined if the header is not found.
|
|
41
|
+
*/
|
|
42
|
+
header(headerName) {
|
|
43
|
+
return this.request.headers.get(headerName.toLowerCase());
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Retrieves the cookies from the request.
|
|
47
|
+
* @returns An object containing the cookies.
|
|
48
|
+
*/
|
|
49
|
+
cookie(cookieName) {
|
|
50
|
+
const cookieHeader = this.header('cookie');
|
|
51
|
+
this.#cookies = cookieHeader ? parseCookie(cookieHeader) : {};
|
|
52
|
+
return this.#cookies[cookieName] || null;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Retrieves the request parameters.
|
|
56
|
+
* @returns An object containing the request parameters.
|
|
57
|
+
*/
|
|
58
|
+
params() {
|
|
59
|
+
return this.#params;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Sets the request parameters.
|
|
63
|
+
* @param params - An object containing the request parameters to set.
|
|
64
|
+
*/
|
|
65
|
+
setParams(params) {
|
|
66
|
+
this.#params = params;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Retrieves the query parameters from the request.
|
|
70
|
+
* @returns An object containing the query parameters.
|
|
71
|
+
*/
|
|
72
|
+
query() {
|
|
73
|
+
return parseQuery(this.#url.searchParams.toString());
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Retrieves the combined data from the request, including parameters, query, and body.
|
|
77
|
+
* @returns A promise that resolves to an object containing the combined data.
|
|
78
|
+
*/
|
|
79
|
+
async data() {
|
|
80
|
+
let body = {};
|
|
81
|
+
try {
|
|
82
|
+
body = await this.json();
|
|
83
|
+
}
|
|
84
|
+
catch (e) { }
|
|
85
|
+
return {
|
|
86
|
+
...this.params(),
|
|
87
|
+
...this.query(),
|
|
88
|
+
// TODO: If body isn't an object, we should insert it as the word...
|
|
89
|
+
...body,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { PikkuHTTPResponse } from './http-routes.types.js';
|
|
2
|
+
export declare class PikkuFetchHTTPResponse implements PikkuHTTPResponse {
|
|
3
|
+
#private;
|
|
4
|
+
status(code: number): this;
|
|
5
|
+
cookie(name: string, value: string, flags: any): this;
|
|
6
|
+
header(name: string, value: string | string[]): this;
|
|
7
|
+
arrayBuffer(data: XMLHttpRequestBodyInit): this;
|
|
8
|
+
json(data: unknown): this;
|
|
9
|
+
text(content: string): this;
|
|
10
|
+
html(content: string): this;
|
|
11
|
+
body(body: BodyInit): this;
|
|
12
|
+
redirect(location: string, status?: number): this;
|
|
13
|
+
toResponse(args?: Record<string, any>): Response;
|
|
14
|
+
}
|