@pikku/core 0.6.21 → 0.6.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/channel/channel-handler.d.ts +2 -2
  3. package/dist/channel/channel-handler.js +49 -26
  4. package/dist/channel/channel-runner.d.ts +3 -3
  5. package/dist/channel/channel-runner.js +4 -4
  6. package/dist/channel/channel.types.d.ts +12 -19
  7. package/dist/channel/local/local-channel-runner.d.ts +1 -1
  8. package/dist/channel/local/local-channel-runner.js +14 -8
  9. package/dist/channel/serverless/serverless-channel-runner.js +12 -7
  10. package/dist/handle-error.js +8 -6
  11. package/dist/http/http-route-runner.d.ts +75 -18
  12. package/dist/http/http-route-runner.js +154 -64
  13. package/dist/http/http-routes.types.d.ts +24 -9
  14. package/dist/http/incomingmessage-to-request-convertor.d.ts +1 -0
  15. package/dist/http/incomingmessage-to-request-convertor.js +1 -0
  16. package/dist/http/index.d.ts +4 -3
  17. package/dist/http/index.js +4 -3
  18. package/dist/http/{pikku-http-abstract-request.d.ts → pikku-fetch-http-request.d.ts} +14 -20
  19. package/dist/http/pikku-fetch-http-request.js +92 -0
  20. package/dist/http/pikku-fetch-http-response.d.ts +14 -0
  21. package/dist/http/pikku-fetch-http-response.js +59 -0
  22. package/dist/middleware/auth-apikey.js +4 -4
  23. package/dist/middleware/auth-bearer.js +4 -4
  24. package/dist/middleware/auth-cookie.js +16 -25
  25. package/dist/middleware-runner.d.ts +1 -1
  26. package/dist/pikku-request.d.ts +3 -3
  27. package/dist/pikku-request.js +5 -5
  28. package/dist/scheduler/scheduler-runner.d.ts +1 -1
  29. package/dist/services/user-session-service.d.ts +9 -14
  30. package/dist/services/user-session-service.js +22 -22
  31. package/dist/types/core.types.d.ts +1 -1
  32. package/dist/types/functions.types.d.ts +7 -2
  33. package/lcov.info +1543 -1420
  34. package/package.json +3 -4
  35. package/src/channel/channel-handler.ts +82 -29
  36. package/src/channel/channel-runner.ts +6 -6
  37. package/src/channel/channel.types.ts +19 -23
  38. package/src/channel/local/local-channel-runner.test.ts +68 -23
  39. package/src/channel/local/local-channel-runner.ts +23 -9
  40. package/src/channel/serverless/serverless-channel-runner.ts +15 -6
  41. package/src/handle-error.ts +8 -6
  42. package/src/http/http-route-runner.test.ts +13 -57
  43. package/src/http/http-route-runner.ts +184 -92
  44. package/src/http/http-routes.types.ts +26 -9
  45. package/src/http/incomingmessage-to-request-convertor.ts +0 -0
  46. package/src/http/index.ts +4 -5
  47. package/src/http/{pikku-http-abstract-request.ts → pikku-fetch-http-request.ts} +48 -39
  48. package/src/http/pikku-fetch-http-response.ts +70 -0
  49. package/src/middleware/auth-apikey.ts +4 -4
  50. package/src/middleware/auth-bearer.ts +4 -4
  51. package/src/middleware/auth-cookie.ts +20 -28
  52. package/src/middleware-runner.ts +1 -1
  53. package/src/pikku-request.ts +8 -4
  54. package/src/scheduler/scheduler-runner.ts +0 -2
  55. package/src/services/user-session-service.ts +26 -28
  56. package/src/types/core.types.ts +1 -1
  57. package/src/types/functions.types.ts +19 -4
  58. package/tsconfig.tsbuildinfo +1 -1
  59. package/dist/http/pikku-http-abstract-request.js +0 -76
  60. package/dist/http/pikku-http-abstract-response.d.ts +0 -58
  61. package/dist/http/pikku-http-abstract-response.js +0 -54
  62. package/src/http/pikku-http-abstract-response.ts +0 -79
@@ -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 { runHTTPRoute, addRoute } from './http-route-runner.js'
5
+ import { fetch, addRoute } from './http-route-runner.js'
8
6
  import { resetPikkuState } from '../pikku-state.js'
9
-
10
- class PikkuTestRequest extends PikkuHTTPAbstractRequest {
11
- public getBody(): Promise<unknown> {
12
- throw new Error('Method not implemented.')
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('runHTTPRoute', () => {
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 PikkuTestRequest('path', 'get')
55
- response = new PikkuTestResponse()
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
- runHTTPRoute({
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 runHTTPRoute({
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 runHTTPRoute({
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
- runHTTPRoute({
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 { LocalUserSessionService } from '../services/user-session-service.js'
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
- * Add middleware to a specific route or globally
27
+ * Registers middleware either globally or for a specific route.
28
28
  *
29
- * @param {APIMiddleware[] | string} routeOrMiddleware - Route pattern to match or middleware array
30
- * @param {APIMiddleware} [middleware] - Middleware to add (required if first param is a string)
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
- * Add a route to the global routes registry
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
- * @param {CoreHTTPFunctionRoute} route - Route configuration to add
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
- * Find a route that matches the given request type and path
92
+ * Finds a matching route based on the HTTP method and URL path.
78
93
  *
79
- * @param {string} requestType - HTTP method (GET, POST, etc.)
80
- * @param {string} requestPath - URL path to match
81
- * @returns {Object | undefined} Matching route information or undefined if no match
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.
97
+ *
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 request method
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
- // Create path matcher function
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
- // Try to match the path
118
+ // Attempt to match the request path
100
119
  const matchedPath = matchFunc(requestPath.replace(/^\/\//, '/'))
101
120
 
102
121
  if (matchedPath) {
103
- // Get all middleware for this route
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
- // Find schema for this route
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
- * Create an HTTP interaction object from request and response
147
+ * Combines the request and response objects into a single HTTP interaction object.
148
+ *
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.
129
151
  *
130
- * @param {PikkuRequest | undefined} request - The HTTP request object
131
- * @param {PikkuResponse | undefined} response - The HTTP response object
132
- * @returns {PikkuHTTP | undefined} HTTP interaction object or undefined
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: PikkuRequest | undefined,
136
- response: PikkuResponse | undefined
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 instanceof PikkuHTTPAbstractRequest) {
164
+ if (request) {
146
165
  http.request = request
147
166
  }
148
- if (response instanceof PikkuHTTPAbstractResponse) {
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
- * Validate input data and execute route handler with appropriate middleware
176
+ * Validates the input data and executes the route handler with associated middleware.
158
177
  *
159
- * @param {Object} services - Available services
160
- * @param {Object} matchedRoute - Information about the matched route
161
- * @param {PikkuHTTP | undefined} http - HTTP interaction object
162
- * @param {Object} options - Additional options
163
- * @returns {Promise<any>} Result of the route handler
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.
187
+ *
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 function
233
+ // Main route execution logic wrapped for middleware handling
205
234
  const runMain = async () => {
206
235
  const session = userSessionService.get()
207
236
 
208
- // Validate session was set if needed
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, context },
254
+ { ...singletonServices, userSessionService },
226
255
  { http },
227
256
  session
228
257
  )
@@ -231,23 +260,28 @@ const executeRouteWithMiddleware = async (
231
260
  ...singletonServices,
232
261
  ...sessionServices,
233
262
  http,
234
- context,
235
263
  }
236
- const data = await http?.request?.getData()
237
-
238
- // Validate schema
239
- validateSchema(
240
- singletonServices.logger,
241
- singletonServices.schemaService,
242
- schemaName,
243
- data
244
- )
264
+ const data = await http?.request?.data()
265
+
266
+ // Validate request data against the defined schema, if any
267
+ try {
268
+ console.log(schemaName, data)
269
+ validateSchema(
270
+ singletonServices.logger,
271
+ singletonServices.schemaService,
272
+ schemaName,
273
+ data
274
+ )
275
+ } catch (e) {
276
+ // TODO: Implement proper handling for schema validation failures
277
+ }
245
278
 
279
+ // Coerce query string parameters to arrays if specified by the schema
246
280
  if (options.coerceToArray && schemaName) {
247
281
  coerceQueryStringToArray(schemaName, data)
248
282
  }
249
283
 
250
- // Run permission checks
284
+ // Execute permission checks
251
285
  const permissioned = await verifyPermissions(
252
286
  route.permissions,
253
287
  allServices,
@@ -259,24 +293,26 @@ const executeRouteWithMiddleware = async (
259
293
  throw new ForbiddenError('Permission denied')
260
294
  }
261
295
 
262
- // Execute the route handler
296
+ // Invoke the actual route handler function
263
297
  result = await route.func(allServices, data, session!)
264
298
 
265
- // Set the response
299
+ // Respond with either a binary or JSON response based on configuration
266
300
  if (route.returnsJSON === false) {
267
- http?.response?.setResponse(result)
301
+ http?.response?.arrayBuffer(result)
268
302
  } else {
269
- http?.response?.setJson(result)
303
+ http?.response?.json(result)
270
304
  }
271
305
 
272
- http?.response?.setStatus(200)
273
- http?.response?.end()
306
+ http?.response?.status(200)
307
+ // TODO: Evaluate if the response stream should be explicitly ended.
308
+ // http?.response?.end()
274
309
 
275
310
  return result
276
311
  }
277
312
 
313
+ // Execute middleware, then run the main logic
278
314
  await runMiddleware(
279
- { ...singletonServices, context, userSessionService },
315
+ { ...singletonServices, userSessionService },
280
316
  { http },
281
317
  middleware,
282
318
  runMain
@@ -286,41 +322,98 @@ const executeRouteWithMiddleware = async (
286
322
  }
287
323
 
288
324
  /**
289
- * Run an HTTP route with the given parameters
325
+ * Executes an HTTP route for a given Fetch API request.
290
326
  *
291
- * @param {Object} options - Options for running the route
292
- * @returns {Promise<Out | void>} Result of the route handler
293
- * @ignore
327
+ * This function wraps the entire lifecycle of handling an HTTP request:
328
+ * - Matching the request to a registered route.
329
+ * - Validating input and session state.
330
+ * - Running middleware and the route handler.
331
+ * - Handling errors and forming the response.
332
+ *
333
+ * @template In Expected input data type.
334
+ * @template Out Expected output data type.
335
+ * @param {Request} request - The native Fetch API Request object.
336
+ * @param {RunRouteOptions & RunRouteParams} params - Additional options including services and session management.
337
+ * @returns {Promise<Response>} A promise that resolves to a Fetch API Response object.
294
338
  */
295
- export const runHTTPRoute = async <In, Out>({
296
- singletonServices,
297
- request,
298
- response,
299
- createSessionServices,
300
- route: apiRoute,
301
- method: apiType,
302
- skipUserSession = false,
303
- respondWith404 = true,
304
- logWarningsForStatusCodes = [],
305
- coerceToArray = false,
306
- bubbleErrors = false,
307
- }: Pick<CoreHTTPFunctionRoute<unknown, unknown, any>, 'route' | 'method'> &
308
- RunRouteOptions &
309
- RunRouteParams<In>): Promise<Out | void> => {
310
- const context = new Map()
311
-
312
- const userSessionService = new LocalUserSessionService()
339
+ export const fetch = async <In, Out>(
340
+ request: Request,
341
+ params: RunRouteOptions & RunRouteParams
342
+ ): Promise<Response> => {
343
+ const pikkuResponse = new PikkuFetchHTTPResponse()
344
+ await fetchData<In, Out>(request, pikkuResponse, params)
345
+ return pikkuResponse.toResponse()
346
+ }
347
+
348
+ /**
349
+ * Executes an HTTP route using a Pikku-specific request wrapper.
350
+ *
351
+ * This variant accepts either a native Request or a PikkuHTTPRequest object and returns
352
+ * a PikkuFetchHTTPResponse for further manipulation if needed.
353
+ *
354
+ * @template In Expected input data type.
355
+ * @template Out Expected output data type.
356
+ * @param {Request | PikkuHTTPRequest} request - The request object.
357
+ * @param {RunRouteOptions & RunRouteParams} params - Execution options including services and session configuration.
358
+ * @returns {Promise<PikkuFetchHTTPResponse>} A promise that resolves to a PikkuFetchHTTPResponse object.
359
+ */
360
+ export const pikkuFetch = async <In, Out>(
361
+ request: Request | PikkuHTTPRequest,
362
+ params: RunRouteOptions & RunRouteParams
363
+ ): Promise<PikkuFetchHTTPResponse> => {
364
+ const pikkuResponse = new PikkuFetchHTTPResponse()
365
+ await fetchData<In, Out>(request, pikkuResponse, params)
366
+ return pikkuResponse
367
+ }
368
+
369
+ /**
370
+ * Core function to process an HTTP request through route matching, validation,
371
+ * middleware execution, error handling, and session service cleanup.
372
+ *
373
+ * This function does the following:
374
+ * - Wraps the incoming request and response into an HTTP interaction object.
375
+ * - Determines the correct route based on HTTP method and path.
376
+ * - Executes middleware and the route handler.
377
+ * - Catches and handles errors, optionally bubbling them if configured.
378
+ * - Cleans up any session services created during processing.
379
+ *
380
+ * @template In Expected input data type.
381
+ * @template Out Expected output data type.
382
+ * @param {Request | PikkuHTTPRequest} request - The incoming HTTP request.
383
+ * @param {PikkuHTTPResponse} response - The response object to be populated.
384
+ * @param {RunRouteOptions & RunRouteParams} options - Options such as singleton services, session handling, and error configuration.
385
+ * @returns {Promise<Out | void>} The output from the route handler or void if an error occurred.
386
+ */
387
+ export const fetchData = async <In, Out>(
388
+ request: Request | PikkuHTTPRequest,
389
+ response: PikkuHTTPResponse,
390
+ {
391
+ singletonServices,
392
+ createSessionServices,
393
+ skipUserSession = false,
394
+ respondWith404 = true,
395
+ logWarningsForStatusCodes = [],
396
+ coerceToArray = false,
397
+ bubbleErrors = false,
398
+ }: RunRouteOptions & RunRouteParams
399
+ ): Promise<Out | void> => {
400
+ const userSessionService = new PikkuUserSessionService()
313
401
  let sessionServices: SessionServices<typeof singletonServices> | undefined
314
402
  let result: Out
315
403
 
316
- // Create HTTP interaction object
317
- const http = createHTTPInteraction(request, response)
404
+ // Combine the request and response into one interaction object
405
+ const http = createHTTPInteraction(
406
+ request instanceof Request ? new PikkuFetchHTTPRequest(request) : request,
407
+ response
408
+ )
409
+ const apiType = http!.request!.method()
410
+ const apiRoute = http!.request!.path()
318
411
 
319
- // Find matching route
412
+ // Locate the matching route based on the HTTP method and path
320
413
  const matchedRoute = getMatchingRoute(apiType, apiRoute)
321
414
 
322
415
  try {
323
- // Handle route not found
416
+ // If no route matches, log the occurrence and throw a NotFoundError
324
417
  if (!matchedRoute) {
325
418
  singletonServices.logger.info({
326
419
  message: 'Route not found',
@@ -330,12 +423,11 @@ export const runHTTPRoute = async <In, Out>({
330
423
  throw new NotFoundError(`Route not found: ${apiRoute}`)
331
424
  }
332
425
 
333
- // Execute route with middleware
426
+ // Execute the matched route along with its middleware and session management
334
427
  ;({ result, sessionServices } = await executeRouteWithMiddleware(
335
428
  {
336
429
  singletonServices,
337
430
  userSessionService,
338
- context,
339
431
  createSessionServices,
340
432
  skipUserSession,
341
433
  },
@@ -346,18 +438,18 @@ export const runHTTPRoute = async <In, Out>({
346
438
 
347
439
  return result
348
440
  } catch (e: any) {
349
- // Handle and possibly bubble the error
441
+ // Handle errors and, depending on configuration, bubble them up or respond with an error
350
442
  handleError(
351
443
  e,
352
444
  http,
353
- context.get('trackingId'),
445
+ '111', // TODO: context.get('trackingId'),
354
446
  singletonServices.logger,
355
447
  logWarningsForStatusCodes,
356
448
  respondWith404,
357
449
  bubbleErrors
358
450
  )
359
451
  } finally {
360
- // Clean up session services
452
+ // Clean up any session-specific services created during processing
361
453
  if (sessionServices) {
362
454
  await closeSessionServices(singletonServices.logger, sessionServices)
363
455
  }
@@ -1,3 +1,4 @@
1
+ import { SerializeOptions } from 'cookie'
1
2
  import { PikkuError } from '../errors/error-handler.js'
2
3
  import {
3
4
  APIDocs,
@@ -12,10 +13,6 @@ import {
12
13
  CoreAPIFunctionSessionless,
13
14
  CoreAPIPermission,
14
15
  } from '../types/functions.types.js'
15
- import { PikkuRequest } from '../pikku-request.js'
16
- import { PikkuResponse } from '../pikku-response.js'
17
- import { PikkuHTTPAbstractRequest } from './pikku-http-abstract-request.js'
18
- import { PikkuHTTPAbstractResponse } from './pikku-http-abstract-response.js'
19
16
 
20
17
  type ExtractRouteParams<S extends string> =
21
18
  S extends `${string}:${infer Param}/${infer Rest}`
@@ -37,10 +34,8 @@ export type RunRouteOptions = Partial<{
37
34
  bubbleErrors: boolean
38
35
  }>
39
36
 
40
- export type RunRouteParams<In> = {
37
+ export type RunRouteParams = {
41
38
  singletonServices: CoreSingletonServices
42
- request: PikkuRequest<In> | PikkuHTTPAbstractRequest<In>
43
- response?: PikkuResponse | PikkuHTTPAbstractResponse | undefined
44
39
  createSessionServices: CreateSessionServices<
45
40
  CoreSingletonServices,
46
41
  CoreServices<CoreSingletonServices>,
@@ -80,8 +75,8 @@ export type CoreHTTPFunction = {
80
75
  * Represents a http interaction within Pikku, including a request and response.
81
76
  */
82
77
  export interface PikkuHTTP {
83
- request?: PikkuHTTPAbstractRequest
84
- response?: PikkuHTTPAbstractResponse
78
+ request?: PikkuHTTPRequest
79
+ response?: PikkuHTTPResponse
85
80
  }
86
81
 
87
82
  /**
@@ -192,3 +187,25 @@ export type HTTPRouteMiddleware = {
192
187
  route: string
193
188
  middleware: PikkuMiddleware[]
194
189
  }
190
+
191
+ export interface PikkuHTTPRequest<In = unknown> {
192
+ method(): HTTPMethod
193
+ path(): string
194
+ data(): Promise<In>
195
+ json(): Promise<unknown>
196
+ arrayBuffer(): Promise<ArrayBuffer>
197
+ header(headerName: string): string | null
198
+ cookie(name?: string): string | null
199
+ params(): Partial<Record<string, string | string[]>>
200
+ setParams(params: Record<string, string | string[] | undefined>): void
201
+ query(): PikkuQuery
202
+ }
203
+
204
+ export interface PikkuHTTPResponse {
205
+ status(code: number): this
206
+ cookie(name: string, value: string | null, options: SerializeOptions): this
207
+ header(name: string, value: string | string[]): this
208
+ arrayBuffer(data: XMLHttpRequestBodyInit): this
209
+ json(data: unknown): this
210
+ redirect(location: string, status?: number): this
211
+ }