@pikku/core 0.6.9

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 (167) hide show
  1. package/CHANGELOG.md +339 -0
  2. package/README.md +5 -0
  3. package/dist/channel/channel-handler.d.ts +3 -0
  4. package/dist/channel/channel-handler.js +97 -0
  5. package/dist/channel/channel-runner.d.ts +26 -0
  6. package/dist/channel/channel-runner.js +86 -0
  7. package/dist/channel/channel-store.d.ts +14 -0
  8. package/dist/channel/channel-store.js +2 -0
  9. package/dist/channel/channel.types.d.ts +106 -0
  10. package/dist/channel/channel.types.js +1 -0
  11. package/dist/channel/eventhub-forwarder.d.ts +17 -0
  12. package/dist/channel/eventhub-forwarder.js +1 -0
  13. package/dist/channel/eventhub-service.d.ts +23 -0
  14. package/dist/channel/eventhub-service.js +1 -0
  15. package/dist/channel/eventhub-store.d.ts +5 -0
  16. package/dist/channel/eventhub-store.js +2 -0
  17. package/dist/channel/index.d.ts +9 -0
  18. package/dist/channel/index.js +9 -0
  19. package/dist/channel/local/index.d.ts +3 -0
  20. package/dist/channel/local/index.js +3 -0
  21. package/dist/channel/local/local-channel-handler.d.ts +17 -0
  22. package/dist/channel/local/local-channel-handler.js +41 -0
  23. package/dist/channel/local/local-channel-runner.d.ts +3 -0
  24. package/dist/channel/local/local-channel-runner.js +46 -0
  25. package/dist/channel/local/local-eventhub-service.d.ts +44 -0
  26. package/dist/channel/local/local-eventhub-service.js +81 -0
  27. package/dist/channel/log-channels.d.ts +6 -0
  28. package/dist/channel/log-channels.js +17 -0
  29. package/dist/channel/pikku-abstract-channel-handler.d.ts +15 -0
  30. package/dist/channel/pikku-abstract-channel-handler.js +33 -0
  31. package/dist/channel/serverless/index.d.ts +1 -0
  32. package/dist/channel/serverless/index.js +1 -0
  33. package/dist/channel/serverless/serverless-channel-runner.d.ts +10 -0
  34. package/dist/channel/serverless/serverless-channel-runner.js +98 -0
  35. package/dist/errors/error-handler.d.ts +39 -0
  36. package/dist/errors/error-handler.js +54 -0
  37. package/dist/errors/errors.d.ts +190 -0
  38. package/dist/errors/errors.js +315 -0
  39. package/dist/errors/index.d.ts +2 -0
  40. package/dist/errors/index.js +2 -0
  41. package/dist/http/http-route-runner.d.ts +30 -0
  42. package/dist/http/http-route-runner.js +208 -0
  43. package/dist/http/http-routes.types.d.ts +127 -0
  44. package/dist/http/http-routes.types.js +1 -0
  45. package/dist/http/http-session-service.d.ts +15 -0
  46. package/dist/http/http-session-service.js +1 -0
  47. package/dist/http/index.d.ts +6 -0
  48. package/dist/http/index.js +5 -0
  49. package/dist/http/log-http-routes.d.ts +6 -0
  50. package/dist/http/log-http-routes.js +17 -0
  51. package/dist/http/pikku-http-abstract-request.d.ts +57 -0
  52. package/dist/http/pikku-http-abstract-request.js +69 -0
  53. package/dist/http/pikku-http-abstract-response.d.ts +58 -0
  54. package/dist/http/pikku-http-abstract-response.js +54 -0
  55. package/dist/http/pikku-http-session-service.d.ts +45 -0
  56. package/dist/http/pikku-http-session-service.js +92 -0
  57. package/dist/index.d.ts +14 -0
  58. package/dist/index.js +14 -0
  59. package/dist/permissions.d.ts +10 -0
  60. package/dist/permissions.js +32 -0
  61. package/dist/pikku-request.d.ts +14 -0
  62. package/dist/pikku-request.js +21 -0
  63. package/dist/pikku-response.d.ts +6 -0
  64. package/dist/pikku-response.js +6 -0
  65. package/dist/scheduler/index.d.ts +3 -0
  66. package/dist/scheduler/index.js +3 -0
  67. package/dist/scheduler/log-schedulers.d.ts +6 -0
  68. package/dist/scheduler/log-schedulers.js +17 -0
  69. package/dist/scheduler/scheduler-runner.d.ts +21 -0
  70. package/dist/scheduler/scheduler-runner.js +73 -0
  71. package/dist/scheduler/scheduler.types.d.ts +21 -0
  72. package/dist/scheduler/scheduler.types.js +1 -0
  73. package/dist/schema.d.ts +26 -0
  74. package/dist/schema.js +100 -0
  75. package/dist/services/content-service.d.ts +55 -0
  76. package/dist/services/content-service.js +1 -0
  77. package/dist/services/index.d.ts +12 -0
  78. package/dist/services/index.js +14 -0
  79. package/dist/services/jwt-service.d.ts +29 -0
  80. package/dist/services/jwt-service.js +1 -0
  81. package/dist/services/local-content.d.ts +33 -0
  82. package/dist/services/local-content.js +6 -0
  83. package/dist/services/local-secrets.d.ts +26 -0
  84. package/dist/services/local-secrets.js +39 -0
  85. package/dist/services/local-variables.d.ts +7 -0
  86. package/dist/services/local-variables.js +12 -0
  87. package/dist/services/logger-console.d.ts +52 -0
  88. package/dist/services/logger-console.js +82 -0
  89. package/dist/services/logger.d.ts +48 -0
  90. package/dist/services/logger.js +9 -0
  91. package/dist/services/schema-service.d.ts +24 -0
  92. package/dist/services/schema-service.js +1 -0
  93. package/dist/services/secret-service.d.ts +17 -0
  94. package/dist/services/secret-service.js +1 -0
  95. package/dist/services/user-session-service.d.ts +6 -0
  96. package/dist/services/user-session-service.js +1 -0
  97. package/dist/services/variables-service.d.ts +4 -0
  98. package/dist/services/variables-service.js +1 -0
  99. package/dist/types/core.types.d.ts +109 -0
  100. package/dist/types/core.types.js +1 -0
  101. package/dist/types/functions.types.d.ts +27 -0
  102. package/dist/types/functions.types.js +1 -0
  103. package/dist/utils.d.ts +2 -0
  104. package/dist/utils.js +15 -0
  105. package/package.json +48 -0
  106. package/run-tests.sh +53 -0
  107. package/src/channel/channel-handler.ts +179 -0
  108. package/src/channel/channel-runner.ts +156 -0
  109. package/src/channel/channel-store.ts +32 -0
  110. package/src/channel/channel.types.ts +202 -0
  111. package/src/channel/eventhub-forwarder.ts +25 -0
  112. package/src/channel/eventhub-service.ts +30 -0
  113. package/src/channel/eventhub-store.ts +8 -0
  114. package/src/channel/index.ts +9 -0
  115. package/src/channel/local/index.ts +3 -0
  116. package/src/channel/local/local-channel-handler.ts +56 -0
  117. package/src/channel/local/local-channel-runner.test.ts +128 -0
  118. package/src/channel/local/local-channel-runner.ts +94 -0
  119. package/src/channel/local/local-eventhub-service.test.ts +95 -0
  120. package/src/channel/local/local-eventhub-service.ts +95 -0
  121. package/src/channel/log-channels.ts +20 -0
  122. package/src/channel/pikku-abstract-channel-handler.test.ts +77 -0
  123. package/src/channel/pikku-abstract-channel-handler.ts +44 -0
  124. package/src/channel/serverless/index.ts +1 -0
  125. package/src/channel/serverless/serverless-channel-runner.ts +190 -0
  126. package/src/errors/error-handler.ts +74 -0
  127. package/src/errors/error.test.ts +54 -0
  128. package/src/errors/errors.ts +340 -0
  129. package/src/errors/index.ts +2 -0
  130. package/src/http/http-route-runner.test.ts +193 -0
  131. package/src/http/http-route-runner.ts +365 -0
  132. package/src/http/http-routes.types.ts +189 -0
  133. package/src/http/http-session-service.ts +19 -0
  134. package/src/http/index.ts +16 -0
  135. package/src/http/log-http-routes.ts +20 -0
  136. package/src/http/pikku-http-abstract-request.ts +95 -0
  137. package/src/http/pikku-http-abstract-response.ts +79 -0
  138. package/src/http/pikku-http-session-service.test.ts +106 -0
  139. package/src/http/pikku-http-session-service.ts +135 -0
  140. package/src/index.ts +15 -0
  141. package/src/permissions.test.ts +58 -0
  142. package/src/permissions.ts +45 -0
  143. package/src/pikku-request.ts +19 -0
  144. package/src/pikku-response.ts +5 -0
  145. package/src/scheduler/index.ts +11 -0
  146. package/src/scheduler/log-schedulers.ts +20 -0
  147. package/src/scheduler/scheduler-runner.ts +132 -0
  148. package/src/scheduler/scheduler.types.ts +27 -0
  149. package/src/schema.test.ts +57 -0
  150. package/src/schema.ts +124 -0
  151. package/src/services/content-service.ts +69 -0
  152. package/src/services/index.ts +16 -0
  153. package/src/services/jwt-service.ts +36 -0
  154. package/src/services/local-content.ts +39 -0
  155. package/src/services/local-secrets.ts +43 -0
  156. package/src/services/local-variables.ts +17 -0
  157. package/src/services/logger-console.ts +97 -0
  158. package/src/services/logger.ts +57 -0
  159. package/src/services/schema-service.ts +26 -0
  160. package/src/services/secret-service.ts +17 -0
  161. package/src/services/user-session-service.ts +7 -0
  162. package/src/services/variables-service.ts +6 -0
  163. package/src/types/core.types.ts +162 -0
  164. package/src/types/functions.types.ts +44 -0
  165. package/src/utils.ts +21 -0
  166. package/tsconfig.json +13 -0
  167. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,193 @@
1
+ import { test, describe, beforeEach, afterEach } from 'node:test'
2
+ import * as assert from 'assert'
3
+ import { NotFoundError, NotImplementedError } from '../errors/errors.js'
4
+ import { PikkuHTTPAbstractRequest } from './pikku-http-abstract-request.js'
5
+ import { PikkuHTTPAbstractResponse } from './pikku-http-abstract-response.js'
6
+ import { JSONValue } from '../types/core.types.js'
7
+ import {
8
+ getUserSession,
9
+ runHTTPRoute,
10
+ clearRoutes,
11
+ addRoute,
12
+ } from './http-route-runner.js'
13
+
14
+ class PikkuTestRequest extends PikkuHTTPAbstractRequest {
15
+ public getBody(): Promise<unknown> {
16
+ throw new Error('Method not implemented.')
17
+ }
18
+ public getHeader(_headerName: string): string | undefined {
19
+ throw new Error('Method not implemented.')
20
+ }
21
+ }
22
+
23
+ class PikkuTestResponse extends PikkuHTTPAbstractResponse {
24
+ public setStatus(_status: number): void {
25
+ throw new Error('Method not implemented.')
26
+ }
27
+ public setJson(_body: JSONValue): void {
28
+ throw new Error('Method not implemented.')
29
+ }
30
+ public setResponse(_response: string | Buffer): void {
31
+ throw new Error('Method not implemented.')
32
+ }
33
+ }
34
+
35
+ describe('runHTTPRoute', () => {
36
+ let singletonServices: any
37
+ let createSessionServices: any
38
+ let request: any
39
+ let response: any
40
+
41
+ beforeEach(() => {
42
+ clearRoutes()
43
+
44
+ singletonServices = {
45
+ logger: {
46
+ info: () => {},
47
+ warn: () => {},
48
+ error: () => {},
49
+ },
50
+ httpSessionService: {
51
+ getUserSession: async () => {},
52
+ },
53
+ }
54
+
55
+ createSessionServices = async () => ({})
56
+ request = new PikkuTestRequest()
57
+ response = new PikkuTestResponse()
58
+
59
+ request.getData = async () => ({})
60
+ request.getHeader = () => 'application/json'
61
+ response.setStatus = (status: number) => {}
62
+ response.setJson = (json: JSONValue) => {}
63
+ })
64
+
65
+ afterEach(() => {})
66
+
67
+ test('should throw RouteNotFoundError when no matching route is found', async () => {
68
+ const apiRoute = '/test'
69
+ const apiType = 'get'
70
+
71
+ await assert.rejects(
72
+ async () =>
73
+ runHTTPRoute({
74
+ request,
75
+ response,
76
+ singletonServices,
77
+ createSessionServices,
78
+ route: apiRoute,
79
+ method: apiType,
80
+ bubbleErrors: true,
81
+ }),
82
+ NotFoundError
83
+ )
84
+ })
85
+
86
+ test('should call the route function and return its result when a matching route is found', async () => {
87
+ const apiRoute = '/test'
88
+ const apiType = 'get'
89
+ const routeFunc = async () => ({ success: true })
90
+ addRoute({
91
+ route: 'test',
92
+ method: 'get',
93
+ func: routeFunc,
94
+ })
95
+
96
+ const result = await runHTTPRoute({
97
+ request,
98
+ response,
99
+ singletonServices,
100
+ createSessionServices,
101
+ route: apiRoute,
102
+ method: apiType,
103
+ })
104
+
105
+ assert.deepStrictEqual(result, { success: true })
106
+ })
107
+
108
+ test('should verify permissions if provided', async () => {
109
+ const apiRoute = '/test'
110
+ const apiType = 'get'
111
+ const permissions = { test: async () => true }
112
+ const routeFunc = async () => ({ success: true })
113
+
114
+ addRoute({
115
+ route: 'test',
116
+ method: 'get',
117
+ func: routeFunc,
118
+ permissions,
119
+ })
120
+
121
+ await runHTTPRoute({
122
+ request,
123
+ response,
124
+ singletonServices,
125
+ createSessionServices,
126
+ route: apiRoute,
127
+ method: apiType,
128
+ })
129
+
130
+ assert.strictEqual(await permissions.test(), true)
131
+ })
132
+
133
+ test('should handle errors and set appropriate response', async () => {
134
+ const apiRoute = '/test'
135
+ const apiType = 'get'
136
+ const error = new Error('Test error')
137
+ const routeFunc = async () => {
138
+ throw error
139
+ }
140
+ addRoute({
141
+ route: 'test',
142
+ method: 'get',
143
+ func: routeFunc,
144
+ })
145
+
146
+ await assert.rejects(
147
+ async () =>
148
+ runHTTPRoute({
149
+ request,
150
+ response,
151
+ singletonServices,
152
+ createSessionServices,
153
+ route: apiRoute,
154
+ method: apiType,
155
+ bubbleErrors: true,
156
+ }),
157
+ error
158
+ )
159
+ })
160
+ })
161
+
162
+ describe('getUserSession', () => {
163
+ let sessionService: any
164
+ let request: any
165
+
166
+ beforeEach(() => {
167
+ sessionService = {
168
+ getUserSession: async () => ({}),
169
+ }
170
+ request = new PikkuTestRequest()
171
+ })
172
+
173
+ test('should return user session when sessionService is provided', async () => {
174
+ const userSession = { id: '123', username: 'test' }
175
+ sessionService.getUserSession = async () => userSession
176
+
177
+ const result = await getUserSession(sessionService, true, request)
178
+
179
+ assert.deepStrictEqual(result, userSession)
180
+ })
181
+
182
+ test('should throw NotImplementedError when session is required but sessionService is not provided', async () => {
183
+ await assert.rejects(
184
+ async () => getUserSession(undefined, true, request),
185
+ NotImplementedError
186
+ )
187
+ })
188
+
189
+ test('should return undefined when session is not required and sessionService is not provided', async () => {
190
+ const result = await getUserSession(undefined, false, request)
191
+ assert.strictEqual(result, undefined)
192
+ })
193
+ })
@@ -0,0 +1,365 @@
1
+ import { getErrorResponse } from '../errors/error-handler.js'
2
+ import { verifyPermissions } from '../permissions.js'
3
+ import {
4
+ CoreHTTPFunctionRoute,
5
+ HTTPRoutesMeta,
6
+ RunRouteOptions,
7
+ RunRouteParams,
8
+ PikkuHTTP,
9
+ } from './http-routes.types.js'
10
+ import { CoreUserSession, SessionServices } from '../types/core.types.js'
11
+ import { match } from 'path-to-regexp'
12
+ import { PikkuHTTPAbstractRequest } from './pikku-http-abstract-request.js'
13
+ import { PikkuHTTPAbstractResponse } from './pikku-http-abstract-response.js'
14
+ import { Logger, SchemaService } from '../services/index.js'
15
+ import {
16
+ ForbiddenError,
17
+ NotFoundError,
18
+ NotImplementedError,
19
+ } from '../errors/errors.js'
20
+ import crypto from 'crypto'
21
+ import { closeSessionServices } from '../utils.js'
22
+ import { CoreAPIChannel } from '../channel/channel.types.js'
23
+ import { PikkuRequest } from '../pikku-request.js'
24
+ import { PikkuResponse } from '../pikku-response.js'
25
+ import { HTTPSessionService } from './http-session-service.js'
26
+ import { getSchema, validateAndCoerce } from '../schema.js'
27
+
28
+ if (!globalThis.pikku?.httpRoutes) {
29
+ globalThis.pikku = globalThis.pikku || {}
30
+ globalThis.pikku.httpRoutes = []
31
+ globalThis.pikku.httpRoutesMeta = []
32
+ }
33
+
34
+ const httpRoutes = (
35
+ data?: CoreHTTPFunctionRoute<any, any, any>[]
36
+ ): CoreHTTPFunctionRoute<any, any, any>[] => {
37
+ if (data) {
38
+ globalThis.pikku.httpRoutes = data
39
+ }
40
+ return globalThis.pikku.httpRoutes
41
+ }
42
+
43
+ const httpRoutesMeta = (data?: HTTPRoutesMeta): HTTPRoutesMeta => {
44
+ if (data) {
45
+ globalThis.pikku.httpRoutesMeta = data
46
+ }
47
+ return globalThis.pikku.httpRoutesMeta
48
+ }
49
+
50
+ export const addRoute = <
51
+ In,
52
+ Out,
53
+ Route extends string,
54
+ APIFunction,
55
+ APIFunctionSessionless,
56
+ APIPermission,
57
+ >(
58
+ route: CoreHTTPFunctionRoute<
59
+ In,
60
+ Out,
61
+ Route,
62
+ APIFunction,
63
+ APIFunctionSessionless,
64
+ APIPermission
65
+ >
66
+ ) => {
67
+ httpRoutes().push(route as any)
68
+ }
69
+
70
+ export const clearRoutes = () => {
71
+ httpRoutes([])
72
+ }
73
+
74
+ /**
75
+ * @ignore
76
+ */
77
+ export const setHTTPRoutesMeta = (_routeMeta: HTTPRoutesMeta) => {
78
+ httpRoutesMeta(_routeMeta)
79
+ }
80
+
81
+ /**
82
+ * Returns all the registered routes and associated metadata.
83
+ * @internal
84
+ */
85
+ export const getRoutes = () => {
86
+ return {
87
+ routes: httpRoutes(),
88
+ routesMeta: httpRoutesMeta(),
89
+ }
90
+ }
91
+
92
+ const getMatchingRoute = (
93
+ schemaService: SchemaService | undefined,
94
+ requestType: string,
95
+ requestPath: string
96
+ ) => {
97
+ 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.
101
+ if (route.method !== requestType.toLowerCase()) {
102
+ continue
103
+ }
104
+ const matchFunc = match(`/${route.route}`.replace(/^\/\//, '/'), {
105
+ decode: decodeURIComponent,
106
+ })
107
+ const matchedPath = matchFunc(requestPath.replace(/^\/\//, '/'))
108
+
109
+ if (matchedPath) {
110
+ // TODO: Cache this loop as a performance improvement
111
+ const schemaName = httpRoutesMeta().find(
112
+ (routeMeta) =>
113
+ routeMeta.method === route.method && routeMeta.route === route.route
114
+ )?.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
+
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
179
+ }
180
+ }
181
+ }
182
+
183
+ return undefined
184
+ }
185
+
186
+ export const createHTTPInteraction = (
187
+ request: PikkuRequest | undefined,
188
+ response: PikkuResponse | undefined
189
+ ) => {
190
+ let http: PikkuHTTP | undefined = undefined
191
+ if (
192
+ request instanceof PikkuHTTPAbstractRequest ||
193
+ response instanceof PikkuHTTPAbstractResponse
194
+ ) {
195
+ http = {}
196
+ if (request instanceof PikkuHTTPAbstractRequest) {
197
+ http.request = request
198
+ }
199
+ if (response instanceof PikkuHTTPAbstractResponse) {
200
+ http.response = response
201
+ }
202
+ }
203
+ return http
204
+ }
205
+
206
+ export const handleError = (
207
+ e: any,
208
+ 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
217
+ }
218
+
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
+ })
227
+
228
+ if (logWarningsForStatusCodes.includes(errorResponse.status)) {
229
+ logger.warn(`Warning id: ${trackerId}`)
230
+ logger.warn(e)
231
+ }
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
+
239
+ if (e instanceof NotFoundError) {
240
+ http?.response?.end()
241
+ }
242
+
243
+ if (bubbleError) {
244
+ throw e
245
+ }
246
+ }
247
+
248
+ /**
249
+ * @ignore
250
+ */
251
+ export const runHTTPRoute = async <In, Out>({
252
+ singletonServices,
253
+ request,
254
+ response,
255
+ createSessionServices,
256
+ route: apiRoute,
257
+ method: apiType,
258
+ skipUserSession = false,
259
+ respondWith404 = true,
260
+ logWarningsForStatusCodes = [],
261
+ coerceToArray = false,
262
+ bubbleErrors = false,
263
+ }: Pick<CoreHTTPFunctionRoute<unknown, unknown, any>, 'route' | 'method'> &
264
+ RunRouteOptions &
265
+ RunRouteParams<In>): Promise<Out | void> => {
266
+ let sessionServices: SessionServices<typeof singletonServices> | undefined
267
+ const trackerId: string = crypto.randomUUID().toString()
268
+
269
+ const http = createHTTPInteraction(request, response)
270
+
271
+ const matchedRoute = getMatchingRoute(
272
+ singletonServices.schemaService,
273
+ apiType,
274
+ apiRoute
275
+ )
276
+
277
+ try {
278
+ if (!matchedRoute) {
279
+ singletonServices.logger.info({
280
+ message: 'Route not found',
281
+ apiRoute,
282
+ apiType,
283
+ })
284
+ throw new NotFoundError(`Route not found: ${apiRoute}`)
285
+ }
286
+
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,
298
+ 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()
348
+
349
+ return result
350
+ } catch (e: any) {
351
+ handleError(
352
+ e,
353
+ http,
354
+ trackerId,
355
+ singletonServices.logger,
356
+ logWarningsForStatusCodes,
357
+ respondWith404,
358
+ bubbleErrors
359
+ )
360
+ } finally {
361
+ if (sessionServices) {
362
+ await closeSessionServices(singletonServices.logger, sessionServices)
363
+ }
364
+ }
365
+ }