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