@pikku/core 0.12.4 → 0.12.5

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 (229) hide show
  1. package/CHANGELOG.md +11 -1
  2. package/dist/dev/hot-reload.d.ts +10 -0
  3. package/dist/dev/hot-reload.js +158 -0
  4. package/dist/middleware-runner.d.ts +1 -0
  5. package/dist/middleware-runner.js +5 -0
  6. package/dist/permissions.d.ts +1 -0
  7. package/dist/permissions.js +5 -0
  8. package/dist/services/in-memory-workflow-service.js +7 -11
  9. package/dist/wirings/ai-agent/ai-agent-prepare.js +16 -1
  10. package/dist/wirings/channel/channel-middleware-runner.d.ts +1 -0
  11. package/dist/wirings/channel/channel-middleware-runner.js +5 -0
  12. package/dist/wirings/workflow/pikku-workflow-service.js +7 -8
  13. package/package.json +3 -2
  14. package/src/crypto-utils.test.ts +214 -0
  15. package/src/crypto-utils.ts +213 -0
  16. package/src/dev/hot-reload.test.ts +484 -0
  17. package/src/dev/hot-reload.ts +212 -0
  18. package/src/errors/error-handler.ts +62 -0
  19. package/src/errors/error.test.ts +195 -0
  20. package/src/errors/errors.ts +374 -0
  21. package/src/errors/index.ts +2 -0
  22. package/src/factory-functions.test.ts +109 -0
  23. package/src/function/function-runner.test.ts +536 -0
  24. package/src/function/function-runner.ts +365 -0
  25. package/src/function/functions.types.ts +304 -0
  26. package/src/function/index.ts +5 -0
  27. package/src/handle-error.test.ts +424 -0
  28. package/src/handle-error.ts +69 -0
  29. package/src/index.ts +118 -0
  30. package/src/internal.ts +6 -0
  31. package/src/middleware/auth-apikey.test.ts +363 -0
  32. package/src/middleware/auth-apikey.ts +47 -0
  33. package/src/middleware/auth-bearer.test.ts +450 -0
  34. package/src/middleware/auth-bearer.ts +72 -0
  35. package/src/middleware/auth-cookie.test.ts +528 -0
  36. package/src/middleware/auth-cookie.ts +80 -0
  37. package/src/middleware/cors.test.ts +424 -0
  38. package/src/middleware/cors.ts +104 -0
  39. package/src/middleware/index.ts +5 -0
  40. package/src/middleware/remote-auth.test.ts +488 -0
  41. package/src/middleware/remote-auth.ts +68 -0
  42. package/src/middleware/timeout.ts +15 -0
  43. package/src/middleware-runner.test.ts +418 -0
  44. package/src/middleware-runner.ts +240 -0
  45. package/src/permissions.test.ts +434 -0
  46. package/src/permissions.ts +327 -0
  47. package/src/pikku-request.ts +23 -0
  48. package/src/pikku-response.ts +5 -0
  49. package/src/pikku-state.test.ts +224 -0
  50. package/src/pikku-state.ts +216 -0
  51. package/src/run-tests-script.test.ts +49 -0
  52. package/src/schema.test.ts +249 -0
  53. package/src/schema.ts +151 -0
  54. package/src/services/ai-agent-runner-service.ts +41 -0
  55. package/src/services/ai-run-state-service.ts +20 -0
  56. package/src/services/ai-storage-service.ts +39 -0
  57. package/src/services/content-service.ts +76 -0
  58. package/src/services/deployment-service.ts +22 -0
  59. package/src/services/gateway-service.ts +20 -0
  60. package/src/services/gopass-secrets.ts +98 -0
  61. package/src/services/in-memory-ai-run-state-service.ts +73 -0
  62. package/src/services/in-memory-trigger-service.ts +64 -0
  63. package/src/services/in-memory-workflow-service.test.ts +351 -0
  64. package/src/services/in-memory-workflow-service.ts +502 -0
  65. package/src/services/index.ts +56 -0
  66. package/src/services/jwt-service.ts +30 -0
  67. package/src/services/local-content.ts +120 -0
  68. package/src/services/local-gateway-service.ts +62 -0
  69. package/src/services/local-secrets.test.ts +80 -0
  70. package/src/services/local-secrets.ts +94 -0
  71. package/src/services/local-variables.test.ts +61 -0
  72. package/src/services/local-variables.ts +39 -0
  73. package/src/services/logger-console.test.ts +118 -0
  74. package/src/services/logger-console.ts +98 -0
  75. package/src/services/logger.ts +57 -0
  76. package/src/services/scheduler-service.ts +86 -0
  77. package/src/services/schema-service.ts +33 -0
  78. package/src/services/scoped-secret-service.test.ts +74 -0
  79. package/src/services/scoped-secret-service.ts +41 -0
  80. package/src/services/secret-service.ts +38 -0
  81. package/src/services/trigger-service.ts +17 -0
  82. package/src/services/typed-secret-service.test.ts +93 -0
  83. package/src/services/typed-secret-service.ts +70 -0
  84. package/src/services/typed-variables-service.test.ts +73 -0
  85. package/src/services/typed-variables-service.ts +81 -0
  86. package/src/services/user-session-service.test.ts +113 -0
  87. package/src/services/user-session-service.ts +86 -0
  88. package/src/services/variables-service.ts +11 -0
  89. package/src/services/workflow-service.ts +95 -0
  90. package/src/testing/index.ts +2 -0
  91. package/src/testing/service-tests.ts +873 -0
  92. package/src/time-utils.test.ts +56 -0
  93. package/src/time-utils.ts +107 -0
  94. package/src/types/core.types.ts +478 -0
  95. package/src/types/state.types.ts +188 -0
  96. package/src/utils/hash.test.ts +68 -0
  97. package/src/utils/hash.ts +26 -0
  98. package/src/utils.test.ts +350 -0
  99. package/src/utils.ts +129 -0
  100. package/src/version.test.ts +80 -0
  101. package/src/version.ts +25 -0
  102. package/src/wirings/ai-agent/agent-dynamic-workflow.ts +469 -0
  103. package/src/wirings/ai-agent/ai-agent-helpers.test.ts +152 -0
  104. package/src/wirings/ai-agent/ai-agent-helpers.ts +76 -0
  105. package/src/wirings/ai-agent/ai-agent-memory.ts +310 -0
  106. package/src/wirings/ai-agent/ai-agent-model-config.test.ts +115 -0
  107. package/src/wirings/ai-agent/ai-agent-model-config.ts +43 -0
  108. package/src/wirings/ai-agent/ai-agent-prepare.ts +659 -0
  109. package/src/wirings/ai-agent/ai-agent-registry.test.ts +37 -0
  110. package/src/wirings/ai-agent/ai-agent-registry.ts +82 -0
  111. package/src/wirings/ai-agent/ai-agent-runner.test.ts +319 -0
  112. package/src/wirings/ai-agent/ai-agent-runner.ts +773 -0
  113. package/src/wirings/ai-agent/ai-agent-stream.test.ts +859 -0
  114. package/src/wirings/ai-agent/ai-agent-stream.ts +1153 -0
  115. package/src/wirings/ai-agent/ai-agent.types.ts +382 -0
  116. package/src/wirings/ai-agent/index.ts +37 -0
  117. package/src/wirings/channel/channel-common.ts +90 -0
  118. package/src/wirings/channel/channel-handler.ts +250 -0
  119. package/src/wirings/channel/channel-middleware-runner.ts +126 -0
  120. package/src/wirings/channel/channel-runner.ts +166 -0
  121. package/src/wirings/channel/channel-store.ts +29 -0
  122. package/src/wirings/channel/channel.types.ts +172 -0
  123. package/src/wirings/channel/define-channel-routes.ts +25 -0
  124. package/src/wirings/channel/eventhub-service.ts +34 -0
  125. package/src/wirings/channel/eventhub-store.ts +13 -0
  126. package/src/wirings/channel/index.ts +24 -0
  127. package/src/wirings/channel/local/index.ts +3 -0
  128. package/src/wirings/channel/local/local-channel-handler.ts +81 -0
  129. package/src/wirings/channel/local/local-channel-runner.test.ts +215 -0
  130. package/src/wirings/channel/local/local-channel-runner.ts +210 -0
  131. package/src/wirings/channel/local/local-eventhub-service.test.ts +95 -0
  132. package/src/wirings/channel/local/local-eventhub-service.ts +101 -0
  133. package/src/wirings/channel/log-channels.ts +20 -0
  134. package/src/wirings/channel/pikku-abstract-channel-handler.test.ts +54 -0
  135. package/src/wirings/channel/pikku-abstract-channel-handler.ts +45 -0
  136. package/src/wirings/channel/serverless/index.ts +5 -0
  137. package/src/wirings/channel/serverless/serverless-channel-runner.ts +289 -0
  138. package/src/wirings/cli/channel/cli-channel-runner.ts +136 -0
  139. package/src/wirings/cli/channel/index.ts +1 -0
  140. package/src/wirings/cli/cli-runner.test.ts +403 -0
  141. package/src/wirings/cli/cli-runner.ts +529 -0
  142. package/src/wirings/cli/cli.types.ts +358 -0
  143. package/src/wirings/cli/command-parser.test.ts +445 -0
  144. package/src/wirings/cli/command-parser.ts +504 -0
  145. package/src/wirings/cli/define-cli-commands.ts +24 -0
  146. package/src/wirings/cli/index.ts +19 -0
  147. package/src/wirings/gateway/gateway-runner.test.ts +474 -0
  148. package/src/wirings/gateway/gateway-runner.ts +411 -0
  149. package/src/wirings/gateway/gateway.types.ts +149 -0
  150. package/src/wirings/gateway/index.ts +13 -0
  151. package/src/wirings/http/http-routes.test.ts +322 -0
  152. package/src/wirings/http/http-routes.ts +197 -0
  153. package/src/wirings/http/http-runner.test.ts +144 -0
  154. package/src/wirings/http/http-runner.ts +588 -0
  155. package/src/wirings/http/http.types.ts +369 -0
  156. package/src/wirings/http/index.ts +28 -0
  157. package/src/wirings/http/log-http-routes.ts +22 -0
  158. package/src/wirings/http/pikku-fetch-http-request.test.ts +237 -0
  159. package/src/wirings/http/pikku-fetch-http-request.ts +170 -0
  160. package/src/wirings/http/pikku-fetch-http-response.test.ts +82 -0
  161. package/src/wirings/http/pikku-fetch-http-response.ts +147 -0
  162. package/src/wirings/http/routers/http-router.ts +13 -0
  163. package/src/wirings/http/routers/path-to-regex.test.ts +319 -0
  164. package/src/wirings/http/routers/path-to-regex.ts +126 -0
  165. package/src/wirings/http/web-request.test.ts +236 -0
  166. package/src/wirings/http/web-request.ts +104 -0
  167. package/src/wirings/mcp/index.ts +27 -0
  168. package/src/wirings/mcp/mcp-endpoint-registry.test.ts +389 -0
  169. package/src/wirings/mcp/mcp-endpoint-registry.ts +159 -0
  170. package/src/wirings/mcp/mcp-runner.ts +323 -0
  171. package/src/wirings/mcp/mcp.types.ts +216 -0
  172. package/src/wirings/node/index.ts +2 -0
  173. package/src/wirings/node/node.types.ts +23 -0
  174. package/src/wirings/oauth2/index.ts +3 -0
  175. package/src/wirings/oauth2/oauth2-client.test.ts +929 -0
  176. package/src/wirings/oauth2/oauth2-client.ts +335 -0
  177. package/src/wirings/oauth2/oauth2.types.ts +69 -0
  178. package/src/wirings/oauth2/wire-oauth2-credential.ts +23 -0
  179. package/src/wirings/queue/index.ts +31 -0
  180. package/src/wirings/queue/queue-runner.test.ts +710 -0
  181. package/src/wirings/queue/queue-runner.ts +192 -0
  182. package/src/wirings/queue/queue.types.ts +189 -0
  183. package/src/wirings/queue/register-queue-helper.ts +60 -0
  184. package/src/wirings/queue/validate-worker-config.test.ts +108 -0
  185. package/src/wirings/queue/validate-worker-config.ts +116 -0
  186. package/src/wirings/rpc/index.ts +4 -0
  187. package/src/wirings/rpc/rpc-runner.ts +460 -0
  188. package/src/wirings/rpc/rpc-types.ts +56 -0
  189. package/src/wirings/rpc/wire-addon.ts +21 -0
  190. package/src/wirings/scheduler/index.ts +11 -0
  191. package/src/wirings/scheduler/log-schedulers.ts +20 -0
  192. package/src/wirings/scheduler/scheduler-runner.test.ts +660 -0
  193. package/src/wirings/scheduler/scheduler-runner.ts +133 -0
  194. package/src/wirings/scheduler/scheduler.types.ts +53 -0
  195. package/src/wirings/secret/index.ts +9 -0
  196. package/src/wirings/secret/secret.types.ts +32 -0
  197. package/src/wirings/secret/validate-secret-definitions.test.ts +140 -0
  198. package/src/wirings/secret/validate-secret-definitions.ts +82 -0
  199. package/src/wirings/trigger/index.ts +10 -0
  200. package/src/wirings/trigger/pikku-trigger-service.ts +112 -0
  201. package/src/wirings/trigger/trigger-runner.test.ts +79 -0
  202. package/src/wirings/trigger/trigger-runner.ts +135 -0
  203. package/src/wirings/trigger/trigger.types.ts +178 -0
  204. package/src/wirings/variable/index.ts +8 -0
  205. package/src/wirings/variable/validate-variable-definitions.test.ts +91 -0
  206. package/src/wirings/variable/validate-variable-definitions.ts +69 -0
  207. package/src/wirings/variable/variable.types.ts +22 -0
  208. package/src/wirings/workflow/dsl/index.ts +31 -0
  209. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +320 -0
  210. package/src/wirings/workflow/dsl/workflow-runner.ts +28 -0
  211. package/src/wirings/workflow/graph/graph-node.ts +222 -0
  212. package/src/wirings/workflow/graph/graph-runner.test.ts +487 -0
  213. package/src/wirings/workflow/graph/graph-runner.ts +816 -0
  214. package/src/wirings/workflow/graph/graph-validation.test.ts +170 -0
  215. package/src/wirings/workflow/graph/graph-validation.ts +237 -0
  216. package/src/wirings/workflow/graph/index.ts +19 -0
  217. package/src/wirings/workflow/graph/template.test.ts +49 -0
  218. package/src/wirings/workflow/graph/template.ts +42 -0
  219. package/src/wirings/workflow/graph/wire-workflow-graph.ts +29 -0
  220. package/src/wirings/workflow/graph/workflow-graph.types.ts +104 -0
  221. package/src/wirings/workflow/index.ts +82 -0
  222. package/src/wirings/workflow/pikku-workflow-service.test.ts +200 -0
  223. package/src/wirings/workflow/pikku-workflow-service.ts +1179 -0
  224. package/src/wirings/workflow/workflow-helpers.test.ts +129 -0
  225. package/src/wirings/workflow/workflow-helpers.ts +79 -0
  226. package/src/wirings/workflow/workflow.types.ts +274 -0
  227. package/tsconfig.json +14 -0
  228. package/tsconfig.tsbuildinfo +1 -0
  229. package/lcov.info +0 -18891
@@ -0,0 +1,588 @@
1
+ import type {
2
+ CoreHTTPFunctionWiring,
3
+ RunHTTPWiringOptions,
4
+ PikkuHTTP,
5
+ PikkuHTTPRequest,
6
+ PikkuHTTPResponse,
7
+ HTTPWiringMeta,
8
+ HTTPMethod,
9
+ } from './http.types.js'
10
+ import type {
11
+ CorePikkuFunction,
12
+ CorePikkuFunctionSessionless,
13
+ CorePikkuPermission,
14
+ CorePermissionGroup,
15
+ } from '../../function/functions.types.js'
16
+ import type {
17
+ CoreUserSession,
18
+ CorePikkuMiddleware,
19
+ CorePikkuMiddlewareGroup,
20
+ WireServices,
21
+ PikkuWire,
22
+ PikkuWiringTypes,
23
+ } from '../../types/core.types.js'
24
+ import { NotFoundError, PikkuMissingMetaError } from '../../errors/errors.js'
25
+ import {
26
+ closeWireServices,
27
+ createWeakUID,
28
+ isSerializable,
29
+ } from '../../utils.js'
30
+ import {
31
+ getSingletonServices,
32
+ getCreateWireServices,
33
+ } from '../../pikku-state.js'
34
+ import { PikkuSessionService } from '../../services/user-session-service.js'
35
+ import { handleHTTPError } from '../../handle-error.js'
36
+ import { pikkuState } from '../../pikku-state.js'
37
+ import { PikkuFetchHTTPResponse } from './pikku-fetch-http-response.js'
38
+ import { PikkuFetchHTTPRequest } from './pikku-fetch-http-request.js'
39
+ import type { PikkuChannel } from '../channel/channel.types.js'
40
+ import { addFunction, runPikkuFunc } from '../../function/function-runner.js'
41
+ import { applyWebResponse } from './web-request.js'
42
+ import { httpRouter } from './routers/http-router.js'
43
+ import { validateSchema } from '../../schema.js'
44
+ import { runMiddleware } from '../../middleware-runner.js'
45
+
46
+ /**
47
+ * Extract headers from a PikkuHTTPRequest based on the schema keys
48
+ */
49
+ function extractHeadersFromRequest(
50
+ request: PikkuHTTPRequest,
51
+ headerKeys: string[]
52
+ ): Record<string, string | string[] | undefined> {
53
+ const headers: Record<string, string | string[] | undefined> = {}
54
+ for (const headerName of headerKeys) {
55
+ const value = request.header(headerName)
56
+ if (value !== null) {
57
+ headers[headerName] = value
58
+ }
59
+ }
60
+ return headers
61
+ }
62
+
63
+ /**
64
+ * Registers HTTP middleware for a specific route pattern.
65
+ *
66
+ * This function registers middleware at runtime that will be applied to
67
+ * HTTP routes matching the specified pattern.
68
+ *
69
+ * For tree-shaking benefits, wrap in a factory function:
70
+ * `export const x = () => addHTTPMiddleware('pattern', [...])`
71
+ *
72
+ * @template PikkuMiddleware The middleware type.
73
+ * @param {string} pattern - Route pattern (e.g., '*' for all routes, '/api/*' for specific routes).
74
+ * @param {CorePikkuMiddlewareGroup} middleware - Array of middleware for this route pattern.
75
+ *
76
+ * @returns {CorePikkuMiddlewareGroup} The middleware array (for chaining/wrapping).
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * // Recommended: tree-shakeable
81
+ * export const httpGlobal = () => addHTTPMiddleware('*', [
82
+ * corsMiddleware,
83
+ * loggingMiddleware
84
+ * ])
85
+ *
86
+ * // Also works: no tree-shaking
87
+ * export const apiMiddleware = addHTTPMiddleware('/api/*', [
88
+ * authMiddleware
89
+ * ])
90
+ * ```
91
+ */
92
+ export const addHTTPMiddleware = <PikkuMiddleware extends CorePikkuMiddleware>(
93
+ pattern: string,
94
+ middleware: CorePikkuMiddlewareGroup,
95
+ packageName: string | null = null
96
+ ): CorePikkuMiddlewareGroup => {
97
+ const httpGroups = pikkuState(packageName, 'middleware', 'httpGroup')
98
+ httpGroups[pattern] = middleware
99
+ return middleware
100
+ }
101
+
102
+ /**
103
+ * Registers HTTP permissions for a specific route pattern.
104
+ *
105
+ * This function registers permissions at runtime that will be applied to
106
+ * HTTP routes matching the specified pattern.
107
+ *
108
+ * For tree-shaking benefits, wrap in a factory function:
109
+ * `export const x = () => addHTTPPermission('pattern', [...])`
110
+ *
111
+ * @template PikkuPermission The permission type.
112
+ * @param {string} pattern - Route pattern (e.g., '*' for all routes, '/api/*' for specific routes).
113
+ * @param {CorePermissionGroup | CorePikkuPermission[]} permissions - Permissions for this route pattern.
114
+ *
115
+ * @returns {CorePermissionGroup | CorePikkuPermission[]} The permissions (for chaining/wrapping).
116
+ *
117
+ * @example
118
+ * ```typescript
119
+ * // Recommended: tree-shakeable
120
+ * export const httpGlobalPermissions = () => addHTTPPermission('*', [
121
+ * authenticatedPermission,
122
+ * rateLimitPermission
123
+ * ])
124
+ *
125
+ * // Also works: no tree-shaking
126
+ * export const apiPermissions = addHTTPPermission('/api/*', [
127
+ * adminPermission
128
+ * ])
129
+ * ```
130
+ */
131
+ export const addHTTPPermission = <PikkuPermission extends CorePikkuPermission>(
132
+ pattern: string,
133
+ permissions: CorePermissionGroup | CorePikkuPermission[],
134
+ packageName: string | null = null
135
+ ): CorePermissionGroup | CorePikkuPermission[] => {
136
+ const httpGroups = pikkuState(packageName, 'permissions', 'httpGroup')
137
+ httpGroups[pattern] = permissions
138
+ return permissions
139
+ }
140
+
141
+ /**
142
+ * Adds a new route to the global HTTP route registry.
143
+ *
144
+ * The route configuration includes the HTTP method, route path, permissions,
145
+ * middleware, and the handler function that implements the route's logic.
146
+ *
147
+ * @template In Expected input type.
148
+ * @template Out Expected output type.
149
+ * @template Route Route pattern as a string.
150
+ * @template PikkuFunction Type for the route handler function.
151
+ * @template PikkuFunctionSessionless Type for a sessionless handler.
152
+ * @template PikkuPermissionGroup Type representing required permissions.
153
+ * @template PikkuMiddleware Middleware type to be used with the route.
154
+ * @param {CoreHTTPFunctionWiring<In, Out, Route, PikkuFunction, PikkuFunctionSessionless, PikkuPermission, PikkuMiddleware>} httpWiring - The HTTP wiring configuration object.
155
+ */
156
+ export const wireHTTP = <
157
+ In,
158
+ Out,
159
+ Route extends string,
160
+ PikkuFunction extends CorePikkuFunction<In, Out> = CorePikkuFunction<In, Out>,
161
+ PikkuFunctionSessionless extends CorePikkuFunctionSessionless<
162
+ In,
163
+ Out
164
+ > = CorePikkuFunctionSessionless<In, Out>,
165
+ PikkuPermissionGroup extends
166
+ CorePikkuPermission<In> = CorePikkuPermission<In>,
167
+ PikkuMiddleware extends CorePikkuMiddleware = CorePikkuMiddleware,
168
+ >(
169
+ httpWiring: CoreHTTPFunctionWiring<
170
+ In,
171
+ Out,
172
+ Route,
173
+ PikkuFunction,
174
+ PikkuFunctionSessionless,
175
+ PikkuPermissionGroup,
176
+ PikkuMiddleware
177
+ >
178
+ ) => {
179
+ const httpMeta = pikkuState(null, 'http', 'meta')
180
+ const routeMeta = httpMeta[httpWiring.method][httpWiring.route]
181
+ if (!routeMeta) {
182
+ throw new PikkuMissingMetaError(
183
+ `Missing generated metadata for HTTP route '${httpWiring.method.toUpperCase()} ${httpWiring.route}'`
184
+ )
185
+ }
186
+ if (httpWiring.func) {
187
+ addFunction(routeMeta.pikkuFuncId, httpWiring.func)
188
+ }
189
+ const routes = pikkuState(null, 'http', 'routes')
190
+ if (!routes.has(httpWiring.method)) {
191
+ routes.set(httpWiring.method, new Map())
192
+ }
193
+ pikkuState(null, 'http', 'routes')
194
+ .get(httpWiring.method)
195
+ ?.set(httpWiring.route, httpWiring as any)
196
+ }
197
+
198
+ /**
199
+ * Finds a matching route based on the HTTP method and URL path.
200
+ *
201
+ * Iterates over all registered routes, skipping those with a mismatched method.
202
+ * When a route pattern matches the incoming request path, this function aggregates
203
+ * any global middleware along with route-specific middleware and identifies any input schema.
204
+ *
205
+ * @param {string} requestType - The HTTP method (e.g., GET, POST).
206
+ * @param {string} requestPath - The URL path of the incoming request.
207
+ * @returns {Object | undefined} An object with matched route details or undefined if no match.
208
+ */
209
+ const getMatchingRoute = (requestType: string, requestPath: string) => {
210
+ const matchedPath = httpRouter.match(
211
+ requestType.toLowerCase() as HTTPMethod,
212
+ requestPath
213
+ )
214
+
215
+ if (matchedPath) {
216
+ const route = pikkuState(null, 'http', 'routes')
217
+ .get(requestType.toLowerCase() as HTTPMethod)!
218
+ .get(matchedPath.route)!
219
+ const meta = pikkuState(null, 'http', 'meta')[
220
+ requestType.toLowerCase() as PikkuWiringTypes
221
+ ][route.route]
222
+
223
+ return {
224
+ matchedPath,
225
+ params: matchedPath.params,
226
+ route,
227
+ permissions: route.permissions,
228
+ meta: meta!,
229
+ }
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Combines the request and response objects into a single HTTP wire object.
235
+ *
236
+ * This utility function creates an object that holds both the HTTP request and response,
237
+ * which simplifies passing these around through middleware and route execution.
238
+ *
239
+ * @param {PikkuHTTPRequest | undefined} request - The HTTP request object.
240
+ * @param {PikkuHTTPResponse | undefined} response - The HTTP response object.
241
+ * @returns {PikkuHTTP | undefined} The combined HTTP wire object or undefined if none provided.
242
+ */
243
+ export const createHTTPWire = (
244
+ request: PikkuHTTPRequest | undefined,
245
+ response: PikkuHTTPResponse | undefined
246
+ ): PikkuHTTP | undefined => {
247
+ let http: PikkuHTTP | undefined = undefined
248
+
249
+ if (request || response) {
250
+ http = {}
251
+ if (request) {
252
+ http.request = request
253
+ }
254
+ if (response) {
255
+ http.response = response
256
+ }
257
+ }
258
+
259
+ return http
260
+ }
261
+
262
+ /**
263
+ * Validates the input data and executes the route handler
264
+ *
265
+ * This function performs these steps:
266
+ * 1. Sets URL parameters on the request.
267
+ * 2. Validates the user session if required.
268
+ * 3. Creates session-specific services.
269
+ * 4. Validates the incoming data against a schema.
270
+ * 5. Optionally coerces query string values to arrays.
271
+ * 6. Checks route-specific permissions.
272
+ * 7. Executes the route handler.
273
+ * 8. Sends the appropriate response.
274
+ *
275
+ * @param {Object} services - A collection of shared services and utilities.
276
+ * @param {Object} matchedRoute - Contains route details, URL parameters, and optional schema.
277
+ * @param {PikkuHTTP | undefined} http - The HTTP wire object.
278
+ * @param {Object} options - Options for route execution (e.g., whether to coerce query strings to arrays).
279
+ * @returns {Promise<any>} An object containing the route handler result and wire services (if any).
280
+ * @throws Throws errors like MissingSessionError or ForbiddenError on validation failures.
281
+ */
282
+ const executeRoute = async (
283
+ services: {
284
+ singletonServices: any
285
+ createWireServices?: any
286
+ skipUserSession: boolean
287
+ requestId: string
288
+ },
289
+ matchedRoute: {
290
+ matchedPath: any
291
+ params: any
292
+ route: CoreHTTPFunctionWiring<any, any, any>
293
+ meta: HTTPWiringMeta
294
+ },
295
+ http: PikkuHTTP,
296
+ options: {
297
+ coerceDataFromSchema: boolean
298
+ }
299
+ ) => {
300
+ const userSession = new PikkuSessionService<CoreUserSession>()
301
+ const { params, route, meta } = matchedRoute
302
+ const { singletonServices, createWireServices, skipUserSession, requestId } =
303
+ services
304
+
305
+ // Attach URL parameters to the request object
306
+ http?.request?.setParams(params)
307
+
308
+ // Validate request headers if schema is defined
309
+ if (meta.headersSchemaName && http.request && singletonServices.schema) {
310
+ const headerKeys = singletonServices.schema.getSchemaKeys(
311
+ meta.headersSchemaName
312
+ )
313
+ const rawHeaders = extractHeadersFromRequest(http.request, headerKeys)
314
+ await validateSchema(
315
+ singletonServices.logger,
316
+ singletonServices.schema,
317
+ meta.headersSchemaName,
318
+ rawHeaders
319
+ )
320
+ }
321
+
322
+ const requiresSession = route.auth !== false
323
+ let wireServices: any
324
+ let result: any
325
+
326
+ singletonServices.logger.info(
327
+ `Matched route: ${route.route} | method: ${route.method.toUpperCase()} | auth: ${requiresSession.toString()}`
328
+ )
329
+
330
+ // Ensure session is available when required
331
+ if (skipUserSession && requiresSession) {
332
+ throw new Error("Can't skip trying to get user session if auth is required")
333
+ }
334
+
335
+ let cachedData: unknown | undefined
336
+ const data = async () => {
337
+ if (cachedData === undefined) {
338
+ cachedData = await http.request!.data()
339
+ }
340
+ return cachedData
341
+ }
342
+ let channel: PikkuChannel<unknown, unknown> | undefined
343
+
344
+ if (matchedRoute.route.sse) {
345
+ const response = http?.response
346
+ if (!response) {
347
+ throw new Error('SSE requires a valid HTTP response object')
348
+ }
349
+ if (!response.setMode) {
350
+ throw new Error('Response object does not support SSE mode')
351
+ }
352
+ response.setMode('stream')
353
+ response.header('Content-Type', 'text/event-stream')
354
+ response.header('Cache-Control', 'no-cache')
355
+ response.header('Connection', 'keep-alive')
356
+ response.header('Transfer-Encoding', 'chunked')
357
+ channel = {
358
+ channelId: requestId,
359
+ openingData: await data(),
360
+ send: (data: any) => {
361
+ response.arrayBuffer(isSerializable(data) ? JSON.stringify(data) : data)
362
+ },
363
+ sendBinary: (data) => {
364
+ response.arrayBuffer(data)
365
+ },
366
+ close: () => {
367
+ channel!.state = 'closed'
368
+ response.close?.()
369
+ },
370
+ state: 'open',
371
+ }
372
+ }
373
+
374
+ const wire: PikkuWire = {
375
+ http,
376
+ channel,
377
+ session: userSession.get() as CoreUserSession | undefined,
378
+ setSession: (s: any) => userSession.setInitial(s),
379
+ getSession: () => userSession.get(),
380
+ hasSessionChanged: () => userSession.sessionChanged,
381
+ }
382
+
383
+ result = await runPikkuFunc(
384
+ 'http',
385
+ `${meta.method}:${meta.route}`,
386
+ meta.pikkuFuncId,
387
+ {
388
+ singletonServices,
389
+ createWireServices,
390
+ auth: route.auth !== false,
391
+ data,
392
+ inheritedMiddleware: meta.middleware,
393
+ wireMiddleware: route.middleware,
394
+ inheritedPermissions: meta.permissions,
395
+ wirePermissions: route.permissions,
396
+ coerceDataFromSchema: options.coerceDataFromSchema,
397
+ tags: route.tags,
398
+ wire,
399
+ sessionService: userSession,
400
+ packageName: meta.packageName,
401
+ }
402
+ )
403
+ if (!matchedRoute.route.sse) {
404
+ if (result instanceof Response) {
405
+ await applyWebResponse(http!.response!, result)
406
+ } else if (result === undefined || result === null) {
407
+ http?.response?.status(204)
408
+ } else if (route.returnsJSON === false) {
409
+ http?.response?.arrayBuffer(result)
410
+ } else {
411
+ http?.response?.status(200)
412
+ http?.response?.json(result)
413
+ }
414
+ }
415
+
416
+ return wireServices ? { result, wireServices } : { result }
417
+ }
418
+
419
+ /**
420
+ * Executes an HTTP route for a given Fetch API request.
421
+ *
422
+ * This function wraps the entire lifecycle of handling an HTTP request:
423
+ * - Matching the request to a registered route.
424
+ * - Validating input and session state.
425
+ * - Running middleware and the route handler.
426
+ * - Handling errors and forming the response.
427
+ *
428
+ * @template In Expected input data type.
429
+ * @template Out Expected output data type.
430
+ * @param {Request} request - The native Fetch API Request object.
431
+ * @param {RunHTTPWiringOptions} params - Additional options including services and session management.
432
+ * @returns {Promise<Response>} A promise that resolves to a Fetch API Response object.
433
+ */
434
+ export const fetch = async <In, Out>(
435
+ request: Request,
436
+ params: RunHTTPWiringOptions = {}
437
+ ): Promise<Response> => {
438
+ const pikkuResponse = new PikkuFetchHTTPResponse()
439
+ await fetchData<In, Out>(request, pikkuResponse, params)
440
+ return pikkuResponse.toResponse()
441
+ }
442
+
443
+ /**
444
+ * Executes an HTTP route using a Pikku-specific request wrapper.
445
+ *
446
+ * This variant accepts either a native Request or a PikkuHTTPRequest object and returns
447
+ * a PikkuFetchHTTPResponse for further manipulation if needed.
448
+ *
449
+ * @template In Expected input data type.
450
+ * @template Out Expected output data type.
451
+ * @param {Request | PikkuHTTPRequest} request - The request object.
452
+ * @param {RunHTTPWiringOptions} params - Execution options including services and session configuration.
453
+ * @returns {Promise<PikkuFetchHTTPResponse>} A promise that resolves to a PikkuFetchHTTPResponse object.
454
+ */
455
+ export const pikkuFetch = async <In, Out>(
456
+ request: Request | PikkuHTTPRequest,
457
+ params: RunHTTPWiringOptions = {}
458
+ ): Promise<PikkuFetchHTTPResponse> => {
459
+ const pikkuResponse = new PikkuFetchHTTPResponse()
460
+ await fetchData<In, Out>(request, pikkuResponse, params)
461
+ return pikkuResponse
462
+ }
463
+
464
+ /**
465
+ * Core function to process an HTTP request through route matching, validation,
466
+ * middleware execution, error handling, and session service cleanup.
467
+ *
468
+ * This function does the following:
469
+ * - Wraps the incoming request and response into an HTTP wire object.
470
+ * - Determines the correct route based on HTTP method and path.
471
+ * - Executes middleware and the route handler.
472
+ * - Catches and handles errors, optionally bubbling them if configured.
473
+ * - Cleans up any wire services created during processing.
474
+ *
475
+ * @template In Expected input data type.
476
+ * @template Out Expected output data type.
477
+ * @param {Request | PikkuHTTPRequest} request - The incoming HTTP request.
478
+ * @param {PikkuHTTPResponse} response - The response object to be populated.
479
+ * @param {RunHTTPWiringOptions} options - Options such as singleton services, session handling, and error configuration.
480
+ * @returns {Promise<Out | void>} The output from the route handler or void if an error occurred.
481
+ */
482
+ export const fetchData = async <In, Out>(
483
+ request: Request | PikkuHTTPRequest,
484
+ response: PikkuHTTPResponse,
485
+ {
486
+ skipUserSession = false,
487
+ respondWith404 = true,
488
+ logWarningsForStatusCodes = [],
489
+ coerceDataFromSchema = true,
490
+ bubbleErrors = false,
491
+ exposeErrors = false,
492
+ generateRequestId,
493
+ }: RunHTTPWiringOptions = {}
494
+ ): Promise<Out | void> => {
495
+ const singletonServices = getSingletonServices()
496
+ const createWireServices = getCreateWireServices()
497
+ let wireServices: WireServices<typeof singletonServices> | undefined
498
+ let result: Out
499
+
500
+ // Combine the request and response into one wire object
501
+ const pikkuRequest =
502
+ request instanceof Request ? new PikkuFetchHTTPRequest(request) : request
503
+ let requestId: string | null = null
504
+ try {
505
+ requestId = pikkuRequest.header('x-request-id')
506
+ } catch {}
507
+ requestId = requestId || generateRequestId?.() || createWeakUID()
508
+ const http = createHTTPWire(pikkuRequest, response)
509
+ const apiType = http!.request!.method()
510
+ const apiRoute = http!.request!.path()
511
+
512
+ // Locate the matching route based on the HTTP method and path
513
+ const matchedRoute = getMatchingRoute(apiType, apiRoute)
514
+ try {
515
+ if (!matchedRoute) {
516
+ if (apiType.toLowerCase() === 'options') {
517
+ const httpGroups = pikkuState(null, 'middleware', 'httpGroup')
518
+ const globalMiddleware = httpGroups['*']
519
+ const wire: PikkuWire = { http: http! }
520
+ if (globalMiddleware) {
521
+ await runMiddleware(
522
+ singletonServices,
523
+ wire,
524
+ globalMiddleware as CorePikkuMiddleware[]
525
+ )
526
+ }
527
+ response.status(204).json(undefined as any)
528
+ return
529
+ }
530
+ singletonServices.logger.info({
531
+ message: 'Route not found',
532
+ apiRoute,
533
+ apiType,
534
+ })
535
+ throw new NotFoundError(`Route not found: ${apiRoute}`)
536
+ }
537
+
538
+ // Execute the matched route along with its middleware and session management
539
+ ;({ result, wireServices } = await executeRoute(
540
+ {
541
+ singletonServices,
542
+ createWireServices,
543
+ skipUserSession,
544
+ requestId,
545
+ },
546
+ matchedRoute,
547
+ http!,
548
+ { coerceDataFromSchema }
549
+ ))
550
+
551
+ return result
552
+ } catch (e: any) {
553
+ if (matchedRoute?.route.sse) {
554
+ // For SSE routes, send error through the stream since the response is already in stream mode
555
+ singletonServices.logger.error(e instanceof Error ? e.message : e)
556
+ try {
557
+ response.arrayBuffer(
558
+ JSON.stringify({
559
+ type: 'error',
560
+ errorText: e instanceof Error ? e.message : String(e),
561
+ })
562
+ )
563
+ response.arrayBuffer('[DONE]')
564
+ } catch (streamErr: any) {
565
+ singletonServices.logger.error(
566
+ `SSE error while sending error payload: ${streamErr instanceof Error ? streamErr.message : String(streamErr)}`
567
+ )
568
+ }
569
+ response.close?.()
570
+ } else {
571
+ handleHTTPError(
572
+ e,
573
+ http,
574
+ requestId,
575
+ singletonServices.logger,
576
+ logWarningsForStatusCodes,
577
+ respondWith404,
578
+ bubbleErrors,
579
+ exposeErrors
580
+ )
581
+ }
582
+ } finally {
583
+ // Clean up any session-specific services created during processing
584
+ if (wireServices) {
585
+ await closeWireServices(singletonServices.logger, wireServices)
586
+ }
587
+ }
588
+ }