@pikku/core 0.6.13 → 0.6.15

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 (82) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/channel/channel-handler.d.ts +2 -2
  3. package/dist/channel/channel-handler.js +4 -4
  4. package/dist/channel/channel-runner.d.ts +5 -4
  5. package/dist/channel/channel-runner.js +8 -14
  6. package/dist/channel/channel.types.d.ts +11 -18
  7. package/dist/channel/local/local-channel-handler.d.ts +1 -3
  8. package/dist/channel/local/local-channel-handler.js +0 -3
  9. package/dist/channel/local/local-channel-runner.js +49 -33
  10. package/dist/channel/pikku-abstract-channel-handler.d.ts +4 -7
  11. package/dist/channel/pikku-abstract-channel-handler.js +1 -5
  12. package/dist/channel/serverless/serverless-channel-runner.js +45 -38
  13. package/dist/handle-error.d.ts +14 -0
  14. package/dist/handle-error.js +53 -0
  15. package/dist/http/http-route-runner.d.ts +34 -11
  16. package/dist/http/http-route-runner.js +177 -106
  17. package/dist/http/http-routes.types.d.ts +15 -9
  18. package/dist/http/index.d.ts +0 -1
  19. package/dist/http/index.js +0 -1
  20. package/dist/http/pikku-http-abstract-request.d.ts +4 -1
  21. package/dist/http/pikku-http-abstract-request.js +7 -0
  22. package/dist/index.d.ts +2 -0
  23. package/dist/index.js +2 -0
  24. package/dist/middleware/auth-api-key.d.ts +9 -0
  25. package/dist/middleware/auth-api-key.js +23 -0
  26. package/dist/middleware/auth-cookie-middleware.d.ts +11 -0
  27. package/dist/middleware/auth-cookie-middleware.js +36 -0
  28. package/dist/middleware/auth-jwt-middleware.d.ts +10 -0
  29. package/dist/middleware/auth-jwt-middleware.js +32 -0
  30. package/dist/middleware/auth-query-middleware.d.ts +10 -0
  31. package/dist/middleware/auth-query-middleware.js +21 -0
  32. package/dist/middleware/index.d.ts +4 -0
  33. package/dist/middleware/index.js +4 -0
  34. package/dist/middleware-runner.d.ts +22 -0
  35. package/dist/middleware-runner.js +28 -0
  36. package/dist/scheduler/scheduler.types.d.ts +2 -0
  37. package/dist/schema.d.ts +1 -1
  38. package/dist/schema.js +3 -6
  39. package/dist/services/index.d.ts +1 -0
  40. package/dist/services/index.js +1 -0
  41. package/dist/services/user-session-service.d.ts +20 -3
  42. package/dist/services/user-session-service.js +35 -1
  43. package/dist/types/core.types.d.ts +11 -15
  44. package/dist/types/functions.types.d.ts +4 -4
  45. package/lcov.info +1572 -1729
  46. package/package.json +1 -2
  47. package/src/channel/channel-handler.ts +6 -11
  48. package/src/channel/channel-runner.ts +13 -32
  49. package/src/channel/channel.types.ts +13 -39
  50. package/src/channel/local/local-channel-handler.ts +1 -7
  51. package/src/channel/local/local-channel-runner.test.ts +2 -5
  52. package/src/channel/local/local-channel-runner.ts +72 -54
  53. package/src/channel/pikku-abstract-channel-handler.test.ts +3 -28
  54. package/src/channel/pikku-abstract-channel-handler.ts +3 -9
  55. package/src/channel/serverless/serverless-channel-runner.ts +68 -56
  56. package/src/handle-error.ts +67 -0
  57. package/src/http/http-route-runner.test.ts +12 -45
  58. package/src/http/http-route-runner.ts +256 -182
  59. package/src/http/http-routes.types.ts +15 -10
  60. package/src/http/index.ts +0 -2
  61. package/src/http/pikku-http-abstract-request.ts +8 -1
  62. package/src/index.ts +2 -0
  63. package/src/middleware/auth-api-key.ts +32 -0
  64. package/src/middleware/auth-cookie-middleware.ts +54 -0
  65. package/src/middleware/auth-jwt-middleware.ts +47 -0
  66. package/src/middleware/auth-query-middleware.ts +33 -0
  67. package/src/middleware/index.ts +4 -0
  68. package/src/middleware-runner.ts +43 -0
  69. package/src/scheduler/scheduler.types.ts +2 -0
  70. package/src/schema.ts +4 -8
  71. package/src/services/index.ts +1 -0
  72. package/src/services/user-session-service.ts +45 -3
  73. package/src/types/core.types.ts +19 -18
  74. package/src/types/functions.types.ts +11 -7
  75. package/tsconfig.tsbuildinfo +1 -1
  76. package/dist/http/http-session-service.d.ts +0 -15
  77. package/dist/http/http-session-service.js +0 -1
  78. package/dist/http/pikku-http-session-service.d.ts +0 -45
  79. package/dist/http/pikku-http-session-service.js +0 -92
  80. package/src/http/http-session-service.ts +0 -19
  81. package/src/http/pikku-http-session-service.test.ts +0 -106
  82. package/src/http/pikku-http-session-service.ts +0 -135
@@ -1,4 +1,3 @@
1
- import { getErrorResponse } from '../errors/error-handler.js'
2
1
  import { verifyPermissions } from '../permissions.js'
3
2
  import {
4
3
  CoreHTTPFunctionRoute,
@@ -6,31 +5,42 @@ import {
6
5
  RunRouteOptions,
7
6
  RunRouteParams,
8
7
  PikkuHTTP,
8
+ HTTPRouteMiddleware,
9
9
  } from './http-routes.types.js'
10
- import { CoreUserSession, SessionServices } from '../types/core.types.js'
10
+ import { SessionServices } from '../types/core.types.js'
11
11
  import { match } from 'path-to-regexp'
12
12
  import { PikkuHTTPAbstractRequest } from './pikku-http-abstract-request.js'
13
13
  import { PikkuHTTPAbstractResponse } from './pikku-http-abstract-response.js'
14
- import { Logger, SchemaService } from '../services/index.js'
15
14
  import {
16
15
  ForbiddenError,
16
+ MissingSessionError,
17
17
  NotFoundError,
18
- NotImplementedError,
19
18
  } from '../errors/errors.js'
20
19
  import crypto from 'crypto'
21
20
  import { closeSessionServices } from '../utils.js'
22
- import { CoreAPIChannel } from '../channel/channel.types.js'
23
21
  import { PikkuRequest } from '../pikku-request.js'
24
22
  import { PikkuResponse } from '../pikku-response.js'
25
- import { HTTPSessionService } from './http-session-service.js'
26
- import { getSchema, validateAndCoerce } from '../schema.js'
23
+ import { coerceQueryStringToArray, validateSchema } from '../schema.js'
24
+ import { LocalUserSessionService } from '../services/user-session-service.js'
25
+ import { runMiddleware } from '../middleware-runner.js'
26
+ import { handleError } from '../handle-error.js'
27
27
 
28
+ /**
29
+ * Initialize global state for HTTP routes and middleware if not already available
30
+ */
28
31
  if (!globalThis.pikku?.httpRoutes) {
29
32
  globalThis.pikku = globalThis.pikku || {}
33
+ globalThis.pikku.httpMiddleware = []
30
34
  globalThis.pikku.httpRoutes = []
31
35
  globalThis.pikku.httpRoutesMeta = []
32
36
  }
33
37
 
38
+ /**
39
+ * Get or set the global HTTP routes
40
+ *
41
+ * @param {CoreHTTPFunctionRoute<any, any, any>[]} [data] - Optional routes data to set
42
+ * @returns {CoreHTTPFunctionRoute<any, any, any>[]} Current routes
43
+ */
34
44
  const httpRoutes = (
35
45
  data?: CoreHTTPFunctionRoute<any, any, any>[]
36
46
  ): CoreHTTPFunctionRoute<any, any, any>[] => {
@@ -40,6 +50,12 @@ const httpRoutes = (
40
50
  return globalThis.pikku.httpRoutes
41
51
  }
42
52
 
53
+ /**
54
+ * Get or set the global HTTP route metadata
55
+ *
56
+ * @param {HTTPRoutesMeta} [data] - Optional route metadata to set
57
+ * @returns {HTTPRoutesMeta} Current route metadata
58
+ */
43
59
  const httpRoutesMeta = (data?: HTTPRoutesMeta): HTTPRoutesMeta => {
44
60
  if (data) {
45
61
  globalThis.pikku.httpRoutesMeta = data
@@ -47,6 +63,49 @@ const httpRoutesMeta = (data?: HTTPRoutesMeta): HTTPRoutesMeta => {
47
63
  return globalThis.pikku.httpRoutesMeta
48
64
  }
49
65
 
66
+ /**
67
+ * Get or set the global HTTP middleware
68
+ *
69
+ * @param {HTTPRouteMiddleware[]} [data] - Optional middleware to set
70
+ * @returns {HTTPRouteMiddleware[]} Current middleware
71
+ */
72
+ const httpMiddleware = (
73
+ data?: HTTPRouteMiddleware[]
74
+ ): HTTPRouteMiddleware[] => {
75
+ if (data) {
76
+ globalThis.pikku.httpMiddleware = data
77
+ }
78
+ return globalThis.pikku.httpMiddleware
79
+ }
80
+
81
+ /**
82
+ * Add middleware to a specific route or globally
83
+ *
84
+ * @param {APIMiddleware[] | string} routeOrMiddleware - Route pattern to match or middleware array
85
+ * @param {APIMiddleware} [middleware] - Middleware to add (required if first param is a string)
86
+ */
87
+ export const addMiddleware = <APIMiddleware>(
88
+ routeOrMiddleware: APIMiddleware[] | string,
89
+ middleware?: APIMiddleware
90
+ ) => {
91
+ if (typeof routeOrMiddleware === 'string') {
92
+ globalThis.pikku.httpMiddleware.push({
93
+ route: routeOrMiddleware,
94
+ middleware: middleware!,
95
+ })
96
+ } else {
97
+ globalThis.pikku.httpMiddleware.push({
98
+ route: '*',
99
+ middleware: routeOrMiddleware,
100
+ })
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Add a route to the global routes registry
106
+ *
107
+ * @param {CoreHTTPFunctionRoute} route - Route configuration to add
108
+ */
50
109
  export const addRoute = <
51
110
  In,
52
111
  Out,
@@ -54,6 +113,7 @@ export const addRoute = <
54
113
  APIFunction,
55
114
  APIFunctionSessionless,
56
115
  APIPermission,
116
+ APIMiddleware,
57
117
  >(
58
118
  route: CoreHTTPFunctionRoute<
59
119
  In,
@@ -61,25 +121,34 @@ export const addRoute = <
61
121
  Route,
62
122
  APIFunction,
63
123
  APIFunctionSessionless,
64
- APIPermission
124
+ APIPermission,
125
+ APIMiddleware
65
126
  >
66
127
  ) => {
67
128
  httpRoutes().push(route as any)
68
129
  }
69
130
 
131
+ /**
132
+ * Remove all routes from the global registry
133
+ */
70
134
  export const clearRoutes = () => {
71
135
  httpRoutes([])
72
136
  }
73
137
 
74
138
  /**
139
+ * Set the HTTP routes metadata
140
+ *
141
+ * @param {HTTPRoutesMeta} routeMeta - Metadata for routes
75
142
  * @ignore
76
143
  */
77
- export const setHTTPRoutesMeta = (_routeMeta: HTTPRoutesMeta) => {
78
- httpRoutesMeta(_routeMeta)
144
+ export const setHTTPRoutesMeta = (routeMeta: HTTPRoutesMeta) => {
145
+ httpRoutesMeta(routeMeta)
79
146
  }
80
147
 
81
148
  /**
82
- * Returns all the registered routes and associated metadata.
149
+ * Returns all the registered routes and associated metadata
150
+ *
151
+ * @returns {Object} Object containing routes and routesMeta
83
152
  * @internal
84
153
  */
85
154
  export const getRoutes = () => {
@@ -89,105 +158,66 @@ export const getRoutes = () => {
89
158
  }
90
159
  }
91
160
 
92
- const getMatchingRoute = (
93
- schemaService: SchemaService | undefined,
94
- requestType: string,
95
- requestPath: string
96
- ) => {
161
+ /**
162
+ * Find a route that matches the given request type and path
163
+ *
164
+ * @param {string} requestType - HTTP method (GET, POST, etc.)
165
+ * @param {string} requestPath - URL path to match
166
+ * @returns {Object | undefined} Matching route information or undefined if no match
167
+ */
168
+ const getMatchingRoute = (requestType: string, requestPath: string) => {
97
169
  for (const route of httpRoutes()) {
98
- // TODO: This is a performance improvement, but we could
99
- // run against all routes if we want to return a 405 method.
100
- // Probably want a cache to support.
170
+ // Skip routes that don't match the request method
101
171
  if (route.method !== requestType.toLowerCase()) {
102
172
  continue
103
173
  }
174
+
175
+ // Create path matcher function
104
176
  const matchFunc = match(`/${route.route}`.replace(/^\/\//, '/'), {
105
177
  decode: decodeURIComponent,
106
178
  })
179
+
180
+ // Try to match the path
107
181
  const matchedPath = matchFunc(requestPath.replace(/^\/\//, '/'))
108
182
 
109
183
  if (matchedPath) {
110
- // TODO: Cache this loop as a performance improvement
184
+ // Get all middleware for this route
185
+ const globalMiddleware = httpMiddleware()
186
+ .filter((m) => m.route === '*' || new RegExp(m.route).test(route.route))
187
+ .map((m) => m.middleware)
188
+ .flat()
189
+
190
+ // Find schema for this route
111
191
  const schemaName = httpRoutesMeta().find(
112
192
  (routeMeta) =>
113
193
  routeMeta.method === route.method && routeMeta.route === route.route
114
194
  )?.input
115
- if (schemaName && schemaService) {
116
- const schema = getSchema(schemaName)
117
- schemaService.compileSchema(schemaName, schema)
118
- }
119
- return { matchedPath, params: matchedPath.params, route, schemaName }
120
- }
121
- }
122
- return undefined
123
- }
124
-
125
- export const getUserSession = async <UserSession extends CoreUserSession>(
126
- httpSessionService: HTTPSessionService<UserSession> | undefined,
127
- auth: boolean,
128
- request: PikkuHTTPAbstractRequest
129
- ): Promise<CoreUserSession | undefined> => {
130
- if (httpSessionService) {
131
- return (await httpSessionService.getUserSession(
132
- auth,
133
- request
134
- )) as UserSession
135
- } else if (auth) {
136
- throw new NotImplementedError('Session service not implemented')
137
- }
138
- return undefined
139
- }
140
-
141
- export const loadUserSession = async (
142
- skipUserSession: boolean,
143
- requiresSession: boolean,
144
- http: PikkuHTTP | undefined,
145
- matchedPath: any,
146
- route:
147
- | CoreHTTPFunctionRoute<unknown, unknown, any>
148
- | CoreAPIChannel<unknown, any>,
149
- logger: Logger,
150
- httpSessionService: HTTPSessionService | undefined
151
- ) => {
152
- if (skipUserSession && requiresSession) {
153
- throw new Error("Can't skip trying to get user session if auth is required")
154
- }
155
195
 
156
- if (skipUserSession === false) {
157
- try {
158
- if (http?.request) {
159
- return await getUserSession(
160
- httpSessionService,
161
- requiresSession,
162
- http.request
163
- )
164
- } else if (requiresSession) {
165
- logger.error({
166
- action: 'Can only get user session with HTTP request',
167
- path: matchedPath,
168
- route,
169
- })
170
- throw new Error('Can only get user session with HTTP request')
171
- }
172
- } catch (e: any) {
173
- if (requiresSession) {
174
- logger.info({
175
- action: 'Rejecting route (invalid session)',
176
- path: matchedPath,
177
- })
178
- throw e
196
+ return {
197
+ matchedPath,
198
+ params: matchedPath.params,
199
+ route,
200
+ middleware: [...globalMiddleware, ...(route.middleware || [])],
201
+ schemaName,
179
202
  }
180
203
  }
181
204
  }
182
-
183
205
  return undefined
184
206
  }
185
207
 
208
+ /**
209
+ * Create an HTTP interaction object from request and response
210
+ *
211
+ * @param {PikkuRequest | undefined} request - The HTTP request object
212
+ * @param {PikkuResponse | undefined} response - The HTTP response object
213
+ * @returns {PikkuHTTP | undefined} HTTP interaction object or undefined
214
+ */
186
215
  export const createHTTPInteraction = (
187
216
  request: PikkuRequest | undefined,
188
217
  response: PikkuResponse | undefined
189
- ) => {
218
+ ): PikkuHTTP | undefined => {
190
219
  let http: PikkuHTTP | undefined = undefined
220
+
191
221
  if (
192
222
  request instanceof PikkuHTTPAbstractRequest ||
193
223
  response instanceof PikkuHTTPAbstractResponse
@@ -200,52 +230,141 @@ export const createHTTPInteraction = (
200
230
  http.response = response
201
231
  }
202
232
  }
233
+
203
234
  return http
204
235
  }
205
236
 
206
- export const handleError = (
207
- e: any,
237
+ /**
238
+ * Validate input data and execute route handler with appropriate middleware
239
+ *
240
+ * @param {Object} services - Available services
241
+ * @param {Object} matchedRoute - Information about the matched route
242
+ * @param {PikkuHTTP | undefined} http - HTTP interaction object
243
+ * @param {Object} options - Additional options
244
+ * @returns {Promise<any>} Result of the route handler
245
+ */
246
+ const executeRouteWithMiddleware = async (
247
+ services: {
248
+ singletonServices: any
249
+ userSessionService: any
250
+ createSessionServices: Function
251
+ skipUserSession: boolean
252
+ },
253
+ matchedRoute: {
254
+ matchedPath: any
255
+ params: any
256
+ route: CoreHTTPFunctionRoute<any, any, any>
257
+ middleware: any[]
258
+ schemaName?: string | null
259
+ },
208
260
  http: PikkuHTTP | undefined,
209
- trackerId: string,
210
- logger: Logger,
211
- logWarningsForStatusCodes: number[],
212
- respondWith404: boolean,
213
- bubbleError: boolean
214
- ) => {
215
- if (e instanceof NotFoundError && !respondWith404) {
216
- return
261
+ options: {
262
+ coerceToArray: boolean
217
263
  }
264
+ ) => {
265
+ const { matchedPath, params, route, middleware, schemaName } = matchedRoute
266
+ const {
267
+ singletonServices,
268
+ userSessionService,
269
+ createSessionServices,
270
+ skipUserSession,
271
+ } = services
272
+ const { coerceToArray } = options
273
+
274
+ const requiresSession = route.auth !== false
275
+ let sessionServices: any
276
+ let result: any
277
+
278
+ http?.request?.setParams(params)
279
+
280
+ singletonServices.logger.info(
281
+ `Matched route: ${route.route} | method: ${route.method.toUpperCase()} | auth: ${requiresSession.toString()}`
282
+ )
218
283
 
219
- const errorResponse = getErrorResponse(e)
220
- if (errorResponse != null) {
221
- http?.response?.setStatus(errorResponse.status)
222
- http?.response?.setJson({
223
- message: errorResponse.message,
224
- payload: (e as any).payload,
225
- traceId: trackerId,
226
- })
284
+ // Main route execution function
285
+ const runMain = async () => {
286
+ const session = userSessionService.get()
227
287
 
228
- if (logWarningsForStatusCodes.includes(errorResponse.status)) {
229
- logger.warn(`Warning id: ${trackerId}`)
230
- logger.warn(e)
288
+ // Validate session was set if needed
289
+ if (skipUserSession && requiresSession) {
290
+ throw new Error(
291
+ "Can't skip trying to get user session if auth is required"
292
+ )
231
293
  }
232
- } else {
233
- logger.warn(`Error id: ${trackerId}`)
234
- logger.error(e)
235
- http?.response?.setStatus(500)
236
- http?.response?.setJson({ errorId: trackerId })
237
- }
238
294
 
239
- if (e instanceof NotFoundError) {
295
+ if (requiresSession && !session) {
296
+ singletonServices.logger.info({
297
+ action: 'Rejecting route (invalid session)',
298
+ path: matchedPath,
299
+ })
300
+ throw new MissingSessionError()
301
+ }
302
+
303
+ // Create session services
304
+ sessionServices = await createSessionServices(
305
+ singletonServices,
306
+ { http },
307
+ session
308
+ )
309
+
310
+ const allServices = { ...singletonServices, ...sessionServices, http }
311
+ const data = await http?.request?.getData()
312
+
313
+ // Validate schema
314
+ validateSchema(
315
+ singletonServices.logger,
316
+ singletonServices.schemaService,
317
+ schemaName,
318
+ data
319
+ )
320
+
321
+ if (coerceToArray && schemaName) {
322
+ coerceQueryStringToArray(schemaName, data)
323
+ }
324
+
325
+ // Run permission checks
326
+ const permissioned = await verifyPermissions(
327
+ route.permissions,
328
+ allServices,
329
+ data,
330
+ session
331
+ )
332
+
333
+ if (permissioned === false) {
334
+ throw new ForbiddenError('Permission denied')
335
+ }
336
+
337
+ // Execute the route handler
338
+ result = await route.func(allServices, data, session!)
339
+
340
+ // Set the response
341
+ if (route.returnsJSON === false) {
342
+ http?.response?.setResponse(result)
343
+ } else {
344
+ http?.response?.setJson(result)
345
+ }
346
+
347
+ http?.response?.setStatus(200)
240
348
  http?.response?.end()
241
- }
242
349
 
243
- if (bubbleError) {
244
- throw e
350
+ return result
245
351
  }
352
+
353
+ await runMiddleware(
354
+ { ...singletonServices, userSessionService },
355
+ { http },
356
+ middleware,
357
+ runMain
358
+ )
359
+
360
+ return sessionServices ? { result, sessionServices } : { result }
246
361
  }
247
362
 
248
363
  /**
364
+ * Run an HTTP route with the given parameters
365
+ *
366
+ * @param {Object} options - Options for running the route
367
+ * @returns {Promise<Out | void>} Result of the route handler
249
368
  * @ignore
250
369
  */
251
370
  export const runHTTPRoute = async <In, Out>({
@@ -263,18 +382,20 @@ export const runHTTPRoute = async <In, Out>({
263
382
  }: Pick<CoreHTTPFunctionRoute<unknown, unknown, any>, 'route' | 'method'> &
264
383
  RunRouteOptions &
265
384
  RunRouteParams<In>): Promise<Out | void> => {
266
- let sessionServices: SessionServices<typeof singletonServices> | undefined
267
385
  const trackerId: string = crypto.randomUUID().toString()
268
386
 
387
+ const userSessionService = new LocalUserSessionService()
388
+ let sessionServices: SessionServices<typeof singletonServices> | undefined
389
+ let result: Out
390
+
391
+ // Create HTTP interaction object
269
392
  const http = createHTTPInteraction(request, response)
270
393
 
271
- const matchedRoute = getMatchingRoute(
272
- singletonServices.schemaService,
273
- apiType,
274
- apiRoute
275
- )
394
+ // Find matching route
395
+ const matchedRoute = getMatchingRoute(apiType, apiRoute)
276
396
 
277
397
  try {
398
+ // Handle route not found
278
399
  if (!matchedRoute) {
279
400
  singletonServices.logger.info({
280
401
  message: 'Route not found',
@@ -284,70 +405,22 @@ export const runHTTPRoute = async <In, Out>({
284
405
  throw new NotFoundError(`Route not found: ${apiRoute}`)
285
406
  }
286
407
 
287
- const { matchedPath, params, route, schemaName } = matchedRoute
288
- const requiresSession = route.auth !== false
289
- http?.request?.setParams(params)
290
-
291
- singletonServices.logger.info(
292
- `Matched route: ${route.route} | method: ${route.method.toUpperCase()} | auth: ${requiresSession.toString()}`
293
- )
294
-
295
- const session = await loadUserSession(
296
- skipUserSession,
297
- requiresSession,
408
+ // Execute route with middleware
409
+ ;({ result, sessionServices } = await executeRouteWithMiddleware(
410
+ {
411
+ singletonServices,
412
+ userSessionService,
413
+ createSessionServices,
414
+ skipUserSession,
415
+ },
416
+ matchedRoute,
298
417
  http,
299
- matchedPath,
300
- route,
301
- singletonServices.logger,
302
- singletonServices.httpSessionService
303
- )
304
- const data = await request.getData()
305
-
306
- validateAndCoerce(
307
- singletonServices.logger,
308
- singletonServices.schemaService,
309
- schemaName,
310
- data,
311
- coerceToArray
312
- )
313
-
314
- sessionServices = await createSessionServices(
315
- singletonServices,
316
- { http },
317
- session
318
- )
319
- const allServices = { ...singletonServices, ...sessionServices, http }
320
-
321
- if (singletonServices.enforceHTTPAccess) {
322
- await singletonServices.enforceHTTPAccess(route, session)
323
- }
324
-
325
- const permissioned = await verifyPermissions(
326
- route.permissions,
327
- allServices,
328
- data,
329
- session
330
- )
331
- if (permissioned === false) {
332
- throw new ForbiddenError('Permission denied')
333
- }
334
-
335
- const result: any = (await route.func(
336
- allServices,
337
- data,
338
- session!
339
- )) as unknown as Out
340
-
341
- if (route.returnsJSON === false) {
342
- http?.response?.setResponse(result)
343
- } else {
344
- http?.response?.setJson(result)
345
- }
346
- http?.response?.setStatus(200)
347
- http?.response?.end()
418
+ { coerceToArray }
419
+ ))
348
420
 
349
421
  return result
350
422
  } catch (e: any) {
423
+ // Handle and possibly bubble the error
351
424
  handleError(
352
425
  e,
353
426
  http,
@@ -358,6 +431,7 @@ export const runHTTPRoute = async <In, Out>({
358
431
  bubbleErrors
359
432
  )
360
433
  } finally {
434
+ // Clean up session services
361
435
  if (sessionServices) {
362
436
  await closeSessionServices(singletonServices.logger, sessionServices)
363
437
  }
@@ -5,6 +5,7 @@ import {
5
5
  CoreSingletonServices,
6
6
  CoreUserSession,
7
7
  CreateSessionServices,
8
+ PikkuMiddleware,
8
9
  } from '../types/core.types.js'
9
10
  import {
10
11
  CoreAPIFunction,
@@ -115,6 +116,7 @@ export type CoreHTTPFunctionRoute<
115
116
  APIFunction = CoreAPIFunction<In, Out>,
116
117
  APIFunctionSessionless = CoreAPIFunctionSessionless<In, Out>,
117
118
  APIPermission = CoreAPIPermission<In>,
119
+ APIMiddleware = PikkuMiddleware,
118
120
  > =
119
121
  | (CoreHTTPFunction & {
120
122
  route: R
@@ -122,6 +124,8 @@ export type CoreHTTPFunctionRoute<
122
124
  func: APIFunction
123
125
  permissions?: Record<string, APIPermission[] | APIPermission>
124
126
  auth?: true
127
+ tags?: string[]
128
+ middleware?: APIMiddleware[]
125
129
  })
126
130
  | (CoreHTTPFunction & {
127
131
  route: R
@@ -129,6 +133,8 @@ export type CoreHTTPFunctionRoute<
129
133
  func: APIFunctionSessionless
130
134
  permissions?: undefined
131
135
  auth?: false
136
+ tags?: string[]
137
+ middleware?: APIMiddleware[]
132
138
  })
133
139
  | (CoreHTTPFunction & {
134
140
  route: R
@@ -137,6 +143,8 @@ export type CoreHTTPFunctionRoute<
137
143
  permissions?: Record<string, APIPermission[] | APIPermission>
138
144
  auth?: true
139
145
  query?: Array<keyof In>
146
+ tags?: string[]
147
+ middleware?: APIMiddleware[]
140
148
  })
141
149
  | (CoreHTTPFunction & {
142
150
  route: R
@@ -145,6 +153,8 @@ export type CoreHTTPFunctionRoute<
145
153
  permissions?: undefined
146
154
  auth?: false
147
155
  query?: Array<keyof In>
156
+ tags?: string[]
157
+ middleware?: APIMiddleware[]
148
158
  })
149
159
 
150
160
  /**
@@ -175,15 +185,10 @@ export type HTTPRoutesMeta = Array<{
175
185
  output: string | null
176
186
  inputTypes?: HTTPFunctionMetaInputTypes
177
187
  docs?: APIDocs
188
+ tags?: string[]
178
189
  }>
179
190
 
180
- /**
181
- * Verifies access to a route.
182
- * @param route - The route to verify access for.
183
- * @param session - The user session.
184
- * @returns A promise that resolves if access is granted.
185
- */
186
- export type enforceHTTPAccess = (
187
- route: CoreHTTPFunctionRoute<unknown, unknown, any>,
188
- session?: CoreUserSession
189
- ) => Promise<void> | void
191
+ export type HTTPRouteMiddleware = {
192
+ route: string
193
+ middleware: PikkuMiddleware[]
194
+ }
package/src/http/index.ts CHANGED
@@ -1,5 +1,3 @@
1
- export * from './pikku-http-session-service.js'
2
-
3
1
  export * from './pikku-http-abstract-request.js'
4
2
 
5
3
  export * from './pikku-http-abstract-response.js'
@@ -1,6 +1,6 @@
1
1
  import { parse as parseCookie } from 'cookie'
2
2
  import { PikkuRequest } from '../pikku-request.js'
3
- import { PikkuQuery } from './http-routes.types.js'
3
+ import { HTTPMethod, PikkuQuery } from './http-routes.types.js'
4
4
 
5
5
  /**
6
6
  * Abstract class representing a pikku request.
@@ -12,6 +12,13 @@ export abstract class PikkuHTTPAbstractRequest<
12
12
  > extends PikkuRequest<In> {
13
13
  private params: Partial<Record<string, string | string[]>> = {}
14
14
 
15
+ constructor(
16
+ public path: string,
17
+ public method: HTTPMethod
18
+ ) {
19
+ super()
20
+ }
21
+
15
22
  /**
16
23
  * Retrieves the request body.
17
24
  * @returns A promise that resolves to the request body.
package/src/index.ts CHANGED
@@ -10,7 +10,9 @@ export * from './http/index.js'
10
10
  export * from './channel/index.js'
11
11
  export * from './scheduler/index.js'
12
12
  export * from './errors/index.js'
13
+ export * from './middleware/index.js'
13
14
 
15
+ export { runMiddleware } from './middleware-runner.js'
14
16
  export { addRoute } from './http/http-route-runner.js'
15
17
  export { addChannel } from './channel/channel-runner.js'
16
18
  export { addScheduledTask } from './scheduler/scheduler-runner.js'