@pikku/core 0.12.4 → 0.12.6

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 (235) hide show
  1. package/CHANGELOG.md +21 -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/content-service.d.ts +2 -0
  9. package/dist/services/in-memory-workflow-service.d.ts +1 -0
  10. package/dist/services/in-memory-workflow-service.js +16 -11
  11. package/dist/services/workflow-service.d.ts +1 -0
  12. package/dist/wirings/ai-agent/ai-agent-prepare.js +16 -1
  13. package/dist/wirings/channel/channel-middleware-runner.d.ts +1 -0
  14. package/dist/wirings/channel/channel-middleware-runner.js +5 -0
  15. package/dist/wirings/http/http-runner.js +1 -1
  16. package/dist/wirings/workflow/pikku-workflow-service.d.ts +6 -0
  17. package/dist/wirings/workflow/pikku-workflow-service.js +51 -16
  18. package/dist/wirings/workflow/workflow.types.d.ts +2 -0
  19. package/package.json +5 -3
  20. package/src/crypto-utils.test.ts +214 -0
  21. package/src/crypto-utils.ts +213 -0
  22. package/src/dev/hot-reload.test.ts +484 -0
  23. package/src/dev/hot-reload.ts +212 -0
  24. package/src/errors/error-handler.ts +62 -0
  25. package/src/errors/error.test.ts +195 -0
  26. package/src/errors/errors.ts +374 -0
  27. package/src/errors/index.ts +2 -0
  28. package/src/factory-functions.test.ts +109 -0
  29. package/src/function/function-runner.test.ts +536 -0
  30. package/src/function/function-runner.ts +365 -0
  31. package/src/function/functions.types.ts +304 -0
  32. package/src/function/index.ts +5 -0
  33. package/src/handle-error.test.ts +424 -0
  34. package/src/handle-error.ts +69 -0
  35. package/src/index.ts +118 -0
  36. package/src/internal.ts +6 -0
  37. package/src/middleware/auth-apikey.test.ts +363 -0
  38. package/src/middleware/auth-apikey.ts +47 -0
  39. package/src/middleware/auth-bearer.test.ts +450 -0
  40. package/src/middleware/auth-bearer.ts +72 -0
  41. package/src/middleware/auth-cookie.test.ts +528 -0
  42. package/src/middleware/auth-cookie.ts +80 -0
  43. package/src/middleware/cors.test.ts +424 -0
  44. package/src/middleware/cors.ts +104 -0
  45. package/src/middleware/index.ts +5 -0
  46. package/src/middleware/remote-auth.test.ts +488 -0
  47. package/src/middleware/remote-auth.ts +68 -0
  48. package/src/middleware/timeout.ts +15 -0
  49. package/src/middleware-runner.test.ts +418 -0
  50. package/src/middleware-runner.ts +240 -0
  51. package/src/permissions.test.ts +434 -0
  52. package/src/permissions.ts +327 -0
  53. package/src/pikku-request.ts +23 -0
  54. package/src/pikku-response.ts +5 -0
  55. package/src/pikku-state.test.ts +224 -0
  56. package/src/pikku-state.ts +216 -0
  57. package/src/run-tests-script.test.ts +49 -0
  58. package/src/schema.test.ts +249 -0
  59. package/src/schema.ts +151 -0
  60. package/src/services/ai-agent-runner-service.ts +41 -0
  61. package/src/services/ai-run-state-service.ts +20 -0
  62. package/src/services/ai-storage-service.ts +39 -0
  63. package/src/services/content-service.ts +81 -0
  64. package/src/services/deployment-service.ts +22 -0
  65. package/src/services/gateway-service.ts +20 -0
  66. package/src/services/gopass-secrets.ts +98 -0
  67. package/src/services/in-memory-ai-run-state-service.ts +73 -0
  68. package/src/services/in-memory-trigger-service.ts +64 -0
  69. package/src/services/in-memory-workflow-service.test.ts +351 -0
  70. package/src/services/in-memory-workflow-service.ts +512 -0
  71. package/src/services/index.ts +56 -0
  72. package/src/services/jwt-service.ts +30 -0
  73. package/src/services/local-content.ts +120 -0
  74. package/src/services/local-gateway-service.ts +62 -0
  75. package/src/services/local-secrets.test.ts +80 -0
  76. package/src/services/local-secrets.ts +94 -0
  77. package/src/services/local-variables.test.ts +61 -0
  78. package/src/services/local-variables.ts +39 -0
  79. package/src/services/logger-console.test.ts +118 -0
  80. package/src/services/logger-console.ts +98 -0
  81. package/src/services/logger.ts +57 -0
  82. package/src/services/scheduler-service.ts +86 -0
  83. package/src/services/schema-service.ts +33 -0
  84. package/src/services/scoped-secret-service.test.ts +74 -0
  85. package/src/services/scoped-secret-service.ts +41 -0
  86. package/src/services/secret-service.ts +38 -0
  87. package/src/services/trigger-service.ts +17 -0
  88. package/src/services/typed-secret-service.test.ts +93 -0
  89. package/src/services/typed-secret-service.ts +70 -0
  90. package/src/services/typed-variables-service.test.ts +73 -0
  91. package/src/services/typed-variables-service.ts +81 -0
  92. package/src/services/user-session-service.test.ts +113 -0
  93. package/src/services/user-session-service.ts +86 -0
  94. package/src/services/variables-service.ts +11 -0
  95. package/src/services/workflow-service.ts +96 -0
  96. package/src/testing/index.ts +2 -0
  97. package/src/testing/service-tests.ts +873 -0
  98. package/src/time-utils.test.ts +56 -0
  99. package/src/time-utils.ts +107 -0
  100. package/src/types/core.types.ts +478 -0
  101. package/src/types/state.types.ts +188 -0
  102. package/src/utils/hash.test.ts +68 -0
  103. package/src/utils/hash.ts +26 -0
  104. package/src/utils.test.ts +350 -0
  105. package/src/utils.ts +129 -0
  106. package/src/version.test.ts +80 -0
  107. package/src/version.ts +25 -0
  108. package/src/wirings/ai-agent/agent-dynamic-workflow.ts +469 -0
  109. package/src/wirings/ai-agent/ai-agent-helpers.test.ts +152 -0
  110. package/src/wirings/ai-agent/ai-agent-helpers.ts +76 -0
  111. package/src/wirings/ai-agent/ai-agent-memory.ts +310 -0
  112. package/src/wirings/ai-agent/ai-agent-model-config.test.ts +115 -0
  113. package/src/wirings/ai-agent/ai-agent-model-config.ts +43 -0
  114. package/src/wirings/ai-agent/ai-agent-prepare.ts +659 -0
  115. package/src/wirings/ai-agent/ai-agent-registry.test.ts +37 -0
  116. package/src/wirings/ai-agent/ai-agent-registry.ts +82 -0
  117. package/src/wirings/ai-agent/ai-agent-runner.test.ts +319 -0
  118. package/src/wirings/ai-agent/ai-agent-runner.ts +773 -0
  119. package/src/wirings/ai-agent/ai-agent-stream.test.ts +859 -0
  120. package/src/wirings/ai-agent/ai-agent-stream.ts +1153 -0
  121. package/src/wirings/ai-agent/ai-agent.types.ts +382 -0
  122. package/src/wirings/ai-agent/index.ts +37 -0
  123. package/src/wirings/channel/channel-common.ts +90 -0
  124. package/src/wirings/channel/channel-handler.ts +250 -0
  125. package/src/wirings/channel/channel-middleware-runner.ts +126 -0
  126. package/src/wirings/channel/channel-runner.ts +166 -0
  127. package/src/wirings/channel/channel-store.ts +29 -0
  128. package/src/wirings/channel/channel.types.ts +172 -0
  129. package/src/wirings/channel/define-channel-routes.ts +25 -0
  130. package/src/wirings/channel/eventhub-service.ts +34 -0
  131. package/src/wirings/channel/eventhub-store.ts +13 -0
  132. package/src/wirings/channel/index.ts +24 -0
  133. package/src/wirings/channel/local/index.ts +3 -0
  134. package/src/wirings/channel/local/local-channel-handler.ts +81 -0
  135. package/src/wirings/channel/local/local-channel-runner.test.ts +215 -0
  136. package/src/wirings/channel/local/local-channel-runner.ts +210 -0
  137. package/src/wirings/channel/local/local-eventhub-service.test.ts +95 -0
  138. package/src/wirings/channel/local/local-eventhub-service.ts +101 -0
  139. package/src/wirings/channel/log-channels.ts +20 -0
  140. package/src/wirings/channel/pikku-abstract-channel-handler.test.ts +54 -0
  141. package/src/wirings/channel/pikku-abstract-channel-handler.ts +45 -0
  142. package/src/wirings/channel/serverless/index.ts +5 -0
  143. package/src/wirings/channel/serverless/serverless-channel-runner.ts +289 -0
  144. package/src/wirings/cli/channel/cli-channel-runner.ts +136 -0
  145. package/src/wirings/cli/channel/index.ts +1 -0
  146. package/src/wirings/cli/cli-runner.test.ts +403 -0
  147. package/src/wirings/cli/cli-runner.ts +529 -0
  148. package/src/wirings/cli/cli.types.ts +358 -0
  149. package/src/wirings/cli/command-parser.test.ts +445 -0
  150. package/src/wirings/cli/command-parser.ts +504 -0
  151. package/src/wirings/cli/define-cli-commands.ts +24 -0
  152. package/src/wirings/cli/index.ts +19 -0
  153. package/src/wirings/gateway/gateway-runner.test.ts +474 -0
  154. package/src/wirings/gateway/gateway-runner.ts +411 -0
  155. package/src/wirings/gateway/gateway.types.ts +149 -0
  156. package/src/wirings/gateway/index.ts +13 -0
  157. package/src/wirings/http/http-routes.test.ts +322 -0
  158. package/src/wirings/http/http-routes.ts +197 -0
  159. package/src/wirings/http/http-runner.test.ts +144 -0
  160. package/src/wirings/http/http-runner.ts +588 -0
  161. package/src/wirings/http/http.types.ts +369 -0
  162. package/src/wirings/http/index.ts +28 -0
  163. package/src/wirings/http/log-http-routes.ts +22 -0
  164. package/src/wirings/http/pikku-fetch-http-request.test.ts +237 -0
  165. package/src/wirings/http/pikku-fetch-http-request.ts +170 -0
  166. package/src/wirings/http/pikku-fetch-http-response.test.ts +82 -0
  167. package/src/wirings/http/pikku-fetch-http-response.ts +147 -0
  168. package/src/wirings/http/routers/http-router.ts +13 -0
  169. package/src/wirings/http/routers/path-to-regex.test.ts +319 -0
  170. package/src/wirings/http/routers/path-to-regex.ts +126 -0
  171. package/src/wirings/http/web-request.test.ts +236 -0
  172. package/src/wirings/http/web-request.ts +104 -0
  173. package/src/wirings/mcp/index.ts +27 -0
  174. package/src/wirings/mcp/mcp-endpoint-registry.test.ts +389 -0
  175. package/src/wirings/mcp/mcp-endpoint-registry.ts +159 -0
  176. package/src/wirings/mcp/mcp-runner.ts +323 -0
  177. package/src/wirings/mcp/mcp.types.ts +216 -0
  178. package/src/wirings/node/index.ts +2 -0
  179. package/src/wirings/node/node.types.ts +23 -0
  180. package/src/wirings/oauth2/index.ts +3 -0
  181. package/src/wirings/oauth2/oauth2-client.test.ts +929 -0
  182. package/src/wirings/oauth2/oauth2-client.ts +335 -0
  183. package/src/wirings/oauth2/oauth2.types.ts +69 -0
  184. package/src/wirings/oauth2/wire-oauth2-credential.ts +23 -0
  185. package/src/wirings/queue/index.ts +31 -0
  186. package/src/wirings/queue/queue-runner.test.ts +710 -0
  187. package/src/wirings/queue/queue-runner.ts +192 -0
  188. package/src/wirings/queue/queue.types.ts +189 -0
  189. package/src/wirings/queue/register-queue-helper.ts +60 -0
  190. package/src/wirings/queue/validate-worker-config.test.ts +108 -0
  191. package/src/wirings/queue/validate-worker-config.ts +116 -0
  192. package/src/wirings/rpc/index.ts +4 -0
  193. package/src/wirings/rpc/rpc-runner.ts +460 -0
  194. package/src/wirings/rpc/rpc-types.ts +56 -0
  195. package/src/wirings/rpc/wire-addon.ts +21 -0
  196. package/src/wirings/scheduler/index.ts +11 -0
  197. package/src/wirings/scheduler/log-schedulers.ts +20 -0
  198. package/src/wirings/scheduler/scheduler-runner.test.ts +660 -0
  199. package/src/wirings/scheduler/scheduler-runner.ts +133 -0
  200. package/src/wirings/scheduler/scheduler.types.ts +53 -0
  201. package/src/wirings/secret/index.ts +9 -0
  202. package/src/wirings/secret/secret.types.ts +32 -0
  203. package/src/wirings/secret/validate-secret-definitions.test.ts +140 -0
  204. package/src/wirings/secret/validate-secret-definitions.ts +82 -0
  205. package/src/wirings/trigger/index.ts +10 -0
  206. package/src/wirings/trigger/pikku-trigger-service.ts +112 -0
  207. package/src/wirings/trigger/trigger-runner.test.ts +79 -0
  208. package/src/wirings/trigger/trigger-runner.ts +135 -0
  209. package/src/wirings/trigger/trigger.types.ts +178 -0
  210. package/src/wirings/variable/index.ts +8 -0
  211. package/src/wirings/variable/validate-variable-definitions.test.ts +91 -0
  212. package/src/wirings/variable/validate-variable-definitions.ts +69 -0
  213. package/src/wirings/variable/variable.types.ts +22 -0
  214. package/src/wirings/workflow/dsl/index.ts +31 -0
  215. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +320 -0
  216. package/src/wirings/workflow/dsl/workflow-runner.ts +28 -0
  217. package/src/wirings/workflow/graph/graph-node.ts +222 -0
  218. package/src/wirings/workflow/graph/graph-runner.test.ts +487 -0
  219. package/src/wirings/workflow/graph/graph-runner.ts +816 -0
  220. package/src/wirings/workflow/graph/graph-validation.test.ts +170 -0
  221. package/src/wirings/workflow/graph/graph-validation.ts +237 -0
  222. package/src/wirings/workflow/graph/index.ts +19 -0
  223. package/src/wirings/workflow/graph/template.test.ts +49 -0
  224. package/src/wirings/workflow/graph/template.ts +42 -0
  225. package/src/wirings/workflow/graph/wire-workflow-graph.ts +29 -0
  226. package/src/wirings/workflow/graph/workflow-graph.types.ts +104 -0
  227. package/src/wirings/workflow/index.ts +82 -0
  228. package/src/wirings/workflow/pikku-workflow-service.test.ts +200 -0
  229. package/src/wirings/workflow/pikku-workflow-service.ts +1229 -0
  230. package/src/wirings/workflow/workflow-helpers.test.ts +129 -0
  231. package/src/wirings/workflow/workflow-helpers.ts +79 -0
  232. package/src/wirings/workflow/workflow.types.ts +276 -0
  233. package/tsconfig.json +14 -0
  234. package/tsconfig.tsbuildinfo +1 -0
  235. package/lcov.info +0 -18891
@@ -0,0 +1,250 @@
1
+ import type {
2
+ CoreServices,
3
+ JSONValue,
4
+ CoreUserSession,
5
+ } from '../../types/core.types.js'
6
+ import type {
7
+ BinaryData,
8
+ ChannelMessageMeta,
9
+ CoreChannel,
10
+ PikkuChannelHandler,
11
+ } from './channel.types.js'
12
+ import { pikkuState } from '../../pikku-state.js'
13
+ import { runPikkuFunc } from '../../function/function-runner.js'
14
+ import type { SessionService } from '../../services/user-session-service.js'
15
+ import { createMiddlewareSessionWireProps } from '../../services/user-session-service.js'
16
+
17
+ const getChannelMeta = (channelName: string) => {
18
+ return pikkuState(null, 'channel', 'meta')[channelName]
19
+ }
20
+
21
+ const getRouteMeta = (
22
+ channelName: string,
23
+ routingProperty?: string,
24
+ routerValue?: string
25
+ ): ChannelMessageMeta => {
26
+ const channelMeta = pikkuState(null, 'channel', 'meta')[channelName]
27
+ if (!channelMeta) {
28
+ throw new Error(`Channel ${channelName} not found`)
29
+ }
30
+ if (!routingProperty) {
31
+ if (!channelMeta.message) {
32
+ throw new Error(`Channel ${channelName} has no default message route`)
33
+ }
34
+ return channelMeta.message
35
+ }
36
+ if (!routerValue) {
37
+ throw new Error(
38
+ `Channel ${channelName} requires a router value for ${routingProperty}`
39
+ )
40
+ }
41
+ const route = channelMeta.messageWirings[routingProperty]?.[routerValue]
42
+ if (!route) {
43
+ throw new Error(
44
+ `Channel ${channelName} has no route for ${routingProperty}:${routerValue}`
45
+ )
46
+ }
47
+ return route
48
+ }
49
+
50
+ const validateAuth = (
51
+ requiresSession: boolean,
52
+ session: CoreUserSession | undefined,
53
+ onMessage: any
54
+ ) => {
55
+ const auth =
56
+ typeof onMessage === 'function'
57
+ ? requiresSession
58
+ : onMessage.auth === undefined
59
+ ? requiresSession
60
+ : onMessage.auth
61
+
62
+ if (auth && !session) {
63
+ return false
64
+ }
65
+ return true
66
+ }
67
+
68
+ export const processMessageHandlers = (
69
+ services: CoreServices,
70
+ channelConfig: CoreChannel<any, any>,
71
+ channelHandler: PikkuChannelHandler,
72
+ userSession?: SessionService<CoreUserSession>
73
+ ) => {
74
+ const logger = services.logger
75
+ const requiresSession = channelConfig.auth !== false
76
+
77
+ const processMessage = async (
78
+ data: JSONValue,
79
+ onMessage: any,
80
+ routingProperty?: string,
81
+ routerValue?: string
82
+ ): Promise<unknown> => {
83
+ if (
84
+ !validateAuth(
85
+ requiresSession,
86
+ userSession?.get() as CoreUserSession | undefined,
87
+ onMessage
88
+ )
89
+ ) {
90
+ const routeMessage = routingProperty
91
+ ? `route '${routingProperty}:${routerValue}'`
92
+ : 'the default message route'
93
+ logger.error(
94
+ `Channel ${channelConfig.name} with id ${channelHandler.getChannel().channelId} requires a session for ${routeMessage}`
95
+ )
96
+ // TODO: Send error message back breaks typescript, but should be implemented somehow
97
+ channelHandler.getChannel().send(`Unauthorized for ${routeMessage}`)
98
+ return
99
+ }
100
+
101
+ const routeMeta = getRouteMeta(
102
+ channelConfig.name,
103
+ routingProperty,
104
+ routerValue
105
+ )
106
+ const {
107
+ packageName,
108
+ middleware: routeInheritedMiddleware,
109
+ permissions: inheritedPermissions,
110
+ } = routeMeta
111
+
112
+ const pikkuFuncId = routeMeta.pikkuFuncId
113
+
114
+ // Get wire middleware: channel-level middleware + message-specific middleware
115
+ const channelWireMiddleware = channelConfig.middleware || []
116
+
117
+ // Check if onMessage is a wrapper object vs direct function config:
118
+ // - Direct config: onMessage.func is a plain Function
119
+ // - Wrapper: onMessage.func is a CorePikkuFunctionConfig (has onMessage.func.func)
120
+ // - Simple wrapper: onMessage has both func (plain Function) and middleware properties
121
+ const isWrapper =
122
+ onMessage &&
123
+ typeof onMessage === 'object' &&
124
+ 'func' in onMessage &&
125
+ ((typeof onMessage.func === 'object' && 'func' in onMessage.func) ||
126
+ 'middleware' in onMessage)
127
+ const messageWireMiddleware = isWrapper ? onMessage.middleware || [] : []
128
+
129
+ // Combine channel middleware with message middleware (actual functions)
130
+ // Channel middleware runs first, then message middleware
131
+ const wireMiddleware = [...channelWireMiddleware, ...messageWireMiddleware]
132
+
133
+ // Inherited middleware comes from metadata (tag groups, non-inline wire)
134
+ const inheritedMiddleware = routeInheritedMiddleware || []
135
+
136
+ const wirePermissions = isWrapper ? onMessage.permissions : undefined
137
+
138
+ // Create unique cache key that includes routing info to avoid cache collisions
139
+ // when multiple message handlers use the same function
140
+ const cacheKey = routingProperty
141
+ ? `${channelConfig.name}:${routingProperty}:${routerValue}`
142
+ : `${channelConfig.name}:default`
143
+
144
+ const channelMeta = getChannelMeta(channelConfig.name)
145
+ const wireChannelMiddleware = channelConfig.channelMiddleware || []
146
+
147
+ return await runPikkuFunc('channel', cacheKey, pikkuFuncId, {
148
+ singletonServices: services,
149
+ data: () => data,
150
+ inheritedMiddleware,
151
+ wireMiddleware,
152
+ inheritedChannelMiddleware: channelMeta?.channelMiddleware,
153
+ wireChannelMiddleware: wireChannelMiddleware as any,
154
+ inheritedPermissions,
155
+ wirePermissions,
156
+ coerceDataFromSchema: true,
157
+ tags: channelConfig.tags,
158
+ sessionService: userSession,
159
+ wire: {
160
+ channel: channelHandler.getChannel(),
161
+ ...(userSession && createMiddlewareSessionWireProps(userSession)),
162
+ },
163
+ packageName,
164
+ })
165
+ }
166
+
167
+ const onMessage = async (rawData: unknown): Promise<unknown> => {
168
+ let result: unknown
169
+ let processed = false
170
+
171
+ // Route-specific handling
172
+ if (typeof rawData === 'string' && channelConfig.onMessageWiring) {
173
+ let messageData: any
174
+ try {
175
+ messageData = JSON.parse(rawData)
176
+ } catch {
177
+ // Most likely a json error.. ignore
178
+ }
179
+
180
+ if (messageData) {
181
+ const entries = Object.entries(channelConfig.onMessageWiring)
182
+ for (const [routingProperty, routes] of entries) {
183
+ const routerValue = messageData[routingProperty]
184
+ if (routerValue && routes[routerValue]) {
185
+ const { [routingProperty]: _, ...data } = messageData
186
+
187
+ processed = true
188
+ result =
189
+ (await processMessage(
190
+ data as any,
191
+ routes[routerValue],
192
+ routingProperty,
193
+ routerValue
194
+ )) || {}
195
+ ;(result as any)[routingProperty] = routerValue
196
+ break
197
+ }
198
+ }
199
+
200
+ // Default handler if no routes matched but json data was parsed
201
+ if (!processed && channelConfig.onMessage) {
202
+ processed = true
203
+ result = await processMessage(messageData, channelConfig.onMessage)
204
+ }
205
+ }
206
+ }
207
+
208
+ // Default handler if no routes matched and json data wasn't parsed
209
+ if (!processed && channelConfig.onMessage) {
210
+ processed = true
211
+ result = await processMessage(
212
+ rawData as JSONValue,
213
+ channelConfig.onMessage
214
+ )
215
+ }
216
+
217
+ if (!processed) {
218
+ logger.error(
219
+ `No handler found for message in channel ${channelConfig.name} for ${JSON.stringify(rawData)}`
220
+ )
221
+ logger.error(`Channel config name: ${channelConfig.name}`)
222
+ }
223
+
224
+ return result
225
+ }
226
+
227
+ const onBinaryMessage = channelConfig.onBinaryMessage
228
+ ? async (data: BinaryData): Promise<BinaryData | void> => {
229
+ if (
230
+ !validateAuth(
231
+ requiresSession,
232
+ userSession?.get() as CoreUserSession | undefined,
233
+ channelConfig
234
+ )
235
+ ) {
236
+ logger.error(
237
+ `Channel ${channelConfig.name} with id ${channelHandler.getChannel().channelId} requires a session for binary message`
238
+ )
239
+ return
240
+ }
241
+ return channelConfig.onBinaryMessage!(
242
+ services,
243
+ data,
244
+ channelHandler.getChannel()
245
+ )
246
+ }
247
+ : undefined
248
+
249
+ return { onMessage, onBinaryMessage }
250
+ }
@@ -0,0 +1,126 @@
1
+ import type {
2
+ CoreSingletonServices,
3
+ MiddlewareMetadata,
4
+ PikkuWire,
5
+ } from '../../types/core.types.js'
6
+ import type { CorePikkuChannelMiddleware } from './channel.types.js'
7
+ import { pikkuState } from '../../pikku-state.js'
8
+ import { freezeDedupe, getTagGroups } from '../../utils.js'
9
+
10
+ export const addChannelMiddleware = (
11
+ tag: string,
12
+ middleware: CorePikkuChannelMiddleware[],
13
+ packageName: string | null = null
14
+ ): CorePikkuChannelMiddleware[] => {
15
+ const tagGroups = pikkuState(packageName, 'channelMiddleware', 'tagGroup')
16
+ tagGroups[tag] = middleware
17
+ return middleware
18
+ }
19
+
20
+ const getChannelMiddlewareByName = (
21
+ name: string
22
+ ): CorePikkuChannelMiddleware | undefined => {
23
+ const store = pikkuState(null, 'misc', 'channelMiddleware')
24
+ const middleware = store[name]
25
+ return middleware?.[0]
26
+ }
27
+
28
+ const channelMiddlewareCache: Record<
29
+ string,
30
+ readonly CorePikkuChannelMiddleware[]
31
+ > = {}
32
+
33
+ export const clearChannelMiddlewareCache = () => {
34
+ for (const key of Object.keys(channelMiddlewareCache)) {
35
+ delete channelMiddlewareCache[key]
36
+ }
37
+ }
38
+
39
+ export const combineChannelMiddleware = (
40
+ wireType: string,
41
+ uid: string,
42
+ {
43
+ wireInheritedChannelMiddleware,
44
+ wireChannelMiddleware,
45
+ packageName = null,
46
+ }: {
47
+ wireInheritedChannelMiddleware?: MiddlewareMetadata[]
48
+ wireChannelMiddleware?: CorePikkuChannelMiddleware[]
49
+ packageName?: string | null
50
+ } = {}
51
+ ): readonly CorePikkuChannelMiddleware[] => {
52
+ const cacheKey = `${wireType}:${uid}`
53
+ if (channelMiddlewareCache[cacheKey]) {
54
+ return channelMiddlewareCache[cacheKey]
55
+ }
56
+
57
+ const resolved: CorePikkuChannelMiddleware[] = []
58
+
59
+ if (wireInheritedChannelMiddleware) {
60
+ for (const meta of wireInheritedChannelMiddleware) {
61
+ if (meta.type === 'tag') {
62
+ const groups = getTagGroups(
63
+ pikkuState(packageName, 'channelMiddleware', 'tagGroup'),
64
+ meta.tag
65
+ )
66
+ for (const group of groups) {
67
+ resolved.push(...group)
68
+ }
69
+ } else if (meta.type === 'wire') {
70
+ const middleware = getChannelMiddlewareByName(meta.name)
71
+ if (middleware) {
72
+ resolved.push(middleware)
73
+ }
74
+ }
75
+ }
76
+ }
77
+
78
+ if (wireChannelMiddleware) {
79
+ resolved.push(...wireChannelMiddleware)
80
+ }
81
+
82
+ channelMiddlewareCache[cacheKey] = freezeDedupe(
83
+ resolved
84
+ ) as readonly CorePikkuChannelMiddleware[]
85
+
86
+ return channelMiddlewareCache[cacheKey]
87
+ }
88
+
89
+ export function wrapChannelWithMiddleware<Out>(
90
+ wire: PikkuWire,
91
+ services: CoreSingletonServices,
92
+ middlewares: readonly CorePikkuChannelMiddleware[]
93
+ ): PikkuWire {
94
+ if (middlewares.length === 0 || !wire.channel) return wire
95
+
96
+ const channel = wire.channel
97
+ const originalSend = channel.send.bind(channel)
98
+
99
+ const dispatch = async (index: number, event: Out): Promise<void> => {
100
+ if (index >= middlewares.length) {
101
+ await originalSend(event)
102
+ return
103
+ }
104
+
105
+ const middleware = middlewares[index]!
106
+ await middleware(services, event, async (result: any) => {
107
+ if (result === null || result === undefined) return
108
+ if (Array.isArray(result)) {
109
+ for (const item of result) {
110
+ await dispatch(index + 1, item)
111
+ }
112
+ } else {
113
+ await dispatch(index + 1, result)
114
+ }
115
+ })
116
+ }
117
+
118
+ return {
119
+ ...wire,
120
+ channel: {
121
+ ...channel,
122
+ send: ((data: Out) => dispatch(0, data)) as typeof channel.send,
123
+ sendBinary: channel.sendBinary,
124
+ },
125
+ }
126
+ }
@@ -0,0 +1,166 @@
1
+ import { NotFoundError, PikkuMissingMetaError } from '../../errors/errors.js'
2
+ import { addFunction } from '../../function/function-runner.js'
3
+ import type { CorePikkuPermission } from '../../function/functions.types.js'
4
+ import { pikkuState, getSingletonServices } from '../../pikku-state.js'
5
+ import { coerceTopLevelDataFromSchema, validateSchema } from '../../schema.js'
6
+ import type { SessionService } from '../../services/user-session-service.js'
7
+ import type { CorePikkuMiddleware } from '../../types/core.types.js'
8
+ import { httpRouter } from '../http/routers/http-router.js'
9
+ import type {
10
+ ChannelMeta,
11
+ CoreChannel,
12
+ RunChannelOptions,
13
+ RunChannelParams,
14
+ } from './channel.types.js'
15
+
16
+ /**
17
+ * Adds a channel and registers all functions referenced in it using the
18
+ * function names already stored in the channel metadata
19
+ */
20
+ export const wireChannel = <
21
+ In,
22
+ Channel extends string,
23
+ PikkuPermission extends CorePikkuPermission<In>,
24
+ PikkuMiddleware extends CorePikkuMiddleware,
25
+ ChannelFunction,
26
+ >(
27
+ channel: CoreChannel<
28
+ In,
29
+ Channel,
30
+ PikkuPermission,
31
+ PikkuMiddleware,
32
+ ChannelFunction
33
+ >
34
+ ) => {
35
+ // Get the channel metadata
36
+ const channelsMeta = pikkuState(null, 'channel', 'meta')
37
+ const channelMeta = channelsMeta[channel.name]
38
+ if (!channelMeta) {
39
+ throw new PikkuMissingMetaError(
40
+ `Missing generated metadata for channel '${channel.name}'`
41
+ )
42
+ }
43
+
44
+ pikkuState(null, 'channel', 'channels').set(channel.name, channel as any)
45
+
46
+ // Register onConnect function if provided
47
+ if (channel.onConnect && channelMeta.connect) {
48
+ addFunction(channelMeta.connect.pikkuFuncId, channel.onConnect as any)
49
+ }
50
+
51
+ // Register onDisconnect function if provided
52
+ if (channel.onDisconnect && channelMeta.disconnect) {
53
+ addFunction(channelMeta.disconnect.pikkuFuncId, channel.onDisconnect as any)
54
+ }
55
+
56
+ // Register onMessage function if provided
57
+ if (channel.onMessage && channelMeta.message?.pikkuFuncId) {
58
+ addFunction(
59
+ channelMeta.message.pikkuFuncId,
60
+ (channel.onMessage as any).func instanceof Function
61
+ ? channel.onMessage
62
+ : (channel.onMessage as any).func
63
+ )
64
+ }
65
+
66
+ // Register functions in onMessageWiring
67
+ if (channel.onMessageWiring && channelMeta.messageWirings) {
68
+ // Iterate through each channel in onMessageWiring
69
+ Object.entries(channel.onMessageWiring).forEach(([channelKey, wirings]) => {
70
+ const channelWirings = channelMeta.messageWirings[channelKey]
71
+ if (!channelWirings) return
72
+
73
+ // Iterate through each wiring in the channel
74
+ Object.entries(wirings).forEach(([wiringKey, handler]) => {
75
+ const wiringMeta = channelWirings[wiringKey]
76
+ if (!wiringMeta) return
77
+
78
+ // Register the function using the pikku name from metadata
79
+ // It could be a FuncConfig or a wiring override with a funcConfig
80
+ addFunction(
81
+ wiringMeta.pikkuFuncId,
82
+ (handler as any).func instanceof Function
83
+ ? handler
84
+ : (handler as any).func
85
+ )
86
+ })
87
+ })
88
+ }
89
+
90
+ // Store the channel configuration
91
+ pikkuState(null, 'channel', 'channels').set(channel.name, channel as any)
92
+ }
93
+
94
+ const getMatchingChannelConfig = (path: string) => {
95
+ const matchedPath = httpRouter.match('get', path)
96
+ if (!matchedPath) {
97
+ return null
98
+ }
99
+
100
+ const meta = pikkuState(null, 'channel', 'meta')
101
+ const channelMeta = Object.values(meta).find(
102
+ (channelConfig) => channelConfig.route === matchedPath.route
103
+ )
104
+ if (!channelMeta) {
105
+ return null
106
+ }
107
+
108
+ const channels = pikkuState(null, 'channel', 'channels')
109
+ const channelConfig = channels.get(channelMeta.name)
110
+ if (!channelConfig) {
111
+ return null
112
+ }
113
+
114
+ return {
115
+ matchedPath,
116
+ params: matchedPath.params,
117
+ channelConfig,
118
+ schemaName: channelMeta.input,
119
+ meta: channelMeta,
120
+ }
121
+ }
122
+
123
+ export const openChannel = async ({
124
+ route,
125
+ coerceDataFromSchema = true,
126
+ request,
127
+ }: Pick<CoreChannel<unknown, string>, 'route'> &
128
+ RunChannelParams<unknown> & {
129
+ userSession: SessionService<any>
130
+ } & RunChannelOptions): Promise<{
131
+ openingData: unknown
132
+ channelConfig: CoreChannel<unknown, any>
133
+ meta: ChannelMeta
134
+ }> => {
135
+ const singletonServices = getSingletonServices()
136
+ const matchingChannel = getMatchingChannelConfig(route)
137
+ if (!matchingChannel) {
138
+ singletonServices.logger.info(`Channel not found: ${route}`)
139
+ throw new NotFoundError(`Channel not found: ${route}`)
140
+ }
141
+
142
+ const { params, channelConfig, schemaName, meta } = matchingChannel
143
+
144
+ const requiresSession = channelConfig.auth !== false
145
+ request?.setParams(params)
146
+
147
+ singletonServices.logger.info(
148
+ `Matched channel: ${channelConfig.name} | route: ${channelConfig.route} | auth: ${requiresSession.toString()}`
149
+ )
150
+
151
+ let openingData: any | undefined
152
+ if (request) {
153
+ openingData = await request.data()
154
+ if (coerceDataFromSchema && schemaName) {
155
+ coerceTopLevelDataFromSchema(schemaName, openingData)
156
+ }
157
+ await validateSchema(
158
+ singletonServices.logger,
159
+ singletonServices.schema,
160
+ schemaName,
161
+ openingData
162
+ )
163
+ }
164
+
165
+ return { openingData, channelConfig, meta }
166
+ }
@@ -0,0 +1,29 @@
1
+ import type { CoreUserSession } from '../../types/core.types.js'
2
+
3
+ export type Channel<ChannelType = unknown, OpeningData = unknown> = {
4
+ channelId: string
5
+ channelName: string
6
+ channelObject?: ChannelType
7
+ openingData?: OpeningData
8
+ }
9
+
10
+ export abstract class ChannelStore<
11
+ ChannelType = unknown,
12
+ OpeningData = unknown,
13
+ UserSession extends CoreUserSession = CoreUserSession,
14
+ TypedChannel = Channel<ChannelType, OpeningData>,
15
+ > {
16
+ public abstract addChannel(
17
+ channel: Channel<ChannelType, OpeningData>
18
+ ): Promise<void> | void
19
+ public abstract removeChannels(channelId: string[]): Promise<void> | void
20
+ public abstract setUserSession(
21
+ channelId: string,
22
+ userSession: UserSession | null
23
+ ): Promise<void> | void
24
+ public abstract getChannelAndSession(
25
+ channelId: string
26
+ ):
27
+ | Promise<TypedChannel & { session: UserSession }>
28
+ | (TypedChannel & { session: UserSession })
29
+ }