@pikku/core 0.6.27 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -1
- package/dist/channel/channel-handler.js +16 -38
- package/dist/channel/channel-runner.d.ts +7 -8
- package/dist/channel/channel-runner.js +58 -7
- package/dist/channel/channel.types.d.ts +14 -27
- package/dist/channel/local/local-channel-runner.js +9 -4
- package/dist/channel/log-channels.js +4 -4
- package/dist/channel/serverless/serverless-channel-runner.js +17 -6
- package/dist/function/function-runner.d.ts +12 -0
- package/dist/function/function-runner.js +38 -0
- package/dist/function/functions.types.d.ts +37 -0
- package/dist/function/functions.types.js +1 -0
- package/dist/function/index.d.ts +2 -0
- package/dist/function/index.js +2 -0
- package/dist/handle-error.d.ts +1 -1
- package/dist/http/http-route-runner.d.ts +3 -3
- package/dist/http/http-route-runner.js +60 -35
- package/dist/http/http-routes.types.d.ts +38 -7
- package/dist/http/http-runner.d.ts +90 -0
- package/dist/http/http-runner.js +336 -0
- package/dist/http/http.types.d.ts +177 -0
- package/dist/http/http.types.js +1 -0
- package/dist/http/index.d.ts +2 -2
- package/dist/http/index.js +1 -1
- package/dist/http/log-http-routes.js +7 -5
- package/dist/http/pikku-fetch-http-request.d.ts +1 -1
- package/dist/http/pikku-fetch-http-response.d.ts +4 -2
- package/dist/http/pikku-fetch-http-response.js +64 -12
- package/dist/http/pikku-http-request.d.ts +56 -0
- package/dist/http/pikku-http-request.js +95 -0
- package/dist/http/pikku-http-response.d.ts +13 -0
- package/dist/http/pikku-http-response.js +60 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/middleware/auth-apikey.js +5 -4
- package/dist/middleware/auth-bearer.js +5 -4
- package/dist/middleware/auth-cookie.js +10 -9
- package/dist/parse-relative-time-offset.d.ts +12 -0
- package/dist/parse-relative-time-offset.js +20 -0
- package/dist/permissions.d.ts +2 -2
- package/dist/pikku-func.d.ts +12 -0
- package/dist/pikku-func.js +39 -0
- package/dist/pikku-function.d.ts +1 -0
- package/dist/pikku-function.js +1 -0
- package/dist/pikku-state.d.ts +10 -5
- package/dist/pikku-state.js +7 -3
- package/dist/scheduler/scheduler-runner.d.ts +1 -1
- package/dist/scheduler/scheduler-runner.js +24 -6
- package/dist/scheduler/scheduler.types.d.ts +3 -2
- package/dist/services/content-service.d.ts +33 -37
- package/dist/types/core.types.d.ts +15 -1
- package/dist/types/functions.types.d.ts +11 -6
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +15 -0
- package/package.json +2 -1
- package/lcov.info +0 -4310
- package/src/channel/channel-handler.ts +0 -227
- package/src/channel/channel-runner.ts +0 -98
- package/src/channel/channel-store.ts +0 -29
- package/src/channel/channel.types.ts +0 -171
- package/src/channel/eventhub-service.ts +0 -30
- package/src/channel/eventhub-store.ts +0 -8
- package/src/channel/index.ts +0 -8
- package/src/channel/local/index.ts +0 -3
- package/src/channel/local/local-channel-handler.ts +0 -50
- package/src/channel/local/local-channel-runner.test.ts +0 -158
- package/src/channel/local/local-channel-runner.ts +0 -126
- package/src/channel/local/local-eventhub-service.test.ts +0 -95
- package/src/channel/local/local-eventhub-service.ts +0 -95
- package/src/channel/log-channels.ts +0 -20
- package/src/channel/pikku-abstract-channel-handler.test.ts +0 -52
- package/src/channel/pikku-abstract-channel-handler.ts +0 -38
- package/src/channel/serverless/index.ts +0 -1
- package/src/channel/serverless/serverless-channel-runner.ts +0 -209
- package/src/errors/error-handler.ts +0 -61
- package/src/errors/error.test.ts +0 -54
- package/src/errors/errors.ts +0 -339
- package/src/errors/index.ts +0 -2
- package/src/handle-error.ts +0 -73
- package/src/http/http-route-runner.test.ts +0 -117
- package/src/http/http-route-runner.ts +0 -460
- package/src/http/http-routes.types.ts +0 -211
- package/src/http/incomingmessage-to-request-convertor.ts +0 -0
- package/src/http/index.ts +0 -8
- package/src/http/log-http-routes.ts +0 -20
- package/src/http/pikku-fetch-http-request.test.ts +0 -237
- package/src/http/pikku-fetch-http-request.ts +0 -167
- package/src/http/pikku-fetch-http-response.test.ts +0 -82
- package/src/http/pikku-fetch-http-response.ts +0 -84
- package/src/index.ts +0 -19
- package/src/middleware/auth-apikey.ts +0 -65
- package/src/middleware/auth-bearer.ts +0 -65
- package/src/middleware/auth-cookie.ts +0 -98
- package/src/middleware/index.ts +0 -3
- package/src/middleware/timeout.ts +0 -13
- package/src/middleware-runner.ts +0 -43
- package/src/permissions.test.ts +0 -58
- package/src/permissions.ts +0 -45
- package/src/pikku-request.ts +0 -23
- package/src/pikku-response.ts +0 -5
- package/src/pikku-state.ts +0 -71
- package/src/scheduler/index.ts +0 -5
- package/src/scheduler/log-schedulers.ts +0 -20
- package/src/scheduler/scheduler-runner.ts +0 -87
- package/src/scheduler/scheduler.types.ts +0 -29
- package/src/schema.test.ts +0 -57
- package/src/schema.ts +0 -99
- package/src/services/content-service.ts +0 -72
- package/src/services/index.ts +0 -18
- package/src/services/jwt-service.ts +0 -30
- package/src/services/local-content.ts +0 -105
- package/src/services/local-secrets.ts +0 -43
- package/src/services/local-variables.ts +0 -17
- package/src/services/logger-console.ts +0 -97
- package/src/services/logger.ts +0 -57
- package/src/services/schema-service.ts +0 -26
- package/src/services/secret-service.ts +0 -17
- package/src/services/user-session-service.ts +0 -53
- package/src/services/variables-service.ts +0 -6
- package/src/time-utils.test.ts +0 -56
- package/src/time-utils.ts +0 -32
- package/src/types/core.types.ts +0 -159
- package/src/types/functions.types.ts +0 -63
- package/src/utils.ts +0 -21
- package/tsconfig.json +0 -13
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { match } from 'path-to-regexp';
|
|
2
|
+
import { MissingSessionError, NotFoundError } from '../errors/errors.js';
|
|
3
|
+
import { closeSessionServices, createWeakUID, isSerializable, } from '../utils.js';
|
|
4
|
+
import { PikkuUserSessionService, } from '../services/user-session-service.js';
|
|
5
|
+
import { runMiddleware } from '../middleware-runner.js';
|
|
6
|
+
import { handleError } from '../handle-error.js';
|
|
7
|
+
import { pikkuState } from '../pikku-state.js';
|
|
8
|
+
import { PikkuFetchHTTPResponse } from './pikku-fetch-http-response.js';
|
|
9
|
+
import { PikkuFetchHTTPRequest } from './pikku-fetch-http-request.js';
|
|
10
|
+
import { addFunction, runPikkuFunc } from '../function/function-runner.js';
|
|
11
|
+
/**
|
|
12
|
+
* Registers middleware either globally or for a specific route.
|
|
13
|
+
*
|
|
14
|
+
* When a string route pattern is provided along with middleware, the middleware
|
|
15
|
+
* is applied only to that route. Otherwise, if an array is provided, it is treated
|
|
16
|
+
* as global middleware (applied to all routes).
|
|
17
|
+
*
|
|
18
|
+
* @template APIMiddleware The middleware type.
|
|
19
|
+
* @param {APIMiddleware[] | string} routeOrMiddleware - Either a global middleware array or a route pattern string.
|
|
20
|
+
* @param {APIMiddleware[]} [middleware] - The middleware array to apply when a route pattern is specified.
|
|
21
|
+
*/
|
|
22
|
+
export const addMiddleware = (routeOrMiddleware, middleware) => {
|
|
23
|
+
if (typeof routeOrMiddleware === 'string' && middleware) {
|
|
24
|
+
pikkuState('http', 'middleware').push({
|
|
25
|
+
route: routeOrMiddleware,
|
|
26
|
+
middleware,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
pikkuState('http', 'middleware').push({
|
|
31
|
+
route: '*',
|
|
32
|
+
middleware: routeOrMiddleware,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Adds a new route to the global HTTP route registry.
|
|
38
|
+
*
|
|
39
|
+
* The route configuration includes the HTTP method, route path, permissions,
|
|
40
|
+
* middleware, and the handler function that implements the route's logic.
|
|
41
|
+
*
|
|
42
|
+
* @template In Expected input type.
|
|
43
|
+
* @template Out Expected output type.
|
|
44
|
+
* @template Route Route pattern as a string.
|
|
45
|
+
* @template APIFunction Type for the route handler function.
|
|
46
|
+
* @template APIFunctionSessionless Type for a sessionless handler.
|
|
47
|
+
* @template APIPermissionGroup Type representing required permissions.
|
|
48
|
+
* @template APIMiddleware Middleware type to be used with the route.
|
|
49
|
+
* @param {CoreHTTPFunctionRoute<In, Out, Route, APIFunction, APIFunctionSessionless, APIPermission, APIMiddleware>} route - The route configuration object.
|
|
50
|
+
*/
|
|
51
|
+
export const addHTTPRoute = (httpRoute) => {
|
|
52
|
+
const httpMeta = pikkuState('http', 'meta');
|
|
53
|
+
const routeMeta = httpMeta.find((meta) => meta.route === httpRoute.route && meta.method === httpRoute.method);
|
|
54
|
+
if (!routeMeta) {
|
|
55
|
+
throw new Error('Route metadata not found');
|
|
56
|
+
}
|
|
57
|
+
addFunction(routeMeta.pikkuFuncName, httpRoute.func);
|
|
58
|
+
const routes = pikkuState('http', 'routes');
|
|
59
|
+
if (!routes.has(httpRoute.method)) {
|
|
60
|
+
routes.set(httpRoute.method, new Map());
|
|
61
|
+
}
|
|
62
|
+
pikkuState('http', 'routes').get(httpRoute.method)?.set(httpRoute.route, httpRoute);
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Finds a matching route based on the HTTP method and URL path.
|
|
66
|
+
*
|
|
67
|
+
* Iterates over all registered routes, skipping those with a mismatched method.
|
|
68
|
+
* When a route pattern matches the incoming request path, this function aggregates
|
|
69
|
+
* any global middleware along with route-specific middleware and identifies any input schema.
|
|
70
|
+
*
|
|
71
|
+
* @param {string} requestType - The HTTP method (e.g., GET, POST).
|
|
72
|
+
* @param {string} requestPath - The URL path of the incoming request.
|
|
73
|
+
* @returns {Object | undefined} An object with matched route details or undefined if no match.
|
|
74
|
+
*/
|
|
75
|
+
const getMatchingRoute = (requestType, requestPath) => {
|
|
76
|
+
const allRoutes = pikkuState('http', 'routes');
|
|
77
|
+
const middleware = pikkuState('http', 'middleware');
|
|
78
|
+
const routes = allRoutes.get(requestType.toLowerCase());
|
|
79
|
+
if (!routes) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
for (const route of routes.values()) {
|
|
83
|
+
// Generate a matching function from the route pattern
|
|
84
|
+
const matchFunc = match(`/${route.route}`.replace(/^\/\//, '/'), {
|
|
85
|
+
decode: decodeURIComponent,
|
|
86
|
+
});
|
|
87
|
+
// Attempt to match the request path
|
|
88
|
+
const matchedPath = matchFunc(requestPath.replace(/^\/\//, '/'));
|
|
89
|
+
if (matchedPath) {
|
|
90
|
+
// Aggregate global and route-specific middleware
|
|
91
|
+
const globalMiddleware = middleware
|
|
92
|
+
.filter((m) => m.route === '*' || new RegExp(m.route).test(route.route))
|
|
93
|
+
.map((m) => m.middleware)
|
|
94
|
+
.flat();
|
|
95
|
+
const meta = pikkuState('http', 'meta').find((meta) => meta.route === route.route && meta.method === route.method);
|
|
96
|
+
return {
|
|
97
|
+
matchedPath,
|
|
98
|
+
params: matchedPath.params,
|
|
99
|
+
route,
|
|
100
|
+
permissions: route.permissions,
|
|
101
|
+
middleware: [...globalMiddleware, ...(route.middleware || [])],
|
|
102
|
+
meta: meta
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Combines the request and response objects into a single HTTP interaction object.
|
|
110
|
+
*
|
|
111
|
+
* This utility function creates an object that holds both the HTTP request and response,
|
|
112
|
+
* which simplifies passing these around through middleware and route execution.
|
|
113
|
+
*
|
|
114
|
+
* @param {PikkuHTTPRequest | undefined} request - The HTTP request object.
|
|
115
|
+
* @param {PikkuHTTPResponse | undefined} response - The HTTP response object.
|
|
116
|
+
* @returns {PikkuHTTP | undefined} The combined HTTP interaction object or undefined if none provided.
|
|
117
|
+
*/
|
|
118
|
+
export const createHTTPInteraction = (request, response) => {
|
|
119
|
+
let http = undefined;
|
|
120
|
+
if (request || response) {
|
|
121
|
+
http = {};
|
|
122
|
+
if (request) {
|
|
123
|
+
http.request = request;
|
|
124
|
+
}
|
|
125
|
+
if (response) {
|
|
126
|
+
http.response = response;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return http;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Validates the input data and executes the route handler with associated middleware.
|
|
133
|
+
*
|
|
134
|
+
* This function is the central execution point for a route. It performs these steps:
|
|
135
|
+
* 1. Sets URL parameters on the request.
|
|
136
|
+
* 2. Validates the user session if required.
|
|
137
|
+
* 3. Creates session-specific services.
|
|
138
|
+
* 4. Validates the incoming data against a schema.
|
|
139
|
+
* 5. Optionally coerces query string values to arrays.
|
|
140
|
+
* 6. Checks route-specific permissions.
|
|
141
|
+
* 7. Executes the route handler.
|
|
142
|
+
* 8. Sends the appropriate response.
|
|
143
|
+
*
|
|
144
|
+
* @param {Object} services - A collection of shared services and utilities.
|
|
145
|
+
* @param {Object} matchedRoute - Contains route details, URL parameters, middleware, and optional schema.
|
|
146
|
+
* @param {PikkuHTTP | undefined} http - The HTTP interaction object.
|
|
147
|
+
* @param {Object} options - Options for route execution (e.g., whether to coerce query strings to arrays).
|
|
148
|
+
* @returns {Promise<any>} An object containing the route handler result and session services (if any).
|
|
149
|
+
* @throws Throws errors like MissingSessionError or ForbiddenError on validation failures.
|
|
150
|
+
*/
|
|
151
|
+
const executeRouteWithMiddleware = async (services, matchedRoute, http, options) => {
|
|
152
|
+
const { matchedPath, params, route, middleware, meta } = matchedRoute;
|
|
153
|
+
const { singletonServices, userSession, createSessionServices, skipUserSession, requestId, } = services;
|
|
154
|
+
const requiresSession = route.auth !== false;
|
|
155
|
+
let sessionServices;
|
|
156
|
+
let result;
|
|
157
|
+
// Attach URL parameters to the request object
|
|
158
|
+
http?.request?.setParams(params);
|
|
159
|
+
singletonServices.logger.info(`Matched route: ${route.route} | method: ${route.method.toUpperCase()} | auth: ${requiresSession.toString()}`);
|
|
160
|
+
// Main route execution logic wrapped for middleware handling
|
|
161
|
+
const runMain = async () => {
|
|
162
|
+
const session = userSession.get();
|
|
163
|
+
// Ensure session is available when required
|
|
164
|
+
if (skipUserSession && requiresSession) {
|
|
165
|
+
throw new Error("Can't skip trying to get user session if auth is required");
|
|
166
|
+
}
|
|
167
|
+
if (requiresSession && !session) {
|
|
168
|
+
singletonServices.logger.info({
|
|
169
|
+
action: 'Rejecting route (invalid session)',
|
|
170
|
+
path: matchedPath,
|
|
171
|
+
});
|
|
172
|
+
throw new MissingSessionError();
|
|
173
|
+
}
|
|
174
|
+
const data = http.request.data();
|
|
175
|
+
const getAllServices = async () => {
|
|
176
|
+
let channel;
|
|
177
|
+
if (matchedRoute.route.sse) {
|
|
178
|
+
const response = http?.response;
|
|
179
|
+
if (!response) {
|
|
180
|
+
throw new Error('SSE requires a valid HTTP response object');
|
|
181
|
+
}
|
|
182
|
+
if (!response.setMode) {
|
|
183
|
+
throw new Error('Response object does not support SSE mode');
|
|
184
|
+
}
|
|
185
|
+
response.setMode('stream');
|
|
186
|
+
response.header('Content-Type', 'text/event-stream');
|
|
187
|
+
response.header('Cache-Control', 'no-cache');
|
|
188
|
+
response.header('Connection', 'keep-alive');
|
|
189
|
+
response.header('Transfer-Encoding', 'chunked');
|
|
190
|
+
channel = {
|
|
191
|
+
channelId: requestId,
|
|
192
|
+
openingData: data,
|
|
193
|
+
send: (data) => {
|
|
194
|
+
response.arrayBuffer(isSerializable(data) ? JSON.stringify(data) : data);
|
|
195
|
+
},
|
|
196
|
+
close: () => {
|
|
197
|
+
channel.state = 'closed';
|
|
198
|
+
response.close?.();
|
|
199
|
+
},
|
|
200
|
+
state: 'open',
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
// Create session-specific services for handling the request
|
|
204
|
+
sessionServices = await createSessionServices({ ...singletonServices, userSession, channel }, { http }, session);
|
|
205
|
+
return {
|
|
206
|
+
...singletonServices,
|
|
207
|
+
...sessionServices,
|
|
208
|
+
http,
|
|
209
|
+
userSession,
|
|
210
|
+
channel,
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
const result = await runPikkuFunc(meta.pikkuFuncName, {
|
|
214
|
+
singletonServices,
|
|
215
|
+
getAllServices,
|
|
216
|
+
session,
|
|
217
|
+
data,
|
|
218
|
+
permissions: route.permissions,
|
|
219
|
+
coerceDataFromSchema: options.coerceDataFromSchema,
|
|
220
|
+
});
|
|
221
|
+
// Respond with either a binary or JSON response based on configuration
|
|
222
|
+
if (route.returnsJSON === false) {
|
|
223
|
+
http?.response?.arrayBuffer(result);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
http?.response?.json(result);
|
|
227
|
+
}
|
|
228
|
+
http?.response?.status(200);
|
|
229
|
+
// TODO: Evaluate if the response stream should be explicitly ended.
|
|
230
|
+
// http?.response?.end()
|
|
231
|
+
return result;
|
|
232
|
+
};
|
|
233
|
+
// Execute middleware, then run the main logic
|
|
234
|
+
await runMiddleware({ ...singletonServices, userSession }, { http }, middleware, runMain);
|
|
235
|
+
return sessionServices ? { result, sessionServices } : { result };
|
|
236
|
+
};
|
|
237
|
+
/**
|
|
238
|
+
* Executes an HTTP route for a given Fetch API request.
|
|
239
|
+
*
|
|
240
|
+
* This function wraps the entire lifecycle of handling an HTTP request:
|
|
241
|
+
* - Matching the request to a registered route.
|
|
242
|
+
* - Validating input and session state.
|
|
243
|
+
* - Running middleware and the route handler.
|
|
244
|
+
* - Handling errors and forming the response.
|
|
245
|
+
*
|
|
246
|
+
* @template In Expected input data type.
|
|
247
|
+
* @template Out Expected output data type.
|
|
248
|
+
* @param {Request} request - The native Fetch API Request object.
|
|
249
|
+
* @param {RunRouteOptions & RunRouteParams} params - Additional options including services and session management.
|
|
250
|
+
* @returns {Promise<Response>} A promise that resolves to a Fetch API Response object.
|
|
251
|
+
*/
|
|
252
|
+
export const fetch = async (request, params) => {
|
|
253
|
+
const pikkuResponse = new PikkuFetchHTTPResponse();
|
|
254
|
+
await fetchData(request, pikkuResponse, params);
|
|
255
|
+
return pikkuResponse.toResponse();
|
|
256
|
+
};
|
|
257
|
+
/**
|
|
258
|
+
* Executes an HTTP route using a Pikku-specific request wrapper.
|
|
259
|
+
*
|
|
260
|
+
* This variant accepts either a native Request or a PikkuHTTPRequest object and returns
|
|
261
|
+
* a PikkuFetchHTTPResponse for further manipulation if needed.
|
|
262
|
+
*
|
|
263
|
+
* @template In Expected input data type.
|
|
264
|
+
* @template Out Expected output data type.
|
|
265
|
+
* @param {Request | PikkuHTTPRequest} request - The request object.
|
|
266
|
+
* @param {RunRouteOptions & RunRouteParams} params - Execution options including services and session configuration.
|
|
267
|
+
* @returns {Promise<PikkuFetchHTTPResponse>} A promise that resolves to a PikkuFetchHTTPResponse object.
|
|
268
|
+
*/
|
|
269
|
+
export const pikkuFetch = async (request, params) => {
|
|
270
|
+
const pikkuResponse = new PikkuFetchHTTPResponse();
|
|
271
|
+
await fetchData(request, pikkuResponse, params);
|
|
272
|
+
return pikkuResponse;
|
|
273
|
+
};
|
|
274
|
+
/**
|
|
275
|
+
* Core function to process an HTTP request through route matching, validation,
|
|
276
|
+
* middleware execution, error handling, and session service cleanup.
|
|
277
|
+
*
|
|
278
|
+
* This function does the following:
|
|
279
|
+
* - Wraps the incoming request and response into an HTTP interaction object.
|
|
280
|
+
* - Determines the correct route based on HTTP method and path.
|
|
281
|
+
* - Executes middleware and the route handler.
|
|
282
|
+
* - Catches and handles errors, optionally bubbling them if configured.
|
|
283
|
+
* - Cleans up any session services created during processing.
|
|
284
|
+
*
|
|
285
|
+
* @template In Expected input data type.
|
|
286
|
+
* @template Out Expected output data type.
|
|
287
|
+
* @param {Request | PikkuHTTPRequest} request - The incoming HTTP request.
|
|
288
|
+
* @param {PikkuHTTPResponse} response - The response object to be populated.
|
|
289
|
+
* @param {RunRouteOptions & RunRouteParams} options - Options such as singleton services, session handling, and error configuration.
|
|
290
|
+
* @returns {Promise<Out | void>} The output from the route handler or void if an error occurred.
|
|
291
|
+
*/
|
|
292
|
+
export const fetchData = async (request, response, { singletonServices, createSessionServices, skipUserSession = false, respondWith404 = true, logWarningsForStatusCodes = [], coerceDataFromSchema = true, bubbleErrors = false, generateRequestId, }) => {
|
|
293
|
+
const requestId = request.getHeader?.('x-request-id') ||
|
|
294
|
+
generateRequestId?.() ||
|
|
295
|
+
createWeakUID();
|
|
296
|
+
const userSession = new PikkuUserSessionService();
|
|
297
|
+
let sessionServices;
|
|
298
|
+
let result;
|
|
299
|
+
// Combine the request and response into one interaction object
|
|
300
|
+
const http = createHTTPInteraction(request instanceof Request ? new PikkuFetchHTTPRequest(request) : request, response);
|
|
301
|
+
const apiType = http.request.method();
|
|
302
|
+
const apiRoute = http.request.path();
|
|
303
|
+
// Locate the matching route based on the HTTP method and path
|
|
304
|
+
const matchedRoute = getMatchingRoute(apiType, apiRoute);
|
|
305
|
+
try {
|
|
306
|
+
// If no route matches, log the occurrence and throw a NotFoundError
|
|
307
|
+
if (!matchedRoute) {
|
|
308
|
+
singletonServices.logger.info({
|
|
309
|
+
message: 'Route not found',
|
|
310
|
+
apiRoute,
|
|
311
|
+
apiType,
|
|
312
|
+
});
|
|
313
|
+
throw new NotFoundError(`Route not found: ${apiRoute}`);
|
|
314
|
+
}
|
|
315
|
+
// Execute the matched route along with its middleware and session management
|
|
316
|
+
;
|
|
317
|
+
({ result, sessionServices } = await executeRouteWithMiddleware({
|
|
318
|
+
singletonServices,
|
|
319
|
+
userSession,
|
|
320
|
+
createSessionServices,
|
|
321
|
+
skipUserSession,
|
|
322
|
+
requestId,
|
|
323
|
+
}, matchedRoute, http, { coerceDataFromSchema }));
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
326
|
+
catch (e) {
|
|
327
|
+
// Handle errors and, depending on configuration, bubble them up or respond with an error
|
|
328
|
+
handleError(e, http, requestId, singletonServices.logger, logWarningsForStatusCodes, respondWith404, bubbleErrors);
|
|
329
|
+
}
|
|
330
|
+
finally {
|
|
331
|
+
// Clean up any session-specific services created during processing
|
|
332
|
+
if (sessionServices) {
|
|
333
|
+
await closeSessionServices(singletonServices.logger, sessionServices);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import type { SerializeOptions } from 'cookie';
|
|
2
|
+
import type { PikkuError } from '../errors/error-handler.js';
|
|
3
|
+
import type { APIDocs, CoreServices, CoreSingletonServices, CoreUserSession, CreateSessionServices, PikkuMiddleware } from '../types/core.types.js';
|
|
4
|
+
import type { CoreAPIFunction, CoreAPIFunctionSessionless, CoreAPIPermission, CorePermissionGroup } from '../function/functions.types.js';
|
|
5
|
+
type ExtractRouteParams<S extends string> = S extends `${string}:${infer Param}/${infer Rest}` ? Param | ExtractRouteParams<`/${Rest}`> : S extends `${string}:${infer Param}` ? Param : never;
|
|
6
|
+
export type AssertRouteParams<In, Route extends string> = ExtractRouteParams<Route> extends keyof In ? unknown : ['Error: Route parameters', ExtractRouteParams<Route>, 'not in', keyof In];
|
|
7
|
+
export type RunRouteOptions = Partial<{
|
|
8
|
+
skipUserSession: boolean;
|
|
9
|
+
respondWith404: boolean;
|
|
10
|
+
logWarningsForStatusCodes: number[];
|
|
11
|
+
coerceDataFromSchema: boolean;
|
|
12
|
+
bubbleErrors: boolean;
|
|
13
|
+
generateRequestId: () => string;
|
|
14
|
+
}>;
|
|
15
|
+
export type RunRouteParams = {
|
|
16
|
+
singletonServices: CoreSingletonServices;
|
|
17
|
+
createSessionServices: CreateSessionServices<CoreSingletonServices, CoreServices<CoreSingletonServices>, CoreUserSession>;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Represents the HTTP methods supported for API routes.
|
|
21
|
+
*/
|
|
22
|
+
export type HTTPMethod = 'post' | 'get' | 'delete' | 'patch' | 'head' | 'put' | 'options';
|
|
23
|
+
/**
|
|
24
|
+
* Represents an API route without a function, including metadata such as content type, route, and timeout settings.
|
|
25
|
+
*/
|
|
26
|
+
export type CoreHTTPFunction = {
|
|
27
|
+
contentType?: 'xml' | 'json';
|
|
28
|
+
route: string;
|
|
29
|
+
eventChannel?: false;
|
|
30
|
+
returnsJSON?: false;
|
|
31
|
+
timeout?: number;
|
|
32
|
+
docs?: Partial<{
|
|
33
|
+
description: string;
|
|
34
|
+
response: string;
|
|
35
|
+
errors: Array<typeof PikkuError>;
|
|
36
|
+
tags: string[];
|
|
37
|
+
}>;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Represents a http interaction within Pikku, including a request and response.
|
|
41
|
+
*/
|
|
42
|
+
export interface PikkuHTTP {
|
|
43
|
+
request?: PikkuHTTPRequest;
|
|
44
|
+
response?: PikkuHTTPResponse;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Represents request headers as either a record or a function to get headers by name.
|
|
48
|
+
*/
|
|
49
|
+
export type RequestHeaders = Record<string, string | string[] | undefined> | ((headerName: string) => string | string[] | undefined);
|
|
50
|
+
/**
|
|
51
|
+
* Represents a query object for Pikku, where each key can be a string, a value, or an array of values.
|
|
52
|
+
*/
|
|
53
|
+
export type PikkuQuery<T = Record<string, string | undefined>> = Record<string, string | T | null | Array<T | null>>;
|
|
54
|
+
/**
|
|
55
|
+
* Represents a core API route, which can have different configurations depending on whether it requires authentication and permissions.
|
|
56
|
+
*
|
|
57
|
+
* @template In - The input type.
|
|
58
|
+
* @template Out - The output type.
|
|
59
|
+
* @template R - The route string type.
|
|
60
|
+
* @template APIFunction - The API function type, defaults to `CoreAPIFunction`.
|
|
61
|
+
* @template APIFunctionSessionless - The sessionless API function type, defaults to `CoreAPIFunctionSessionless`.
|
|
62
|
+
* @template APIPermission - The permission function type, defaults to `CoreAPIPermission`.
|
|
63
|
+
*/
|
|
64
|
+
export type CoreHTTPFunctionRoute<In, Out, R extends string, APIFunction = CoreAPIFunction<In, Out>, APIFunctionSessionless = CoreAPIFunctionSessionless<In, Out>, APIPermission = CoreAPIPermission<In>, APIMiddleware = PikkuMiddleware> = (CoreHTTPFunction & {
|
|
65
|
+
route: R;
|
|
66
|
+
method: HTTPMethod;
|
|
67
|
+
func: APIFunction;
|
|
68
|
+
permissions?: CorePermissionGroup<APIPermission>;
|
|
69
|
+
auth?: true;
|
|
70
|
+
tags?: string[];
|
|
71
|
+
middleware?: APIMiddleware[];
|
|
72
|
+
sse?: undefined;
|
|
73
|
+
}) | (CoreHTTPFunction & {
|
|
74
|
+
route: R;
|
|
75
|
+
method: HTTPMethod;
|
|
76
|
+
func: APIFunctionSessionless;
|
|
77
|
+
permissions?: undefined;
|
|
78
|
+
auth?: false;
|
|
79
|
+
tags?: string[];
|
|
80
|
+
middleware?: APIMiddleware[];
|
|
81
|
+
sse?: undefined;
|
|
82
|
+
}) | (CoreHTTPFunction & {
|
|
83
|
+
route: R;
|
|
84
|
+
method: 'get';
|
|
85
|
+
func: APIFunction;
|
|
86
|
+
permissions?: CorePermissionGroup<APIPermission>;
|
|
87
|
+
auth?: true;
|
|
88
|
+
sse?: boolean;
|
|
89
|
+
tags?: string[];
|
|
90
|
+
middleware?: APIMiddleware[];
|
|
91
|
+
}) | (CoreHTTPFunction & {
|
|
92
|
+
route: R;
|
|
93
|
+
method: 'get';
|
|
94
|
+
func: APIFunctionSessionless;
|
|
95
|
+
permissions?: undefined;
|
|
96
|
+
auth?: false;
|
|
97
|
+
sse?: boolean;
|
|
98
|
+
tags?: string[];
|
|
99
|
+
middleware?: APIMiddleware[];
|
|
100
|
+
}) | (CoreHTTPFunction & {
|
|
101
|
+
route: R;
|
|
102
|
+
method: 'post';
|
|
103
|
+
func: APIFunction;
|
|
104
|
+
permissions?: CorePermissionGroup<APIPermission>;
|
|
105
|
+
auth?: true;
|
|
106
|
+
query?: Array<keyof In>;
|
|
107
|
+
tags?: string[];
|
|
108
|
+
middleware?: APIMiddleware[];
|
|
109
|
+
sse?: undefined;
|
|
110
|
+
}) | (CoreHTTPFunction & {
|
|
111
|
+
route: R;
|
|
112
|
+
method: 'post';
|
|
113
|
+
func: APIFunctionSessionless;
|
|
114
|
+
permissions?: undefined;
|
|
115
|
+
auth?: false;
|
|
116
|
+
query?: Array<keyof In>;
|
|
117
|
+
tags?: string[];
|
|
118
|
+
middleware?: APIMiddleware[];
|
|
119
|
+
sse?: undefined;
|
|
120
|
+
});
|
|
121
|
+
/**
|
|
122
|
+
* Represents the input types for route metadata, including parameters, query, and body types.
|
|
123
|
+
*/
|
|
124
|
+
export type HTTPFunctionMetaInputTypes = {
|
|
125
|
+
params?: string;
|
|
126
|
+
query?: string;
|
|
127
|
+
body?: string;
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Represents metadata for a set of routes, including route details, methods, input/output types, and documentation.
|
|
131
|
+
*/
|
|
132
|
+
export type HTTPRouteMeta = {
|
|
133
|
+
pikkuFuncName: string;
|
|
134
|
+
route: string;
|
|
135
|
+
method: HTTPMethod;
|
|
136
|
+
params?: string[];
|
|
137
|
+
query?: string[];
|
|
138
|
+
input: string | null;
|
|
139
|
+
output: string | null;
|
|
140
|
+
inputTypes?: HTTPFunctionMetaInputTypes;
|
|
141
|
+
docs?: APIDocs;
|
|
142
|
+
tags?: string[];
|
|
143
|
+
sse?: true;
|
|
144
|
+
};
|
|
145
|
+
export type HTTPRoutesMeta = Array<HTTPRouteMeta>;
|
|
146
|
+
export type HTTPFunctionsMeta = Array<{
|
|
147
|
+
name: string;
|
|
148
|
+
inputs: string[] | null;
|
|
149
|
+
outputs: string[] | null;
|
|
150
|
+
}>;
|
|
151
|
+
export type HTTPRouteMiddleware = {
|
|
152
|
+
route: string;
|
|
153
|
+
middleware: PikkuMiddleware[];
|
|
154
|
+
};
|
|
155
|
+
export interface PikkuHTTPRequest<In = unknown> {
|
|
156
|
+
method(): HTTPMethod;
|
|
157
|
+
path(): string;
|
|
158
|
+
data(): Promise<In>;
|
|
159
|
+
json(): Promise<unknown>;
|
|
160
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
161
|
+
header(headerName: string): string | null;
|
|
162
|
+
cookie(name?: string): string | null;
|
|
163
|
+
params(): Partial<Record<string, string | string[]>>;
|
|
164
|
+
setParams(params: Record<string, string | string[] | undefined>): void;
|
|
165
|
+
query(): PikkuQuery;
|
|
166
|
+
}
|
|
167
|
+
export interface PikkuHTTPResponse {
|
|
168
|
+
status(code: number): this;
|
|
169
|
+
cookie(name: string, value: string | null, options: SerializeOptions): this;
|
|
170
|
+
header(name: string, value: string | string[]): this;
|
|
171
|
+
arrayBuffer(data: ArrayBuffer | ArrayBufferView | Blob | string | FormData | URLSearchParams | ReadableStream): this;
|
|
172
|
+
json(data: unknown): this;
|
|
173
|
+
redirect(location: string, status?: number): this;
|
|
174
|
+
close?: () => void;
|
|
175
|
+
setMode?: (mode: 'stream') => void;
|
|
176
|
+
}
|
|
177
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/http/index.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ export * from './pikku-fetch-http-request.js';
|
|
|
2
2
|
export * from './pikku-fetch-http-response.js';
|
|
3
3
|
export * from './incomingmessage-to-request-convertor.js';
|
|
4
4
|
export * from './log-http-routes.js';
|
|
5
|
-
export { fetch, fetchData,
|
|
6
|
-
export type * from './http
|
|
5
|
+
export { fetch, fetchData, addHTTPRoute } from './http-runner.js';
|
|
6
|
+
export type * from './http.types.js';
|
package/dist/http/index.js
CHANGED
|
@@ -2,4 +2,4 @@ export * from './pikku-fetch-http-request.js';
|
|
|
2
2
|
export * from './pikku-fetch-http-response.js';
|
|
3
3
|
export * from './incomingmessage-to-request-convertor.js';
|
|
4
4
|
export * from './log-http-routes.js';
|
|
5
|
-
export { fetch, fetchData,
|
|
5
|
+
export { fetch, fetchData, addHTTPRoute } from './http-runner.js';
|
|
@@ -4,14 +4,16 @@ import { pikkuState } from '../pikku-state.js';
|
|
|
4
4
|
* @param logger - A logger for logging information.
|
|
5
5
|
*/
|
|
6
6
|
export const logRoutes = (logger) => {
|
|
7
|
-
const
|
|
8
|
-
if (
|
|
7
|
+
const routesByType = pikkuState('http', 'routes');
|
|
8
|
+
if (routesByType.size === 0) {
|
|
9
9
|
logger.info('No routes added');
|
|
10
10
|
return;
|
|
11
11
|
}
|
|
12
12
|
let routesMessage = 'Routes loaded:';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
routesByType.forEach((routes) => {
|
|
14
|
+
routes.forEach((route) => {
|
|
15
|
+
routesMessage += `\n\t- ${route.method.toUpperCase()} -> ${route}`;
|
|
16
|
+
});
|
|
17
|
+
});
|
|
16
18
|
logger.info(routesMessage);
|
|
17
19
|
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { PikkuHTTPResponse } from './http
|
|
1
|
+
import { PikkuHTTPResponse } from './http.types.js';
|
|
2
2
|
import { SerializeOptions as CookieSerializeOptions } from 'cookie';
|
|
3
3
|
export declare class PikkuFetchHTTPResponse implements PikkuHTTPResponse {
|
|
4
4
|
#private;
|
|
5
|
+
setMode(mode: 'stream'): void;
|
|
5
6
|
status(code: number): this;
|
|
6
7
|
cookie(name: string, value: string, flags: CookieSerializeOptions): this;
|
|
7
8
|
header(name: string, value: string | string[]): this;
|
|
@@ -9,7 +10,8 @@ export declare class PikkuFetchHTTPResponse implements PikkuHTTPResponse {
|
|
|
9
10
|
json(data: unknown): this;
|
|
10
11
|
text(content: string): this;
|
|
11
12
|
html(content: string): this;
|
|
12
|
-
body(body: BodyInit): this;
|
|
13
13
|
redirect(location: string, status?: number): this;
|
|
14
|
+
close(): this;
|
|
14
15
|
toResponse(args?: Record<string, any>): Response;
|
|
16
|
+
private createStream;
|
|
15
17
|
}
|