@pikku/core 0.9.4 → 0.9.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 (71) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/function/function-runner.d.ts +2 -2
  3. package/dist/function/function-runner.js +22 -25
  4. package/dist/function/functions.types.d.ts +4 -0
  5. package/dist/function/functions.types.js +6 -1
  6. package/dist/index.d.ts +3 -3
  7. package/dist/index.js +3 -3
  8. package/dist/middleware-runner.d.ts +22 -13
  9. package/dist/middleware-runner.js +44 -16
  10. package/dist/permissions.d.ts +20 -4
  11. package/dist/permissions.js +84 -2
  12. package/dist/pikku-state.d.ts +4 -6
  13. package/dist/pikku-state.js +6 -5
  14. package/dist/schema.js +8 -6
  15. package/dist/types/core.types.d.ts +7 -4
  16. package/dist/types/core.types.js +6 -0
  17. package/dist/utils.d.ts +1 -0
  18. package/dist/utils.js +17 -0
  19. package/dist/wirings/channel/channel-handler.js +2 -1
  20. package/dist/wirings/channel/channel-runner.d.ts +2 -0
  21. package/dist/wirings/channel/channel-runner.js +23 -25
  22. package/dist/wirings/channel/local/local-channel-runner.js +9 -4
  23. package/dist/wirings/channel/serverless/serverless-channel-runner.js +8 -3
  24. package/dist/wirings/http/http-runner.js +44 -54
  25. package/dist/wirings/http/http.types.d.ts +5 -5
  26. package/dist/wirings/http/pikku-fetch-http-request.js +3 -0
  27. package/dist/wirings/http/routers/http-router.d.ts +12 -0
  28. package/dist/wirings/http/routers/http-router.js +2 -0
  29. package/dist/wirings/http/routers/path-to-regex.d.ts +10 -0
  30. package/dist/wirings/http/routers/path-to-regex.js +118 -0
  31. package/dist/wirings/mcp/mcp-runner.d.ts +1 -1
  32. package/dist/wirings/mcp/mcp-runner.js +10 -4
  33. package/dist/wirings/queue/queue-runner.js +11 -3
  34. package/dist/wirings/rpc/rpc-runner.d.ts +1 -2
  35. package/dist/wirings/rpc/rpc-runner.js +2 -1
  36. package/dist/wirings/scheduler/scheduler-runner.d.ts +1 -1
  37. package/dist/wirings/scheduler/scheduler-runner.js +10 -4
  38. package/package.json +1 -1
  39. package/src/factory-functions.test.ts +37 -0
  40. package/src/function/function-runner.test.ts +499 -0
  41. package/src/function/function-runner.ts +26 -43
  42. package/src/function/functions.types.ts +13 -0
  43. package/src/index.ts +3 -7
  44. package/src/middleware-runner.test.ts +357 -0
  45. package/src/middleware-runner.ts +68 -21
  46. package/src/permissions.test.ts +407 -42
  47. package/src/permissions.ts +159 -7
  48. package/src/pikku-state.ts +14 -8
  49. package/src/schema.ts +8 -6
  50. package/src/types/core.types.ts +15 -4
  51. package/src/utils.ts +19 -0
  52. package/src/wirings/channel/channel-handler.ts +17 -11
  53. package/src/wirings/channel/channel-runner.ts +31 -25
  54. package/src/wirings/channel/local/local-channel-runner.test.ts +4 -0
  55. package/src/wirings/channel/local/local-channel-runner.ts +9 -8
  56. package/src/wirings/channel/serverless/serverless-channel-runner.ts +17 -15
  57. package/src/wirings/http/http-runner.test.ts +18 -6
  58. package/src/wirings/http/http-runner.ts +64 -75
  59. package/src/wirings/http/http.types.ts +5 -5
  60. package/src/wirings/http/pikku-fetch-http-request.ts +3 -0
  61. package/src/wirings/http/routers/http-router.ts +15 -0
  62. package/src/wirings/http/routers/path-to-regex.test.ts +309 -0
  63. package/src/wirings/http/routers/path-to-regex.ts +162 -0
  64. package/src/wirings/mcp/mcp-runner.ts +25 -14
  65. package/src/wirings/queue/queue-runner.ts +26 -8
  66. package/src/wirings/rpc/rpc-runner.ts +21 -16
  67. package/src/wirings/scheduler/scheduler-runner.ts +27 -14
  68. package/tsconfig.tsbuildinfo +1 -1
  69. package/dist/pikku-function.d.ts +0 -1
  70. package/dist/pikku-function.js +0 -1
  71. package/src/pikku-function.ts +0 -1
@@ -83,8 +83,8 @@ export type CoreHTTPFunction = {
83
83
  /**
84
84
  * Represents a http interaction within Pikku, including a request and response.
85
85
  */
86
- export interface PikkuHTTP {
87
- request?: PikkuHTTPRequest
86
+ export interface PikkuHTTP<In = unknown> {
87
+ request?: PikkuHTTPRequest<In>
88
88
  response?: PikkuHTTPResponse
89
89
  }
90
90
 
@@ -208,7 +208,7 @@ export type HTTPWiringMeta = {
208
208
  tags?: string[]
209
209
  sse?: true
210
210
  }
211
- export type HTTPWiringsMeta = Array<HTTPWiringMeta>
211
+ export type HTTPWiringsMeta = Record<HTTPMethod, Record<string, HTTPWiringMeta>>
212
212
 
213
213
  export type HTTPFunctionsMeta = Array<{
214
214
  name: string
@@ -234,7 +234,7 @@ export interface PikkuHTTPRequest<In = unknown> {
234
234
  query(): PikkuQuery
235
235
  }
236
236
 
237
- export interface PikkuHTTPResponse {
237
+ export interface PikkuHTTPResponse<Out = unknown> {
238
238
  status(code: number): this
239
239
  cookie(name: string, value: string | null, options: SerializeOptions): this
240
240
  header(name: string, value: string | string[]): this
@@ -248,7 +248,7 @@ export interface PikkuHTTPResponse {
248
248
  | URLSearchParams
249
249
  | ReadableStream
250
250
  ): this
251
- json(data: unknown): this
251
+ json(data: Out): this
252
252
  redirect(location: string, status?: number): this
253
253
  close?: () => void
254
254
  setMode?: (mode: 'stream') => void
@@ -57,6 +57,9 @@ export class PikkuFetchHTTPRequest<In = unknown>
57
57
  * @returns An object containing the cookies.
58
58
  */
59
59
  public cookie(cookieName: string): string | null {
60
+ if (this.#cookies?.[cookieName]) {
61
+ return this.#cookies[cookieName]
62
+ }
60
63
  const cookieHeader = this.header('cookie')
61
64
  this.#cookies = cookieHeader ? parseCookie(cookieHeader) : {}
62
65
  return this.#cookies[cookieName] || null
@@ -0,0 +1,15 @@
1
+ import { CorePikkuMiddleware } from '../../../types/core.types.js'
2
+ import { HTTPMethod } from '../http.types.js'
3
+ import { PathToRegexRouter } from './path-to-regex.js'
4
+
5
+ export type MatchResult = {
6
+ route: string
7
+ params: Record<string, any>
8
+ middleware?: CorePikkuMiddleware[]
9
+ } | null
10
+
11
+ export interface Router {
12
+ match(method: HTTPMethod, path: string): MatchResult
13
+ }
14
+
15
+ export const httpRouter = new PathToRegexRouter()
@@ -0,0 +1,309 @@
1
+ import { test, describe, beforeEach } from 'node:test'
2
+ import * as assert from 'node:assert/strict'
3
+ import { PathToRegexRouter } from './path-to-regex.js'
4
+ import { resetPikkuState, pikkuState } from '../../../pikku-state.js'
5
+ import { HTTPMethod } from '../http.types.js'
6
+
7
+ describe('PathToRegexRouter', () => {
8
+ let router: PathToRegexRouter
9
+
10
+ // Helper to create mock route wiring
11
+ const mockRoute = (route: string) => ({
12
+ route,
13
+ method: 'get' as HTTPMethod,
14
+ func: (() => {}) as any,
15
+ })
16
+
17
+ beforeEach(() => {
18
+ resetPikkuState()
19
+ router = new PathToRegexRouter()
20
+ })
21
+
22
+ describe('Static Route Optimization', () => {
23
+ test('should correctly identify static routes', () => {
24
+ // Setup test routes
25
+ pikkuState(
26
+ 'http',
27
+ 'routes',
28
+ new Map([
29
+ [
30
+ 'get',
31
+ new Map([
32
+ ['/static', mockRoute('/static')],
33
+ ['/api/about', mockRoute('/api/about')],
34
+ ['/health', mockRoute('/health')],
35
+ ]),
36
+ ],
37
+ ])
38
+ )
39
+ pikkuState('http', 'middleware', new Map())
40
+ pikkuState('channel', 'channels', new Map())
41
+
42
+ router.initialize()
43
+
44
+ // Test static routes - should return exact matches with empty params
45
+ const staticResult = router.match('get', '/static')
46
+ assert.ok(staticResult, 'Static route should match')
47
+ assert.strictEqual(staticResult.route, '/static')
48
+ assert.deepStrictEqual(staticResult.params, {})
49
+ assert.strictEqual(staticResult.middleware?.length, 0)
50
+
51
+ const aboutResult = router.match('get', '/api/about')
52
+ assert.ok(aboutResult, 'Static route /api/about should match')
53
+ assert.strictEqual(aboutResult.route, '/api/about')
54
+ assert.deepStrictEqual(aboutResult.params, {})
55
+ })
56
+
57
+ test('should handle dynamic routes with parameters', () => {
58
+ // Setup test routes with parameters
59
+ pikkuState(
60
+ 'http',
61
+ 'routes',
62
+ new Map([
63
+ [
64
+ 'get',
65
+ new Map([
66
+ ['/api/users/:id', mockRoute('/api/users/:id')],
67
+ [
68
+ '/posts/:slug/comments/:commentId',
69
+ mockRoute('/posts/:slug/comments/:commentId'),
70
+ ],
71
+ ]),
72
+ ],
73
+ ])
74
+ )
75
+ pikkuState('http', 'middleware', new Map())
76
+ pikkuState('channel', 'channels', new Map())
77
+
78
+ router.initialize()
79
+
80
+ // Test dynamic routes - should return matches with extracted parameters
81
+ const userResult = router.match('get', '/api/users/123')
82
+ assert.ok(userResult, 'Dynamic route should match')
83
+ assert.strictEqual(userResult.route, '/api/users/:id')
84
+ assert.strictEqual(userResult.params.id, '123')
85
+
86
+ const commentResult = router.match(
87
+ 'get',
88
+ '/posts/hello-world/comments/456'
89
+ )
90
+ assert.ok(commentResult, 'Complex dynamic route should match')
91
+ assert.strictEqual(
92
+ commentResult.route,
93
+ '/posts/:slug/comments/:commentId'
94
+ )
95
+ assert.strictEqual(commentResult.params.slug, 'hello-world')
96
+ assert.strictEqual(commentResult.params.commentId, '456')
97
+ })
98
+
99
+ test('should handle multi-parameter routes', () => {
100
+ // Setup routes with multiple parameters
101
+ pikkuState(
102
+ 'http',
103
+ 'routes',
104
+ new Map([
105
+ [
106
+ 'get',
107
+ new Map([
108
+ [
109
+ '/api/:version/users/:userId',
110
+ mockRoute('/api/:version/users/:userId'),
111
+ ],
112
+ [
113
+ '/blog/:year/:month/:slug',
114
+ mockRoute('/blog/:year/:month/:slug'),
115
+ ],
116
+ ]),
117
+ ],
118
+ ])
119
+ )
120
+ pikkuState('http', 'middleware', new Map())
121
+ pikkuState('channel', 'channels', new Map())
122
+
123
+ router.initialize()
124
+
125
+ // Test multi-parameter routes
126
+ const apiResult = router.match('get', '/api/v2/users/456')
127
+ assert.ok(apiResult, 'Multi-parameter route should match')
128
+ assert.strictEqual(apiResult.route, '/api/:version/users/:userId')
129
+ assert.strictEqual(apiResult.params.version, 'v2')
130
+ assert.strictEqual(apiResult.params.userId, '456')
131
+
132
+ const blogResult = router.match('get', '/blog/2024/03/hello-world')
133
+ assert.ok(blogResult, 'Deep multi-parameter route should match')
134
+ assert.strictEqual(blogResult.route, '/blog/:year/:month/:slug')
135
+ assert.strictEqual(blogResult.params.year, '2024')
136
+ assert.strictEqual(blogResult.params.month, '03')
137
+ assert.strictEqual(blogResult.params.slug, 'hello-world')
138
+ })
139
+
140
+ test('should prioritize static routes over dynamic routes for performance', () => {
141
+ // Setup mixed routes where static could conflict with dynamic
142
+ pikkuState(
143
+ 'http',
144
+ 'routes',
145
+ new Map([
146
+ [
147
+ 'get',
148
+ new Map([
149
+ ['/api/users', mockRoute('/api/users')], // static
150
+ ['/api/:resource', mockRoute('/api/:resource')], // dynamic that could match above
151
+ ]),
152
+ ],
153
+ ])
154
+ )
155
+ pikkuState('http', 'middleware', new Map())
156
+ pikkuState('channel', 'channels', new Map())
157
+
158
+ router.initialize()
159
+
160
+ // Static route should be found first (O(1) lookup)
161
+ const result = router.match('get', '/api/users')
162
+ assert.ok(result, 'Route should match')
163
+ assert.strictEqual(result.route, '/api/users') // Should match static, not dynamic
164
+ assert.deepStrictEqual(result.params, {})
165
+ })
166
+ })
167
+
168
+ describe('Path Normalization', () => {
169
+ test('should normalize paths without leading slash', () => {
170
+ pikkuState(
171
+ 'http',
172
+ 'routes',
173
+ new Map([
174
+ [
175
+ 'get',
176
+ new Map([
177
+ ['test', mockRoute('test')], // route without leading slash
178
+ ['api/health', mockRoute('api/health')],
179
+ ]),
180
+ ],
181
+ ])
182
+ )
183
+ pikkuState('http', 'middleware', new Map())
184
+ pikkuState('channel', 'channels', new Map())
185
+
186
+ router.initialize()
187
+
188
+ // Should match with or without leading slash in request
189
+ const result1 = router.match('get', '/test')
190
+ assert.ok(result1, 'Should match /test')
191
+ assert.strictEqual(result1.route, 'test')
192
+
193
+ const result2 = router.match('get', 'test')
194
+ assert.ok(result2, 'Should match test')
195
+ assert.strictEqual(result2.route, 'test')
196
+
197
+ const result3 = router.match('get', '/api/health')
198
+ assert.ok(result3, 'Should match /api/health')
199
+ assert.strictEqual(result3.route, 'api/health')
200
+ })
201
+ })
202
+
203
+ describe('Channel Routes Integration', () => {
204
+ test('should handle channel routes as GET routes', () => {
205
+ pikkuState('http', 'routes', new Map())
206
+ pikkuState('http', 'middleware', new Map())
207
+ pikkuState(
208
+ 'channel',
209
+ 'channels',
210
+ new Map([
211
+ ['websocket-test', { name: 'websocket-test', route: '/ws/test' }],
212
+ ['chat', { name: 'chat', route: '/chat/:room' }],
213
+ ])
214
+ )
215
+
216
+ router.initialize()
217
+
218
+ // Channel routes should be accessible via GET method
219
+ const wsResult = router.match('get', '/ws/test')
220
+ assert.ok(wsResult, 'Static channel route should match')
221
+ assert.strictEqual(wsResult.route, '/ws/test')
222
+ assert.deepStrictEqual(wsResult.params, {})
223
+
224
+ const chatResult = router.match('get', '/chat/general')
225
+ assert.ok(chatResult, 'Dynamic channel route should match')
226
+ assert.strictEqual(chatResult.route, '/chat/:room')
227
+ assert.strictEqual(chatResult.params.room, 'general')
228
+ })
229
+ })
230
+
231
+ describe('Middleware Integration', () => {
232
+ test('should include global and route-specific middleware', () => {
233
+ const globalMiddleware = [async () => {}]
234
+ const routeMiddleware = [async () => {}]
235
+
236
+ pikkuState(
237
+ 'http',
238
+ 'routes',
239
+ new Map([['get', new Map([['/api/test', mockRoute('/api/test')]])]])
240
+ )
241
+ pikkuState(
242
+ 'http',
243
+ 'middleware',
244
+ new Map([
245
+ ['*', globalMiddleware],
246
+ ['/api/test', routeMiddleware],
247
+ ])
248
+ )
249
+ pikkuState('channel', 'channels', new Map())
250
+
251
+ router.initialize()
252
+
253
+ const result = router.match('get', '/api/test')
254
+ assert.ok(result, 'Route should match')
255
+ assert.strictEqual(result.middleware?.length, 2) // global + route-specific
256
+ assert.strictEqual(result.middleware?.[0], globalMiddleware[0])
257
+ assert.strictEqual(result.middleware?.[1], routeMiddleware[0])
258
+ })
259
+ })
260
+
261
+ describe('No Match Scenarios', () => {
262
+ test('should return null for non-existent routes', () => {
263
+ pikkuState(
264
+ 'http',
265
+ 'routes',
266
+ new Map([['get', new Map([['/existing', mockRoute('/existing')]])]])
267
+ )
268
+ pikkuState('http', 'middleware', new Map())
269
+ pikkuState('channel', 'channels', new Map())
270
+
271
+ router.initialize()
272
+
273
+ const result = router.match('get', '/non-existent')
274
+ assert.strictEqual(result, null)
275
+ })
276
+
277
+ test('should return null for unsupported HTTP methods', () => {
278
+ pikkuState(
279
+ 'http',
280
+ 'routes',
281
+ new Map([['get', new Map([['/test', mockRoute('/test')]])]])
282
+ )
283
+ pikkuState('http', 'middleware', new Map())
284
+ pikkuState('channel', 'channels', new Map())
285
+
286
+ router.initialize()
287
+
288
+ const result = router.match('post', '/test')
289
+ assert.strictEqual(result, null)
290
+ })
291
+ })
292
+
293
+ describe('Lazy Initialization', () => {
294
+ test('should initialize automatically on first match call', () => {
295
+ pikkuState(
296
+ 'http',
297
+ 'routes',
298
+ new Map([['get', new Map([['/test', mockRoute('/test')]])]])
299
+ )
300
+ pikkuState('http', 'middleware', new Map())
301
+ pikkuState('channel', 'channels', new Map())
302
+
303
+ // Don't call initialize manually
304
+ const result = router.match('get', '/test')
305
+ assert.ok(result, 'Should auto-initialize and match')
306
+ assert.strictEqual(result.route, '/test')
307
+ })
308
+ })
309
+ })
@@ -0,0 +1,162 @@
1
+ import { match, MatchFunction } from 'path-to-regexp'
2
+ import { MatchResult, Router } from './http-router.js'
3
+ import { HTTPMethod } from '../http.types.js'
4
+ import { pikkuState } from '../../../pikku-state.js'
5
+ import { CorePikkuMiddleware } from '../../../types/core.types.js'
6
+
7
+ interface CompiledRoute {
8
+ matcher: MatchFunction<Partial<Record<string, string | string[]>>>
9
+ route: string
10
+ middleware: CorePikkuMiddleware[]
11
+ }
12
+
13
+ interface StaticRoute {
14
+ route: string
15
+ middleware: CorePikkuMiddleware[]
16
+ }
17
+
18
+ export class PathToRegexRouter implements Router {
19
+ private compiledRoutes: Map<HTTPMethod, Map<string, CompiledRoute>> =
20
+ new Map()
21
+ private staticRoutes: Map<HTTPMethod, Map<string, StaticRoute>> = new Map()
22
+ private precompiledMiddleware: Map<string, CorePikkuMiddleware[]> = new Map()
23
+ private isInitialized = false
24
+
25
+ public initialize() {
26
+ const routes = pikkuState('http', 'routes')
27
+ const channelRoutes = pikkuState('channel', 'channels')
28
+ const middlewareMap = pikkuState('http', 'middleware')
29
+
30
+ // Precompile middleware lookups
31
+ for (const [middlewareRoute, middlewareArray] of middlewareMap.entries()) {
32
+ this.precompiledMiddleware.set(middlewareRoute, middlewareArray)
33
+ }
34
+
35
+ // Helper function to compile routes for a given method
36
+ const compileRoutesForMethod = (
37
+ method: HTTPMethod,
38
+ routeEntries: Iterable<[string, any]>
39
+ ) => {
40
+ const methodCompiledRoutes =
41
+ this.compiledRoutes.get(method) || new Map<string, CompiledRoute>()
42
+ const methodStaticRoutes =
43
+ this.staticRoutes.get(method) || new Map<string, StaticRoute>()
44
+
45
+ for (const [routePath] of routeEntries) {
46
+ // Normalize route path - ensure it starts with /
47
+ const normalizedRoutePath = routePath.startsWith('/')
48
+ ? routePath
49
+ : `/${routePath}`
50
+
51
+ // Check if route is static (no parameters or wildcards)
52
+ const isStaticRoute = !/\*|:/.test(normalizedRoutePath)
53
+
54
+ // Precompute middleware for this route
55
+ const routeMiddleware: CorePikkuMiddleware[] = []
56
+
57
+ // Add global middleware (*)
58
+ const globalMiddleware = this.precompiledMiddleware.get('*')
59
+ if (globalMiddleware) {
60
+ routeMiddleware.push(...globalMiddleware)
61
+ }
62
+
63
+ // Add route-specific middleware
64
+ for (const [
65
+ middlewareRoute,
66
+ middlewareArray,
67
+ ] of this.precompiledMiddleware.entries()) {
68
+ if (middlewareRoute !== '*') {
69
+ // Use regex test for pattern matching
70
+ try {
71
+ if (new RegExp(middlewareRoute).test(normalizedRoutePath)) {
72
+ routeMiddleware.push(...middlewareArray)
73
+ }
74
+ } catch {
75
+ // If regex is invalid, do exact match
76
+ if (middlewareRoute === normalizedRoutePath) {
77
+ routeMiddleware.push(...middlewareArray)
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ if (isStaticRoute) {
84
+ // Store static routes for O(1) lookup
85
+ methodStaticRoutes.set(normalizedRoutePath, {
86
+ route: routePath, // Keep the original route path for lookup in pikkuState
87
+ middleware: routeMiddleware,
88
+ })
89
+ } else {
90
+ // Compile dynamic routes with path-to-regexp
91
+ const matcher = match(normalizedRoutePath, {
92
+ decode: decodeURIComponent,
93
+ })
94
+
95
+ methodCompiledRoutes.set(normalizedRoutePath, {
96
+ matcher,
97
+ route: routePath, // Keep the original route path for lookup in pikkuState
98
+ middleware: routeMiddleware,
99
+ })
100
+ }
101
+ }
102
+
103
+ this.compiledRoutes.set(method, methodCompiledRoutes)
104
+ this.staticRoutes.set(method, methodStaticRoutes)
105
+ }
106
+
107
+ // Precompile all HTTP route matchers
108
+ for (const [method, routeMap] of routes.entries()) {
109
+ compileRoutesForMethod(method, routeMap.entries())
110
+ }
111
+
112
+ // Precompile all channel route matchers (treating them as GET routes for WebSocket upgrades)
113
+ const channelRoutesArray: Array<[string, any]> = Array.from(
114
+ channelRoutes.entries()
115
+ ).map(([, channel]) => [channel.route, channel])
116
+ compileRoutesForMethod('get', channelRoutesArray)
117
+
118
+ this.isInitialized = true
119
+ }
120
+
121
+ match(method: HTTPMethod, path: string): MatchResult {
122
+ if (!this.isInitialized) {
123
+ this.initialize()
124
+ }
125
+
126
+ // Normalize path - ensure it starts with /
127
+ const normalizedPath = path.startsWith('/') ? path : `/${path}`
128
+
129
+ // First, try static routes for O(1) lookup
130
+ const methodStaticRoutes = this.staticRoutes.get(method)
131
+ if (methodStaticRoutes) {
132
+ const staticRoute = methodStaticRoutes.get(normalizedPath)
133
+ if (staticRoute) {
134
+ return {
135
+ route: staticRoute.route,
136
+ params: {},
137
+ middleware: staticRoute.middleware,
138
+ }
139
+ }
140
+ }
141
+
142
+ // If no static route matched, try dynamic routes
143
+ const methodRoutes = this.compiledRoutes.get(method)
144
+ if (!methodRoutes) {
145
+ return null
146
+ }
147
+
148
+ // Try each compiled route for this method
149
+ for (const [, compiledRoute] of methodRoutes.entries()) {
150
+ const result = compiledRoute.matcher(normalizedPath)
151
+ if (result) {
152
+ return {
153
+ route: compiledRoute.route,
154
+ params: result.params,
155
+ middleware: compiledRoute.middleware,
156
+ }
157
+ }
158
+ }
159
+
160
+ return null
161
+ }
162
+ }
@@ -1,8 +1,9 @@
1
- import type {
2
- CoreServices,
3
- CoreSingletonServices,
4
- CoreUserSession,
5
- CreateSessionServices,
1
+ import {
2
+ PikkuWiringTypes,
3
+ type CoreServices,
4
+ type CoreSingletonServices,
5
+ type CoreUserSession,
6
+ type CreateSessionServices,
6
7
  } from '../../types/core.types.js'
7
8
  import type {
8
9
  CoreMCPResource,
@@ -20,7 +21,7 @@ import { pikkuState } from '../../pikku-state.js'
20
21
  import { addFunction, runPikkuFunc } from '../../function/function-runner.js'
21
22
  import { rpcService } from '../rpc/rpc-runner.js'
22
23
  import { BadRequestError, NotFoundError } from '../../errors/errors.js'
23
- import { addMiddlewareForTags, runMiddleware } from '../../middleware-runner.js'
24
+ import { combineMiddleware, runMiddleware } from '../../middleware-runner.js'
24
25
 
25
26
  export class MCPError extends Error {
26
27
  constructor(public readonly error: JsonRpcErrorResponse) {
@@ -253,19 +254,29 @@ async function runMCPPikkuFunc(
253
254
  })
254
255
  }
255
256
 
256
- result = await runPikkuFunc(pikkuFuncName, {
257
- getAllServices,
258
- session,
259
- data: request.params,
260
- tags: mcp.tags,
261
- })
257
+ result = await runPikkuFunc(
258
+ PikkuWiringTypes.mcp,
259
+ `${type}:${name}`,
260
+ pikkuFuncName,
261
+ {
262
+ getAllServices,
263
+ session,
264
+ data: request.params,
265
+ tags: mcp.tags,
266
+ }
267
+ )
262
268
  }
263
269
 
264
- // Get middleware for tags and run middleware
270
+ const funcConfig = pikkuState('function', 'functions').get(pikkuFuncName!)
265
271
  await runMiddleware(
266
272
  singletonServices,
267
273
  { mcp: interaction },
268
- addMiddlewareForTags(mcp.middleware, mcp.tags),
274
+ combineMiddleware(PikkuWiringTypes.mcp, `${type}:${name}`, {
275
+ wiringMiddleware: mcp.middleware,
276
+ wiringTags: mcp.tags,
277
+ funcMiddleware: funcConfig?.middleware,
278
+ funcTags: funcConfig?.tags,
279
+ }),
269
280
  runMain
270
281
  )
271
282
 
@@ -4,8 +4,11 @@ import type { CorePikkuFunctionSessionless } from '../../function/functions.type
4
4
  import { getErrorResponse, PikkuError } from '../../errors/error-handler.js'
5
5
  import { pikkuState } from '../../pikku-state.js'
6
6
  import { addFunction, runPikkuFunc } from '../../function/function-runner.js'
7
- import { CreateSessionServices } from '../../types/core.types.js'
8
- import { addMiddlewareForTags, runMiddleware } from '../../middleware-runner.js'
7
+ import {
8
+ CreateSessionServices,
9
+ PikkuWiringTypes,
10
+ } from '../../types/core.types.js'
11
+ import { combineMiddleware, runMiddleware } from '../../middleware-runner.js'
9
12
 
10
13
  /**
11
14
  * Error class for queue processor not found
@@ -149,22 +152,37 @@ export async function runQueueJob({
149
152
  })
150
153
 
151
154
  // Execute the pikku function with the job data
152
- result = await runPikkuFunc(processorMeta.pikkuFuncName, {
153
- getAllServices,
154
- data: job.data,
155
- tags: queueWorker.tags,
156
- })
155
+ result = await runPikkuFunc(
156
+ PikkuWiringTypes.queue,
157
+ job.queueName,
158
+ processorMeta.pikkuFuncName,
159
+ {
160
+ getAllServices,
161
+ data: job.data,
162
+ tags: queueWorker.tags,
163
+ }
164
+ )
157
165
 
158
166
  logger.debug(
159
167
  `Successfully processed job ${job.id} in queue ${job.queueName}`
160
168
  )
161
169
  }
162
170
 
171
+ // Get function config for middleware and tags
172
+ const funcConfig = pikkuState('function', 'functions').get(
173
+ processorMeta.pikkuFuncName
174
+ )
175
+
163
176
  // Get middleware for tags and combine with queue-specific middleware
164
177
  await runMiddleware(
165
178
  singletonServices,
166
179
  { queue },
167
- addMiddlewareForTags(queueWorker.middleware, queueWorker.tags),
180
+ combineMiddleware(PikkuWiringTypes.queue, `${job.queueName}:${job.id}`, {
181
+ wiringMiddleware: queueWorker.middleware,
182
+ wiringTags: queueWorker.tags,
183
+ funcMiddleware: funcConfig?.middleware,
184
+ funcTags: funcConfig?.tags,
185
+ }),
168
186
  runMain
169
187
  )
170
188