@pikku/core 0.6.21 → 0.6.23
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 +5 -5
- 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 +148 -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 +1529 -1412
- package/package.json +3 -4
- package/src/channel/channel-handler.ts +82 -29
- package/src/channel/channel-runner.ts +7 -7
- 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 +173 -86
- 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
package/src/handle-error.ts
CHANGED
|
@@ -32,8 +32,8 @@ export const handleError = (
|
|
|
32
32
|
const errorResponse = getErrorResponse(e)
|
|
33
33
|
if (errorResponse != null) {
|
|
34
34
|
// Set status and response body
|
|
35
|
-
http?.response?.
|
|
36
|
-
http?.response?.
|
|
35
|
+
http?.response?.status(errorResponse.status)
|
|
36
|
+
http?.response?.json({
|
|
37
37
|
message: errorResponse.message,
|
|
38
38
|
payload: (e as any).payload,
|
|
39
39
|
traceId: trackerId,
|
|
@@ -49,23 +49,25 @@ export const handleError = (
|
|
|
49
49
|
} else {
|
|
50
50
|
// Handle unexpected errors
|
|
51
51
|
logger.error(e)
|
|
52
|
-
http?.response?.
|
|
52
|
+
http?.response?.status(500)
|
|
53
53
|
|
|
54
54
|
if (trackerId) {
|
|
55
55
|
logger.warn(`Error id: ${trackerId}`)
|
|
56
|
-
http?.response?.
|
|
56
|
+
http?.response?.json({ errorId: trackerId })
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
// Handle 404 errors specifically
|
|
61
61
|
if (e instanceof NotFoundError) {
|
|
62
|
-
|
|
62
|
+
// TODO
|
|
63
|
+
// http?.response?.end()
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
// Either bubble up or end the response
|
|
66
67
|
if (bubbleError) {
|
|
67
68
|
throw e
|
|
68
69
|
} else {
|
|
69
|
-
|
|
70
|
+
// TODO
|
|
71
|
+
// http?.response?.end()
|
|
70
72
|
}
|
|
71
73
|
}
|
|
@@ -1,39 +1,20 @@
|
|
|
1
1
|
import { test, describe, beforeEach, afterEach } from 'node:test'
|
|
2
2
|
import * as assert from 'assert'
|
|
3
3
|
import { NotFoundError } from '../errors/errors.js'
|
|
4
|
-
import { PikkuHTTPAbstractRequest } from './pikku-http-abstract-request.js'
|
|
5
|
-
import { PikkuHTTPAbstractResponse } from './pikku-http-abstract-response.js'
|
|
6
4
|
import { JSONValue, PikkuMiddleware } from '../types/core.types.js'
|
|
7
|
-
import {
|
|
5
|
+
import { fetch, addRoute } from './http-route-runner.js'
|
|
8
6
|
import { resetPikkuState } from '../pikku-state.js'
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
public getHeader(_headerName: string): string | undefined {
|
|
15
|
-
throw new Error('Method not implemented.')
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
class PikkuTestResponse extends PikkuHTTPAbstractResponse {
|
|
20
|
-
public setStatus(_status: number): void {
|
|
21
|
-
throw new Error('Method not implemented.')
|
|
22
|
-
}
|
|
23
|
-
public setJson(_body: JSONValue): void {
|
|
24
|
-
throw new Error('Method not implemented.')
|
|
25
|
-
}
|
|
26
|
-
public setResponse(_response: string | Buffer): void {
|
|
27
|
-
throw new Error('Method not implemented.')
|
|
28
|
-
}
|
|
29
|
-
}
|
|
7
|
+
import {
|
|
8
|
+
PikkuMockRequest,
|
|
9
|
+
PikkuMockResponse,
|
|
10
|
+
} from '../channel/local/local-channel-runner.test.js'
|
|
30
11
|
|
|
31
12
|
const sessionMiddleware: PikkuMiddleware = async (services, _, next) => {
|
|
32
13
|
services.userSessionService.set({ userId: 'test' } as any)
|
|
33
14
|
await next()
|
|
34
15
|
}
|
|
35
16
|
|
|
36
|
-
describe('
|
|
17
|
+
describe('fetch', () => {
|
|
37
18
|
let singletonServices: any
|
|
38
19
|
let createSessionServices: any
|
|
39
20
|
let request: any
|
|
@@ -51,8 +32,8 @@ describe('runHTTPRoute', () => {
|
|
|
51
32
|
}
|
|
52
33
|
|
|
53
34
|
createSessionServices = async () => ({})
|
|
54
|
-
request = new
|
|
55
|
-
response = new
|
|
35
|
+
request = new PikkuMockRequest('/test', 'get')
|
|
36
|
+
response = new PikkuMockResponse()
|
|
56
37
|
|
|
57
38
|
request.getData = async () => ({})
|
|
58
39
|
request.getHeader = () => 'application/json'
|
|
@@ -63,18 +44,11 @@ describe('runHTTPRoute', () => {
|
|
|
63
44
|
afterEach(() => {})
|
|
64
45
|
|
|
65
46
|
test('should throw RouteNotFoundError when no matching route is found', async () => {
|
|
66
|
-
const apiRoute = '/test'
|
|
67
|
-
const apiType = 'get'
|
|
68
|
-
|
|
69
47
|
await assert.rejects(
|
|
70
48
|
async () =>
|
|
71
|
-
|
|
72
|
-
request,
|
|
73
|
-
response,
|
|
49
|
+
fetch(request, {
|
|
74
50
|
singletonServices,
|
|
75
51
|
createSessionServices,
|
|
76
|
-
route: apiRoute,
|
|
77
|
-
method: apiType,
|
|
78
52
|
bubbleErrors: true,
|
|
79
53
|
}),
|
|
80
54
|
NotFoundError
|
|
@@ -82,8 +56,6 @@ describe('runHTTPRoute', () => {
|
|
|
82
56
|
})
|
|
83
57
|
|
|
84
58
|
test('should call the route function and return its result when a matching route is found', async () => {
|
|
85
|
-
const apiRoute = '/test'
|
|
86
|
-
const apiType = 'get'
|
|
87
59
|
const routeFunc = async () => ({ success: true })
|
|
88
60
|
addRoute({
|
|
89
61
|
route: 'test',
|
|
@@ -92,21 +64,15 @@ describe('runHTTPRoute', () => {
|
|
|
92
64
|
middleware: [sessionMiddleware],
|
|
93
65
|
})
|
|
94
66
|
|
|
95
|
-
const result = await
|
|
96
|
-
request,
|
|
97
|
-
response,
|
|
67
|
+
const result = await fetch(request, {
|
|
98
68
|
singletonServices,
|
|
99
69
|
createSessionServices,
|
|
100
|
-
route: apiRoute,
|
|
101
|
-
method: apiType,
|
|
102
70
|
})
|
|
103
71
|
|
|
104
|
-
assert.deepStrictEqual(result, { success: true })
|
|
72
|
+
assert.deepStrictEqual(await result.json(), { success: true })
|
|
105
73
|
})
|
|
106
74
|
|
|
107
75
|
test('should verify permissions if provided', async () => {
|
|
108
|
-
const apiRoute = '/test'
|
|
109
|
-
const apiType = 'get'
|
|
110
76
|
const permissions = { test: async () => true }
|
|
111
77
|
const routeFunc = async () => ({ success: true })
|
|
112
78
|
|
|
@@ -118,21 +84,15 @@ describe('runHTTPRoute', () => {
|
|
|
118
84
|
middleware: [sessionMiddleware],
|
|
119
85
|
})
|
|
120
86
|
|
|
121
|
-
await
|
|
122
|
-
request,
|
|
123
|
-
response,
|
|
87
|
+
await fetch(request, {
|
|
124
88
|
singletonServices,
|
|
125
89
|
createSessionServices,
|
|
126
|
-
route: apiRoute,
|
|
127
|
-
method: apiType,
|
|
128
90
|
})
|
|
129
91
|
|
|
130
92
|
assert.strictEqual(await permissions.test(), true)
|
|
131
93
|
})
|
|
132
94
|
|
|
133
95
|
test('should handle errors and set appropriate response', async () => {
|
|
134
|
-
const apiRoute = '/test'
|
|
135
|
-
const apiType = 'get'
|
|
136
96
|
const error = new Error('Test error')
|
|
137
97
|
const routeFunc = async () => {
|
|
138
98
|
throw error
|
|
@@ -146,13 +106,9 @@ describe('runHTTPRoute', () => {
|
|
|
146
106
|
|
|
147
107
|
await assert.rejects(
|
|
148
108
|
async () =>
|
|
149
|
-
|
|
150
|
-
request,
|
|
151
|
-
response,
|
|
109
|
+
fetch(request, {
|
|
152
110
|
singletonServices,
|
|
153
111
|
createSessionServices,
|
|
154
|
-
route: apiRoute,
|
|
155
|
-
method: apiType,
|
|
156
112
|
bubbleErrors: true,
|
|
157
113
|
}),
|
|
158
114
|
error
|
|
@@ -4,30 +4,35 @@ import {
|
|
|
4
4
|
RunRouteOptions,
|
|
5
5
|
RunRouteParams,
|
|
6
6
|
PikkuHTTP,
|
|
7
|
+
PikkuHTTPRequest,
|
|
8
|
+
PikkuHTTPResponse,
|
|
7
9
|
} from './http-routes.types.js'
|
|
8
10
|
import { PikkuMiddleware, SessionServices } from '../types/core.types.js'
|
|
9
11
|
import { match } from 'path-to-regexp'
|
|
10
|
-
import { PikkuHTTPAbstractRequest } from './pikku-http-abstract-request.js'
|
|
11
|
-
import { PikkuHTTPAbstractResponse } from './pikku-http-abstract-response.js'
|
|
12
12
|
import {
|
|
13
13
|
ForbiddenError,
|
|
14
14
|
MissingSessionError,
|
|
15
15
|
NotFoundError,
|
|
16
16
|
} from '../errors/errors.js'
|
|
17
17
|
import { closeSessionServices } from '../utils.js'
|
|
18
|
-
import { PikkuRequest } from '../pikku-request.js'
|
|
19
|
-
import { PikkuResponse } from '../pikku-response.js'
|
|
20
18
|
import { coerceQueryStringToArray, validateSchema } from '../schema.js'
|
|
21
|
-
import {
|
|
19
|
+
import { PikkuUserSessionService } from '../services/user-session-service.js'
|
|
22
20
|
import { runMiddleware } from '../middleware-runner.js'
|
|
23
21
|
import { handleError } from '../handle-error.js'
|
|
24
22
|
import { pikkuState } from '../pikku-state.js'
|
|
23
|
+
import { PikkuFetchHTTPResponse } from './pikku-fetch-http-response.js'
|
|
24
|
+
import { PikkuFetchHTTPRequest } from './pikku-fetch-http-request.js'
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
27
|
+
* Registers middleware either globally or for a specific route.
|
|
28
28
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
29
|
+
* When a string route pattern is provided along with middleware, the middleware
|
|
30
|
+
* is applied only to that route. Otherwise, if an array is provided, it is treated
|
|
31
|
+
* as global middleware (applied to all routes).
|
|
32
|
+
*
|
|
33
|
+
* @template APIMiddleware The middleware type.
|
|
34
|
+
* @param {APIMiddleware[] | string} routeOrMiddleware - Either a global middleware array or a route pattern string.
|
|
35
|
+
* @param {APIMiddleware[]} [middleware] - The middleware array to apply when a route pattern is specified.
|
|
31
36
|
*/
|
|
32
37
|
export const addMiddleware = <APIMiddleware extends PikkuMiddleware>(
|
|
33
38
|
routeOrMiddleware: APIMiddleware[] | string,
|
|
@@ -47,9 +52,19 @@ export const addMiddleware = <APIMiddleware extends PikkuMiddleware>(
|
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
/**
|
|
50
|
-
*
|
|
55
|
+
* Adds a new route to the global HTTP route registry.
|
|
56
|
+
*
|
|
57
|
+
* The route configuration includes the HTTP method, route path, permissions,
|
|
58
|
+
* middleware, and the handler function that implements the route's logic.
|
|
51
59
|
*
|
|
52
|
-
* @
|
|
60
|
+
* @template In Expected input type.
|
|
61
|
+
* @template Out Expected output type.
|
|
62
|
+
* @template Route Route pattern as a string.
|
|
63
|
+
* @template APIFunction Type for the route handler function.
|
|
64
|
+
* @template APIFunctionSessionless Type for a sessionless handler.
|
|
65
|
+
* @template APIPermission Type representing required permissions.
|
|
66
|
+
* @template APIMiddleware Middleware type to be used with the route.
|
|
67
|
+
* @param {CoreHTTPFunctionRoute<In, Out, Route, APIFunction, APIFunctionSessionless, APIPermission, APIMiddleware>} route - The route configuration object.
|
|
53
68
|
*/
|
|
54
69
|
export const addRoute = <
|
|
55
70
|
In,
|
|
@@ -74,11 +89,15 @@ export const addRoute = <
|
|
|
74
89
|
}
|
|
75
90
|
|
|
76
91
|
/**
|
|
77
|
-
*
|
|
92
|
+
* Finds a matching route based on the HTTP method and URL path.
|
|
93
|
+
*
|
|
94
|
+
* Iterates over all registered routes, skipping those with a mismatched method.
|
|
95
|
+
* When a route pattern matches the incoming request path, this function aggregates
|
|
96
|
+
* any global middleware along with route-specific middleware and identifies any input schema.
|
|
78
97
|
*
|
|
79
|
-
* @param {string} requestType - HTTP method (GET, POST
|
|
80
|
-
* @param {string} requestPath - URL path
|
|
81
|
-
* @returns {Object | undefined}
|
|
98
|
+
* @param {string} requestType - The HTTP method (e.g., GET, POST).
|
|
99
|
+
* @param {string} requestPath - The URL path of the incoming request.
|
|
100
|
+
* @returns {Object | undefined} An object with matched route details or undefined if no match.
|
|
82
101
|
*/
|
|
83
102
|
const getMatchingRoute = (requestType: string, requestPath: string) => {
|
|
84
103
|
const routes = pikkuState('http', 'routes')
|
|
@@ -86,27 +105,27 @@ const getMatchingRoute = (requestType: string, requestPath: string) => {
|
|
|
86
105
|
const routesMeta = pikkuState('http', 'meta')
|
|
87
106
|
|
|
88
107
|
for (const route of routes) {
|
|
89
|
-
// Skip routes that don't match the
|
|
108
|
+
// Skip routes that don't match the HTTP method
|
|
90
109
|
if (route.method !== requestType.toLowerCase()) {
|
|
91
110
|
continue
|
|
92
111
|
}
|
|
93
112
|
|
|
94
|
-
//
|
|
113
|
+
// Generate a matching function from the route pattern
|
|
95
114
|
const matchFunc = match(`/${route.route}`.replace(/^\/\//, '/'), {
|
|
96
115
|
decode: decodeURIComponent,
|
|
97
116
|
})
|
|
98
117
|
|
|
99
|
-
//
|
|
118
|
+
// Attempt to match the request path
|
|
100
119
|
const matchedPath = matchFunc(requestPath.replace(/^\/\//, '/'))
|
|
101
120
|
|
|
102
121
|
if (matchedPath) {
|
|
103
|
-
//
|
|
122
|
+
// Aggregate global and route-specific middleware
|
|
104
123
|
const globalMiddleware = middleware
|
|
105
124
|
.filter((m) => m.route === '*' || new RegExp(m.route).test(route.route))
|
|
106
125
|
.map((m) => m.middleware)
|
|
107
126
|
.flat()
|
|
108
127
|
|
|
109
|
-
//
|
|
128
|
+
// Extract associated schema information if available
|
|
110
129
|
const schemaName = routesMeta.find(
|
|
111
130
|
(routeMeta) =>
|
|
112
131
|
routeMeta.method === route.method && routeMeta.route === route.route
|
|
@@ -125,27 +144,27 @@ const getMatchingRoute = (requestType: string, requestPath: string) => {
|
|
|
125
144
|
}
|
|
126
145
|
|
|
127
146
|
/**
|
|
128
|
-
*
|
|
147
|
+
* Combines the request and response objects into a single HTTP interaction object.
|
|
129
148
|
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
149
|
+
* This utility function creates an object that holds both the HTTP request and response,
|
|
150
|
+
* which simplifies passing these around through middleware and route execution.
|
|
151
|
+
*
|
|
152
|
+
* @param {PikkuHTTPRequest | undefined} request - The HTTP request object.
|
|
153
|
+
* @param {PikkuHTTPResponse | undefined} response - The HTTP response object.
|
|
154
|
+
* @returns {PikkuHTTP | undefined} The combined HTTP interaction object or undefined if none provided.
|
|
133
155
|
*/
|
|
134
156
|
export const createHTTPInteraction = (
|
|
135
|
-
request:
|
|
136
|
-
response:
|
|
157
|
+
request: PikkuHTTPRequest | undefined,
|
|
158
|
+
response: PikkuHTTPResponse | undefined
|
|
137
159
|
): PikkuHTTP | undefined => {
|
|
138
160
|
let http: PikkuHTTP | undefined = undefined
|
|
139
161
|
|
|
140
|
-
if (
|
|
141
|
-
request instanceof PikkuHTTPAbstractRequest ||
|
|
142
|
-
response instanceof PikkuHTTPAbstractResponse
|
|
143
|
-
) {
|
|
162
|
+
if (request || response) {
|
|
144
163
|
http = {}
|
|
145
|
-
if (request
|
|
164
|
+
if (request) {
|
|
146
165
|
http.request = request
|
|
147
166
|
}
|
|
148
|
-
if (response
|
|
167
|
+
if (response) {
|
|
149
168
|
http.response = response
|
|
150
169
|
}
|
|
151
170
|
}
|
|
@@ -154,19 +173,29 @@ export const createHTTPInteraction = (
|
|
|
154
173
|
}
|
|
155
174
|
|
|
156
175
|
/**
|
|
157
|
-
*
|
|
176
|
+
* Validates the input data and executes the route handler with associated middleware.
|
|
177
|
+
*
|
|
178
|
+
* This function is the central execution point for a route. It performs these steps:
|
|
179
|
+
* 1. Sets URL parameters on the request.
|
|
180
|
+
* 2. Validates the user session if required.
|
|
181
|
+
* 3. Creates session-specific services.
|
|
182
|
+
* 4. Validates the incoming data against a schema.
|
|
183
|
+
* 5. Optionally coerces query string values to arrays.
|
|
184
|
+
* 6. Checks route-specific permissions.
|
|
185
|
+
* 7. Executes the route handler.
|
|
186
|
+
* 8. Sends the appropriate response.
|
|
158
187
|
*
|
|
159
|
-
* @param {Object} services -
|
|
160
|
-
* @param {Object} matchedRoute -
|
|
161
|
-
* @param {PikkuHTTP | undefined} http - HTTP interaction object
|
|
162
|
-
* @param {Object} options -
|
|
163
|
-
* @returns {Promise<any>}
|
|
188
|
+
* @param {Object} services - A collection of shared services and utilities.
|
|
189
|
+
* @param {Object} matchedRoute - Contains route details, URL parameters, middleware, and optional schema.
|
|
190
|
+
* @param {PikkuHTTP | undefined} http - The HTTP interaction object.
|
|
191
|
+
* @param {Object} options - Options for route execution (e.g., whether to coerce query strings to arrays).
|
|
192
|
+
* @returns {Promise<any>} An object containing the route handler result and session services (if any).
|
|
193
|
+
* @throws Throws errors like MissingSessionError or ForbiddenError on validation failures.
|
|
164
194
|
*/
|
|
165
195
|
const executeRouteWithMiddleware = async (
|
|
166
196
|
services: {
|
|
167
197
|
singletonServices: any
|
|
168
198
|
userSessionService: any
|
|
169
|
-
context: Map<string, unknown>
|
|
170
199
|
createSessionServices: Function
|
|
171
200
|
skipUserSession: boolean
|
|
172
201
|
},
|
|
@@ -186,7 +215,6 @@ const executeRouteWithMiddleware = async (
|
|
|
186
215
|
const {
|
|
187
216
|
singletonServices,
|
|
188
217
|
userSessionService,
|
|
189
|
-
context,
|
|
190
218
|
createSessionServices,
|
|
191
219
|
skipUserSession,
|
|
192
220
|
} = services
|
|
@@ -195,17 +223,18 @@ const executeRouteWithMiddleware = async (
|
|
|
195
223
|
let sessionServices: any
|
|
196
224
|
let result: any
|
|
197
225
|
|
|
226
|
+
// Attach URL parameters to the request object
|
|
198
227
|
http?.request?.setParams(params)
|
|
199
228
|
|
|
200
229
|
singletonServices.logger.info(
|
|
201
230
|
`Matched route: ${route.route} | method: ${route.method.toUpperCase()} | auth: ${requiresSession.toString()}`
|
|
202
231
|
)
|
|
203
232
|
|
|
204
|
-
// Main route execution
|
|
233
|
+
// Main route execution logic wrapped for middleware handling
|
|
205
234
|
const runMain = async () => {
|
|
206
235
|
const session = userSessionService.get()
|
|
207
236
|
|
|
208
|
-
//
|
|
237
|
+
// Ensure session is available when required
|
|
209
238
|
if (skipUserSession && requiresSession) {
|
|
210
239
|
throw new Error(
|
|
211
240
|
"Can't skip trying to get user session if auth is required"
|
|
@@ -220,9 +249,9 @@ const executeRouteWithMiddleware = async (
|
|
|
220
249
|
throw new MissingSessionError()
|
|
221
250
|
}
|
|
222
251
|
|
|
223
|
-
// Create session services
|
|
252
|
+
// Create session-specific services for handling the request
|
|
224
253
|
sessionServices = await createSessionServices(
|
|
225
|
-
{ ...singletonServices,
|
|
254
|
+
{ ...singletonServices, userSessionService },
|
|
226
255
|
{ http },
|
|
227
256
|
session
|
|
228
257
|
)
|
|
@@ -231,23 +260,23 @@ const executeRouteWithMiddleware = async (
|
|
|
231
260
|
...singletonServices,
|
|
232
261
|
...sessionServices,
|
|
233
262
|
http,
|
|
234
|
-
context,
|
|
235
263
|
}
|
|
236
|
-
const data = await http?.request?.
|
|
264
|
+
const data = await http?.request?.data()
|
|
237
265
|
|
|
238
|
-
// Validate schema
|
|
239
|
-
validateSchema(
|
|
266
|
+
// Validate request data against the defined schema, if any
|
|
267
|
+
await validateSchema(
|
|
240
268
|
singletonServices.logger,
|
|
241
269
|
singletonServices.schemaService,
|
|
242
270
|
schemaName,
|
|
243
271
|
data
|
|
244
272
|
)
|
|
245
273
|
|
|
274
|
+
// Coerce query string parameters to arrays if specified by the schema
|
|
246
275
|
if (options.coerceToArray && schemaName) {
|
|
247
276
|
coerceQueryStringToArray(schemaName, data)
|
|
248
277
|
}
|
|
249
278
|
|
|
250
|
-
//
|
|
279
|
+
// Execute permission checks
|
|
251
280
|
const permissioned = await verifyPermissions(
|
|
252
281
|
route.permissions,
|
|
253
282
|
allServices,
|
|
@@ -259,24 +288,26 @@ const executeRouteWithMiddleware = async (
|
|
|
259
288
|
throw new ForbiddenError('Permission denied')
|
|
260
289
|
}
|
|
261
290
|
|
|
262
|
-
//
|
|
291
|
+
// Invoke the actual route handler function
|
|
263
292
|
result = await route.func(allServices, data, session!)
|
|
264
293
|
|
|
265
|
-
//
|
|
294
|
+
// Respond with either a binary or JSON response based on configuration
|
|
266
295
|
if (route.returnsJSON === false) {
|
|
267
|
-
http?.response?.
|
|
296
|
+
http?.response?.arrayBuffer(result)
|
|
268
297
|
} else {
|
|
269
|
-
http?.response?.
|
|
298
|
+
http?.response?.json(result)
|
|
270
299
|
}
|
|
271
300
|
|
|
272
|
-
http?.response?.
|
|
273
|
-
|
|
301
|
+
http?.response?.status(200)
|
|
302
|
+
// TODO: Evaluate if the response stream should be explicitly ended.
|
|
303
|
+
// http?.response?.end()
|
|
274
304
|
|
|
275
305
|
return result
|
|
276
306
|
}
|
|
277
307
|
|
|
308
|
+
// Execute middleware, then run the main logic
|
|
278
309
|
await runMiddleware(
|
|
279
|
-
{ ...singletonServices,
|
|
310
|
+
{ ...singletonServices, userSessionService },
|
|
280
311
|
{ http },
|
|
281
312
|
middleware,
|
|
282
313
|
runMain
|
|
@@ -286,41 +317,98 @@ const executeRouteWithMiddleware = async (
|
|
|
286
317
|
}
|
|
287
318
|
|
|
288
319
|
/**
|
|
289
|
-
*
|
|
320
|
+
* Executes an HTTP route for a given Fetch API request.
|
|
321
|
+
*
|
|
322
|
+
* This function wraps the entire lifecycle of handling an HTTP request:
|
|
323
|
+
* - Matching the request to a registered route.
|
|
324
|
+
* - Validating input and session state.
|
|
325
|
+
* - Running middleware and the route handler.
|
|
326
|
+
* - Handling errors and forming the response.
|
|
290
327
|
*
|
|
291
|
-
* @
|
|
292
|
-
* @
|
|
293
|
-
* @
|
|
328
|
+
* @template In Expected input data type.
|
|
329
|
+
* @template Out Expected output data type.
|
|
330
|
+
* @param {Request} request - The native Fetch API Request object.
|
|
331
|
+
* @param {RunRouteOptions & RunRouteParams} params - Additional options including services and session management.
|
|
332
|
+
* @returns {Promise<Response>} A promise that resolves to a Fetch API Response object.
|
|
294
333
|
*/
|
|
295
|
-
export const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
334
|
+
export const fetch = async <In, Out>(
|
|
335
|
+
request: Request,
|
|
336
|
+
params: RunRouteOptions & RunRouteParams
|
|
337
|
+
): Promise<Response> => {
|
|
338
|
+
const pikkuResponse = new PikkuFetchHTTPResponse()
|
|
339
|
+
await fetchData<In, Out>(request, pikkuResponse, params)
|
|
340
|
+
return pikkuResponse.toResponse()
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Executes an HTTP route using a Pikku-specific request wrapper.
|
|
345
|
+
*
|
|
346
|
+
* This variant accepts either a native Request or a PikkuHTTPRequest object and returns
|
|
347
|
+
* a PikkuFetchHTTPResponse for further manipulation if needed.
|
|
348
|
+
*
|
|
349
|
+
* @template In Expected input data type.
|
|
350
|
+
* @template Out Expected output data type.
|
|
351
|
+
* @param {Request | PikkuHTTPRequest} request - The request object.
|
|
352
|
+
* @param {RunRouteOptions & RunRouteParams} params - Execution options including services and session configuration.
|
|
353
|
+
* @returns {Promise<PikkuFetchHTTPResponse>} A promise that resolves to a PikkuFetchHTTPResponse object.
|
|
354
|
+
*/
|
|
355
|
+
export const pikkuFetch = async <In, Out>(
|
|
356
|
+
request: Request | PikkuHTTPRequest,
|
|
357
|
+
params: RunRouteOptions & RunRouteParams
|
|
358
|
+
): Promise<PikkuFetchHTTPResponse> => {
|
|
359
|
+
const pikkuResponse = new PikkuFetchHTTPResponse()
|
|
360
|
+
await fetchData<In, Out>(request, pikkuResponse, params)
|
|
361
|
+
return pikkuResponse
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Core function to process an HTTP request through route matching, validation,
|
|
366
|
+
* middleware execution, error handling, and session service cleanup.
|
|
367
|
+
*
|
|
368
|
+
* This function does the following:
|
|
369
|
+
* - Wraps the incoming request and response into an HTTP interaction object.
|
|
370
|
+
* - Determines the correct route based on HTTP method and path.
|
|
371
|
+
* - Executes middleware and the route handler.
|
|
372
|
+
* - Catches and handles errors, optionally bubbling them if configured.
|
|
373
|
+
* - Cleans up any session services created during processing.
|
|
374
|
+
*
|
|
375
|
+
* @template In Expected input data type.
|
|
376
|
+
* @template Out Expected output data type.
|
|
377
|
+
* @param {Request | PikkuHTTPRequest} request - The incoming HTTP request.
|
|
378
|
+
* @param {PikkuHTTPResponse} response - The response object to be populated.
|
|
379
|
+
* @param {RunRouteOptions & RunRouteParams} options - Options such as singleton services, session handling, and error configuration.
|
|
380
|
+
* @returns {Promise<Out | void>} The output from the route handler or void if an error occurred.
|
|
381
|
+
*/
|
|
382
|
+
export const fetchData = async <In, Out>(
|
|
383
|
+
request: Request | PikkuHTTPRequest,
|
|
384
|
+
response: PikkuHTTPResponse,
|
|
385
|
+
{
|
|
386
|
+
singletonServices,
|
|
387
|
+
createSessionServices,
|
|
388
|
+
skipUserSession = false,
|
|
389
|
+
respondWith404 = true,
|
|
390
|
+
logWarningsForStatusCodes = [],
|
|
391
|
+
coerceToArray = false,
|
|
392
|
+
bubbleErrors = false,
|
|
393
|
+
}: RunRouteOptions & RunRouteParams
|
|
394
|
+
): Promise<Out | void> => {
|
|
395
|
+
const userSessionService = new PikkuUserSessionService()
|
|
313
396
|
let sessionServices: SessionServices<typeof singletonServices> | undefined
|
|
314
397
|
let result: Out
|
|
315
398
|
|
|
316
|
-
//
|
|
317
|
-
const http = createHTTPInteraction(
|
|
399
|
+
// Combine the request and response into one interaction object
|
|
400
|
+
const http = createHTTPInteraction(
|
|
401
|
+
request instanceof Request ? new PikkuFetchHTTPRequest(request) : request,
|
|
402
|
+
response
|
|
403
|
+
)
|
|
404
|
+
const apiType = http!.request!.method()
|
|
405
|
+
const apiRoute = http!.request!.path()
|
|
318
406
|
|
|
319
|
-
//
|
|
407
|
+
// Locate the matching route based on the HTTP method and path
|
|
320
408
|
const matchedRoute = getMatchingRoute(apiType, apiRoute)
|
|
321
409
|
|
|
322
410
|
try {
|
|
323
|
-
//
|
|
411
|
+
// If no route matches, log the occurrence and throw a NotFoundError
|
|
324
412
|
if (!matchedRoute) {
|
|
325
413
|
singletonServices.logger.info({
|
|
326
414
|
message: 'Route not found',
|
|
@@ -330,12 +418,11 @@ export const runHTTPRoute = async <In, Out>({
|
|
|
330
418
|
throw new NotFoundError(`Route not found: ${apiRoute}`)
|
|
331
419
|
}
|
|
332
420
|
|
|
333
|
-
// Execute route with middleware
|
|
421
|
+
// Execute the matched route along with its middleware and session management
|
|
334
422
|
;({ result, sessionServices } = await executeRouteWithMiddleware(
|
|
335
423
|
{
|
|
336
424
|
singletonServices,
|
|
337
425
|
userSessionService,
|
|
338
|
-
context,
|
|
339
426
|
createSessionServices,
|
|
340
427
|
skipUserSession,
|
|
341
428
|
},
|
|
@@ -346,18 +433,18 @@ export const runHTTPRoute = async <In, Out>({
|
|
|
346
433
|
|
|
347
434
|
return result
|
|
348
435
|
} catch (e: any) {
|
|
349
|
-
// Handle and
|
|
436
|
+
// Handle errors and, depending on configuration, bubble them up or respond with an error
|
|
350
437
|
handleError(
|
|
351
438
|
e,
|
|
352
439
|
http,
|
|
353
|
-
context.get('trackingId'),
|
|
440
|
+
'111', // TODO: context.get('trackingId'),
|
|
354
441
|
singletonServices.logger,
|
|
355
442
|
logWarningsForStatusCodes,
|
|
356
443
|
respondWith404,
|
|
357
444
|
bubbleErrors
|
|
358
445
|
)
|
|
359
446
|
} finally {
|
|
360
|
-
// Clean up session services
|
|
447
|
+
// Clean up any session-specific services created during processing
|
|
361
448
|
if (sessionServices) {
|
|
362
449
|
await closeSessionServices(singletonServices.logger, sessionServices)
|
|
363
450
|
}
|