@pikku/core 0.6.27 → 0.7.1

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 (75) hide show
  1. package/CHANGELOG.md +16 -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/{types → function}/functions.types.d.ts +12 -7
  12. package/dist/function/index.d.ts +2 -0
  13. package/dist/function/index.js +2 -0
  14. package/dist/handle-error.d.ts +1 -1
  15. package/dist/http/{http-route-runner.d.ts → http-runner.d.ts} +4 -4
  16. package/dist/http/{http-route-runner.js → http-runner.js} +81 -43
  17. package/dist/http/{http-routes.types.d.ts → http.types.d.ts} +41 -12
  18. package/dist/http/index.d.ts +2 -2
  19. package/dist/http/index.js +1 -1
  20. package/dist/http/log-http-routes.js +7 -5
  21. package/dist/http/pikku-fetch-http-request.d.ts +1 -1
  22. package/dist/http/pikku-fetch-http-response.d.ts +4 -2
  23. package/dist/http/pikku-fetch-http-response.js +64 -12
  24. package/dist/index.d.ts +3 -2
  25. package/dist/index.js +3 -2
  26. package/dist/middleware/auth-apikey.js +5 -4
  27. package/dist/middleware/auth-bearer.js +5 -4
  28. package/dist/middleware/auth-cookie.js +10 -9
  29. package/dist/permissions.d.ts +2 -2
  30. package/dist/pikku-function.d.ts +1 -0
  31. package/dist/pikku-function.js +1 -0
  32. package/dist/pikku-state.d.ts +10 -5
  33. package/dist/pikku-state.js +7 -3
  34. package/dist/scheduler/scheduler-runner.d.ts +1 -1
  35. package/dist/scheduler/scheduler-runner.js +24 -6
  36. package/dist/scheduler/scheduler.types.d.ts +3 -2
  37. package/dist/services/content-service.d.ts +33 -37
  38. package/dist/types/core.types.d.ts +15 -1
  39. package/dist/utils.d.ts +2 -0
  40. package/dist/utils.js +15 -0
  41. package/lcov.info +1240 -965
  42. package/package.json +2 -1
  43. package/src/channel/channel-handler.ts +16 -78
  44. package/src/channel/channel-runner.ts +72 -17
  45. package/src/channel/channel.types.ts +25 -63
  46. package/src/channel/local/local-channel-runner.test.ts +10 -11
  47. package/src/channel/local/local-channel-runner.ts +20 -7
  48. package/src/channel/log-channels.ts +4 -4
  49. package/src/channel/serverless/serverless-channel-runner.ts +26 -15
  50. package/src/function/function-runner.ts +91 -0
  51. package/src/{types → function}/functions.types.ts +25 -11
  52. package/src/function/index.ts +2 -0
  53. package/src/handle-error.ts +1 -1
  54. package/src/http/{http-route-runner.test.ts → http-runner.test.ts} +29 -6
  55. package/src/http/{http-route-runner.ts → http-runner.ts} +106 -73
  56. package/src/http/{http-routes.types.ts → http.types.ts} +55 -16
  57. package/src/http/index.ts +2 -2
  58. package/src/http/log-http-routes.ts +7 -5
  59. package/src/http/pikku-fetch-http-request.ts +1 -5
  60. package/src/http/pikku-fetch-http-response.ts +67 -13
  61. package/src/index.ts +3 -2
  62. package/src/middleware/auth-apikey.ts +5 -4
  63. package/src/middleware/auth-bearer.ts +5 -4
  64. package/src/middleware/auth-cookie.ts +14 -9
  65. package/src/permissions.ts +2 -4
  66. package/src/pikku-function.ts +1 -0
  67. package/src/pikku-state.ts +25 -9
  68. package/src/scheduler/scheduler-runner.ts +31 -13
  69. package/src/scheduler/scheduler.types.ts +12 -8
  70. package/src/services/content-service.ts +36 -40
  71. package/src/types/core.types.ts +20 -1
  72. package/src/utils.ts +19 -0
  73. package/tsconfig.tsbuildinfo +1 -1
  74. /package/dist/{types → function}/functions.types.js +0 -0
  75. /package/dist/http/{http-routes.types.js → http.types.js} +0 -0
@@ -0,0 +1,91 @@
1
+ import { ForbiddenError } from '../errors/errors.js'
2
+ import { verifyPermissions } from '../permissions.js'
3
+ import { pikkuState } from '../pikku-state.js'
4
+ import { coerceTopLevelDataFromSchema, validateSchema } from '../schema.js'
5
+ import {
6
+ CoreServices,
7
+ CoreSingletonServices,
8
+ CoreUserSession,
9
+ } from '../types/core.types.js'
10
+ import {
11
+ CoreAPIFunction,
12
+ CoreAPIFunctionSessionless,
13
+ CorePermissionGroup,
14
+ } from './functions.types.js'
15
+
16
+ export const addFunction = (
17
+ funcName: string,
18
+ func: CoreAPIFunction<any, any> | CoreAPIFunctionSessionless<any, any>
19
+ ) => {
20
+ pikkuState('functions', 'nameToFunction').set(funcName, func)
21
+ }
22
+
23
+ export const runPikkuFuncDirectly = async <In, Out>(
24
+ funcName: string,
25
+ allServices: CoreServices,
26
+ data: In,
27
+ session?: CoreUserSession
28
+ ) => {
29
+ const func = pikkuState('functions', 'nameToFunction').get(funcName)
30
+ if (!func) {
31
+ throw new Error(`Function not found: ${funcName}`)
32
+ }
33
+ return (await func(allServices, data, session!)) as Out
34
+ }
35
+
36
+ export const runPikkuFunc = async <In = any, Out = any>(
37
+ funcName: string,
38
+ {
39
+ singletonServices,
40
+ getAllServices,
41
+ data,
42
+ session,
43
+ permissions,
44
+ coerceDataFromSchema,
45
+ }: {
46
+ singletonServices: CoreSingletonServices
47
+ getAllServices: () => Promise<CoreServices> | CoreServices
48
+ data: In
49
+ session?: CoreUserSession
50
+ permissions?: CorePermissionGroup
51
+ coerceDataFromSchema?: boolean
52
+ }
53
+ ): Promise<Out> => {
54
+ const func = pikkuState('functions', 'nameToFunction').get(funcName)
55
+ if (!func) {
56
+ throw new Error(`Function not found: ${funcName}`)
57
+ }
58
+ const funcMeta = pikkuState('functions', 'meta')[funcName]
59
+ if (!funcMeta) {
60
+ throw new Error(`Function meta not found: ${funcName}`)
61
+ }
62
+ const schemaName = funcMeta.schemaName
63
+ // Validate request data against the defined schema, if any
64
+ await validateSchema(
65
+ singletonServices.logger,
66
+ singletonServices.schema,
67
+ schemaName,
68
+ data
69
+ )
70
+
71
+ // Coerce (top level) query string parameters or date objects if specified by the schema
72
+ if (coerceDataFromSchema && schemaName) {
73
+ coerceTopLevelDataFromSchema(schemaName, data)
74
+ }
75
+
76
+ const allServices = await getAllServices()
77
+
78
+ // Execute permission checks
79
+ const permissioned = await verifyPermissions(
80
+ permissions,
81
+ allServices,
82
+ data,
83
+ session
84
+ )
85
+
86
+ if (permissioned === false) {
87
+ throw new ForbiddenError('Permission denied')
88
+ }
89
+
90
+ return (await func(allServices, data, session!)) as Out
91
+ }
@@ -3,7 +3,7 @@ import type {
3
3
  CoreServices,
4
4
  CoreSingletonServices,
5
5
  CoreUserSession,
6
- } from './core.types.js'
6
+ } from '../types/core.types.js'
7
7
 
8
8
  /**
9
9
  * Represents a core API function that performs an operation using core services and a user session.
@@ -16,16 +16,21 @@ import type {
16
16
  export type CoreAPIFunction<
17
17
  In,
18
18
  Out,
19
- Services extends CoreSingletonServices = CoreServices & {
20
- channel?: PikkuChannel<unknown, Out>
21
- },
19
+ ChannelData extends unknown | null = null,
20
+ Services extends CoreSingletonServices = CoreServices &
21
+ (ChannelData extends null
22
+ ? {
23
+ channel?: PikkuChannel<unknown, Out> | undefined
24
+ }
25
+ : {
26
+ channel: PikkuChannel<ChannelData, Out>
27
+ }),
22
28
  Session extends CoreUserSession = CoreUserSession,
23
- Channel extends boolean = false,
24
29
  > = (
25
30
  services: Services,
26
31
  data: In,
27
32
  session: Session
28
- ) => Channel extends true ? Promise<Out> | Promise<void> : Promise<Out>
33
+ ) => ChannelData extends null ? Promise<Out> : Promise<Out> | Promise<void>
29
34
 
30
35
  /**
31
36
  * Represents a core API function that can be used without a session.
@@ -38,16 +43,21 @@ export type CoreAPIFunction<
38
43
  export type CoreAPIFunctionSessionless<
39
44
  In,
40
45
  Out,
41
- Services extends CoreSingletonServices = CoreServices & {
42
- channel?: PikkuChannel<unknown, Out>
43
- },
46
+ ChannelData extends unknown | null = null,
47
+ Services extends CoreSingletonServices = CoreServices &
48
+ (ChannelData extends null
49
+ ? {
50
+ channel?: PikkuChannel<unknown, Out> | undefined
51
+ }
52
+ : {
53
+ channel: PikkuChannel<ChannelData, Out>
54
+ }),
44
55
  Session extends CoreUserSession = CoreUserSession,
45
- Channel extends boolean = false,
46
56
  > = (
47
57
  services: Services,
48
58
  data: In,
49
59
  session?: Session
50
- ) => Channel extends true ? Promise<Out> | Promise<void> : Promise<Out>
60
+ ) => ChannelData extends null ? Promise<Out> : Promise<Out> | Promise<void>
51
61
 
52
62
  /**
53
63
  * Represents a function that checks permissions for a given API operation.
@@ -61,3 +71,7 @@ export type CoreAPIPermission<
61
71
  Services extends CoreSingletonServices = CoreServices,
62
72
  Session extends CoreUserSession = CoreUserSession,
63
73
  > = (services: Services, data: In, session?: Session) => Promise<boolean>
74
+
75
+ export type CorePermissionGroup<APIPermission = CoreAPIPermission<any>> =
76
+ | Record<string, APIPermission | APIPermission[]>
77
+ | undefined
@@ -0,0 +1,2 @@
1
+ export * from './function-runner.js'
2
+ export * from './functions.types.js'
@@ -1,7 +1,7 @@
1
1
  import { getErrorResponse } from './errors/error-handler.js'
2
2
  import { NotFoundError } from './errors/errors.js'
3
3
  import { Logger } from './services/logger.js'
4
- import { PikkuHTTP } from './http/http-routes.types.js'
4
+ import { PikkuHTTP } from './http/http.types.js'
5
5
 
6
6
  /**
7
7
  * Handle errors that occur during route processing
@@ -2,18 +2,38 @@ import { test, describe, beforeEach, afterEach } from 'node:test'
2
2
  import * as assert from 'assert'
3
3
  import { NotFoundError } from '../errors/errors.js'
4
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'
5
+ import { fetch, addHTTPRoute } from './http-runner.js'
6
+ import { pikkuState, resetPikkuState } from '../pikku-state.js'
7
7
  import {
8
8
  PikkuMockRequest,
9
9
  PikkuMockResponse,
10
10
  } from '../channel/local/local-channel-runner.test.js'
11
+ import { addFunction } from '../function/function-runner.js'
11
12
 
12
13
  const sessionMiddleware: PikkuMiddleware = async (services, _, next) => {
13
14
  services.userSession.set({ userId: 'test' } as any)
14
15
  await next()
15
16
  }
16
17
 
18
+ const setHTTPFunctionMap = (fun: any) => {
19
+ pikkuState('functions', 'meta', {
20
+ pikku_func_name: {
21
+ pikkuFuncName: 'pikku_func_name',
22
+ services: ['userSession'],
23
+ },
24
+ } as any)
25
+ pikkuState('http', 'meta', [
26
+ {
27
+ pikkuFuncName: 'pikku_func_name',
28
+ route: 'test',
29
+ method: 'get',
30
+ input: null,
31
+ output: null,
32
+ },
33
+ ])
34
+ addFunction('pikku_func_name', fun)
35
+ }
36
+
17
37
  describe('fetch', () => {
18
38
  let singletonServices: any
19
39
  let createSessionServices: any
@@ -57,7 +77,9 @@ describe('fetch', () => {
57
77
 
58
78
  test('should call the route function and return its result when a matching route is found', async () => {
59
79
  const routeFunc = async () => ({ success: true })
60
- addRoute({
80
+ setHTTPFunctionMap(routeFunc)
81
+
82
+ addHTTPRoute({
61
83
  route: 'test',
62
84
  method: 'get',
63
85
  func: routeFunc,
@@ -75,8 +97,9 @@ describe('fetch', () => {
75
97
  test('should verify permissions if provided', async () => {
76
98
  const permissions = { test: async () => true }
77
99
  const routeFunc = async () => ({ success: true })
100
+ setHTTPFunctionMap(routeFunc)
78
101
 
79
- addRoute({
102
+ addHTTPRoute({
80
103
  route: 'test',
81
104
  method: 'get',
82
105
  func: routeFunc,
@@ -97,13 +120,13 @@ describe('fetch', () => {
97
120
  const routeFunc = async () => {
98
121
  throw error
99
122
  }
100
- addRoute({
123
+ setHTTPFunctionMap(routeFunc)
124
+ addHTTPRoute({
101
125
  route: 'test',
102
126
  method: 'get',
103
127
  func: routeFunc,
104
128
  middleware: [sessionMiddleware],
105
129
  })
106
-
107
130
  await assert.rejects(
108
131
  async () =>
109
132
  fetch(request, {
@@ -1,4 +1,3 @@
1
- import { verifyPermissions } from '../permissions.js'
2
1
  import {
3
2
  CoreHTTPFunctionRoute,
4
3
  RunRouteOptions,
@@ -6,20 +5,21 @@ import {
6
5
  PikkuHTTP,
7
6
  PikkuHTTPRequest,
8
7
  PikkuHTTPResponse,
9
- } from './http-routes.types.js'
8
+ HTTPRouteMeta,
9
+ HTTPMethod,
10
+ } from './http.types.js'
10
11
  import {
11
12
  CoreUserSession,
12
13
  PikkuMiddleware,
13
14
  SessionServices,
14
15
  } from '../types/core.types.js'
15
16
  import { match } from 'path-to-regexp'
17
+ import { MissingSessionError, NotFoundError } from '../errors/errors.js'
16
18
  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'
19
+ closeSessionServices,
20
+ createWeakUID,
21
+ isSerializable,
22
+ } from '../utils.js'
23
23
  import {
24
24
  PikkuUserSessionService,
25
25
  UserSessionService,
@@ -29,6 +29,8 @@ import { handleError } from '../handle-error.js'
29
29
  import { pikkuState } from '../pikku-state.js'
30
30
  import { PikkuFetchHTTPResponse } from './pikku-fetch-http-response.js'
31
31
  import { PikkuFetchHTTPRequest } from './pikku-fetch-http-request.js'
32
+ import { PikkuChannel } from '../channel/channel.types.js'
33
+ import { addFunction, runPikkuFunc } from '../function/function-runner.js'
32
34
 
33
35
  /**
34
36
  * Registers middleware either globally or for a specific route.
@@ -69,30 +71,44 @@ export const addMiddleware = <APIMiddleware extends PikkuMiddleware>(
69
71
  * @template Route Route pattern as a string.
70
72
  * @template APIFunction Type for the route handler function.
71
73
  * @template APIFunctionSessionless Type for a sessionless handler.
72
- * @template APIPermission Type representing required permissions.
74
+ * @template APIPermissionGroup Type representing required permissions.
73
75
  * @template APIMiddleware Middleware type to be used with the route.
74
76
  * @param {CoreHTTPFunctionRoute<In, Out, Route, APIFunction, APIFunctionSessionless, APIPermission, APIMiddleware>} route - The route configuration object.
75
77
  */
76
- export const addRoute = <
78
+ export const addHTTPRoute = <
77
79
  In,
78
80
  Out,
79
81
  Route extends string,
80
82
  APIFunction,
81
83
  APIFunctionSessionless,
82
- APIPermission,
84
+ APIPermissionGroup,
83
85
  APIMiddleware,
84
86
  >(
85
- route: CoreHTTPFunctionRoute<
87
+ httpRoute: CoreHTTPFunctionRoute<
86
88
  In,
87
89
  Out,
88
90
  Route,
89
91
  APIFunction,
90
92
  APIFunctionSessionless,
91
- APIPermission,
93
+ APIPermissionGroup,
92
94
  APIMiddleware
93
95
  >
94
96
  ) => {
95
- pikkuState('http', 'routes').push(route as any)
97
+ const httpMeta = pikkuState('http', 'meta')
98
+ const routeMeta = httpMeta.find(
99
+ (meta) => meta.route === httpRoute.route && meta.method === httpRoute.method
100
+ )
101
+ if (!routeMeta) {
102
+ throw new Error('Route metadata not found')
103
+ }
104
+ addFunction(routeMeta.pikkuFuncName, httpRoute.func as any)
105
+ const routes = pikkuState('http', 'routes')
106
+ if (!routes.has(httpRoute.method)) {
107
+ routes.set(httpRoute.method, new Map())
108
+ }
109
+ pikkuState('http', 'routes')
110
+ .get(httpRoute.method)
111
+ ?.set(httpRoute.route, httpRoute as any)
96
112
  }
97
113
 
98
114
  /**
@@ -107,16 +123,13 @@ export const addRoute = <
107
123
  * @returns {Object | undefined} An object with matched route details or undefined if no match.
108
124
  */
109
125
  const getMatchingRoute = (requestType: string, requestPath: string) => {
110
- const routes = pikkuState('http', 'routes')
126
+ const allRoutes = pikkuState('http', 'routes')
111
127
  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
-
128
+ const routes = allRoutes.get(requestType.toLowerCase() as HTTPMethod)
129
+ if (!routes) {
130
+ return undefined
131
+ }
132
+ for (const route of routes.values()) {
120
133
  // Generate a matching function from the route pattern
121
134
  const matchFunc = match(`/${route.route}`.replace(/^\/\//, '/'), {
122
135
  decode: decodeURIComponent,
@@ -132,18 +145,17 @@ const getMatchingRoute = (requestType: string, requestPath: string) => {
132
145
  .map((m) => m.middleware)
133
146
  .flat()
134
147
 
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
148
+ const meta = pikkuState('http', 'meta').find(
149
+ (meta) => meta.route === route.route && meta.method === route.method
150
+ )
140
151
 
141
152
  return {
142
153
  matchedPath,
143
154
  params: matchedPath.params,
144
155
  route,
156
+ permissions: route.permissions,
145
157
  middleware: [...globalMiddleware, ...(route.middleware || [])],
146
- schemaName,
158
+ meta: meta!,
147
159
  }
148
160
  }
149
161
  }
@@ -205,25 +217,27 @@ const executeRouteWithMiddleware = async (
205
217
  userSession: UserSessionService<CoreUserSession>
206
218
  createSessionServices: Function
207
219
  skipUserSession: boolean
220
+ requestId: string
208
221
  },
209
222
  matchedRoute: {
210
223
  matchedPath: any
211
224
  params: any
212
225
  route: CoreHTTPFunctionRoute<any, any, any>
213
226
  middleware: any[]
214
- schemaName?: string | null
227
+ meta: HTTPRouteMeta
215
228
  },
216
- http: PikkuHTTP | undefined,
229
+ http: PikkuHTTP,
217
230
  options: {
218
231
  coerceDataFromSchema: boolean
219
232
  }
220
233
  ) => {
221
- const { matchedPath, params, route, middleware, schemaName } = matchedRoute
234
+ const { matchedPath, params, route, middleware, meta } = matchedRoute
222
235
  const {
223
236
  singletonServices,
224
237
  userSession,
225
238
  createSessionServices,
226
239
  skipUserSession,
240
+ requestId,
227
241
  } = services
228
242
 
229
243
  const requiresSession = route.auth !== false
@@ -256,48 +270,62 @@ const executeRouteWithMiddleware = async (
256
270
  throw new MissingSessionError()
257
271
  }
258
272
 
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
- )
273
+ const data = http.request!.data()
274
+
275
+ const getAllServices = async () => {
276
+ let channel: PikkuChannel<unknown, unknown> | undefined
277
+ if (matchedRoute.route.sse) {
278
+ const response = http?.response
279
+ if (!response) {
280
+ throw new Error('SSE requires a valid HTTP response object')
281
+ }
282
+ if (!response.setMode) {
283
+ throw new Error('Response object does not support SSE mode')
284
+ }
285
+ response.setMode('stream')
286
+ response.header('Content-Type', 'text/event-stream')
287
+ response.header('Cache-Control', 'no-cache')
288
+ response.header('Connection', 'keep-alive')
289
+ response.header('Transfer-Encoding', 'chunked')
290
+ channel = {
291
+ channelId: requestId,
292
+ openingData: data,
293
+ send: (data: any) => {
294
+ response.arrayBuffer(
295
+ isSerializable(data) ? JSON.stringify(data) : data
296
+ )
297
+ },
298
+ close: () => {
299
+ channel!.state = 'closed'
300
+ response.close?.()
301
+ },
302
+ state: 'open',
303
+ }
304
+ }
281
305
 
282
- // Coerce (top level) query string parameters or date objects if specified by the schema
283
- if (options.coerceDataFromSchema && schemaName) {
284
- coerceTopLevelDataFromSchema(schemaName, data)
306
+ // Create session-specific services for handling the request
307
+ sessionServices = await createSessionServices(
308
+ { ...singletonServices, userSession, channel },
309
+ { http },
310
+ session
311
+ )
312
+ return {
313
+ ...singletonServices,
314
+ ...sessionServices,
315
+ http,
316
+ userSession,
317
+ channel,
318
+ }
285
319
  }
286
320
 
287
- // Execute permission checks
288
- const permissioned = await verifyPermissions(
289
- route.permissions,
290
- allServices,
321
+ const result = await runPikkuFunc(meta.pikkuFuncName, {
322
+ singletonServices,
323
+ getAllServices,
324
+ session,
291
325
  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!)
326
+ permissions: route.permissions,
327
+ coerceDataFromSchema: options.coerceDataFromSchema,
328
+ })
301
329
 
302
330
  // Respond with either a binary or JSON response based on configuration
303
331
  if (route.returnsJSON === false) {
@@ -398,8 +426,13 @@ export const fetchData = async <In, Out>(
398
426
  logWarningsForStatusCodes = [],
399
427
  coerceDataFromSchema = true,
400
428
  bubbleErrors = false,
429
+ generateRequestId,
401
430
  }: RunRouteOptions & RunRouteParams
402
431
  ): Promise<Out | void> => {
432
+ const requestId =
433
+ (request as any).getHeader?.('x-request-id') ||
434
+ generateRequestId?.() ||
435
+ createWeakUID()
403
436
  const userSession = new PikkuUserSessionService()
404
437
  let sessionServices: SessionServices<typeof singletonServices> | undefined
405
438
  let result: Out
@@ -414,7 +447,6 @@ export const fetchData = async <In, Out>(
414
447
 
415
448
  // Locate the matching route based on the HTTP method and path
416
449
  const matchedRoute = getMatchingRoute(apiType, apiRoute)
417
-
418
450
  try {
419
451
  // If no route matches, log the occurrence and throw a NotFoundError
420
452
  if (!matchedRoute) {
@@ -433,9 +465,10 @@ export const fetchData = async <In, Out>(
433
465
  userSession,
434
466
  createSessionServices,
435
467
  skipUserSession,
468
+ requestId,
436
469
  },
437
470
  matchedRoute,
438
- http,
471
+ http!,
439
472
  { coerceDataFromSchema }
440
473
  ))
441
474
 
@@ -445,7 +478,7 @@ export const fetchData = async <In, Out>(
445
478
  handleError(
446
479
  e,
447
480
  http,
448
- '111', // TODO: context.get('trackingId'),
481
+ requestId,
449
482
  singletonServices.logger,
450
483
  logWarningsForStatusCodes,
451
484
  respondWith404,