@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
@@ -10,7 +10,11 @@ import {
10
10
  ScheduledTasksMeta,
11
11
  } from './wirings/scheduler/scheduler.types.js'
12
12
  import { ErrorDetails, PikkuError } from './errors/error-handler.js'
13
- import { CorePikkuFunctionConfig } from './function/functions.types.js'
13
+ import {
14
+ CorePermissionGroup,
15
+ CorePikkuFunctionConfig,
16
+ CorePikkuPermission,
17
+ } from './function/functions.types.js'
14
18
  import {
15
19
  queueWorkersMeta,
16
20
  CoreQueueWorker,
@@ -40,7 +44,8 @@ interface PikkuState {
40
44
  >
41
45
  }
42
46
  http: {
43
- middleware: Array<{ route: string; middleware: CorePikkuMiddleware[] }>
47
+ middleware: Map<string, CorePikkuMiddleware<any, any>[]>
48
+ permissions: Map<string, CorePermissionGroup | CorePikkuPermission[]>
44
49
  routes: Map<HTTPMethod, Map<string, CoreHTTPFunctionWiring<any, any, any>>>
45
50
  meta: HTTPWiringsMeta
46
51
  }
@@ -68,7 +73,7 @@ interface PikkuState {
68
73
  errors: Map<PikkuError, ErrorDetails>
69
74
  schemas: Map<string, any>
70
75
  middleware: Record<string, CorePikkuMiddleware[]>
71
- permissions: Record<string, any[]>
76
+ permissions: Record<string, CorePermissionGroup | CorePikkuPermission[]>
72
77
  }
73
78
  }
74
79
 
@@ -83,9 +88,10 @@ export const resetPikkuState = () => {
83
88
  files: new Map(),
84
89
  },
85
90
  http: {
86
- middleware: [],
91
+ permissions: new Map(),
92
+ middleware: new Map(),
87
93
  routes: new Map(),
88
- meta: [],
94
+ meta: {},
89
95
  },
90
96
  channel: {
91
97
  channels: new Map(),
@@ -109,9 +115,9 @@ export const resetPikkuState = () => {
109
115
  },
110
116
  misc: {
111
117
  errors: globalThis.pikkuState?.misc?.errors || new Map(),
112
- schemas: globalThis.pikkuState?.misc?.schema || new Map(),
113
- middleware: globalThis.pikkuState?.misc?.middleware || {},
114
- permissions: globalThis.pikkuState?.misc?.permissions || {},
118
+ schemas: new Map(),
119
+ middleware: {},
120
+ permissions: {},
115
121
  },
116
122
  } as PikkuState
117
123
  }
package/src/schema.ts CHANGED
@@ -40,13 +40,15 @@ const validateAllSchemasLoaded = (
40
40
 
41
41
  const missingSchemas: string[] = []
42
42
 
43
- for (const route of routesMeta) {
44
- const inputs = pikkuState('function', 'meta')[route.pikkuFuncName]?.inputs
45
- const input = inputs?.[0]
46
- if (!input || validators.has(input)) {
47
- continue
43
+ for (const routePaths of Object.values(routesMeta)) {
44
+ for (const meta of Object.values(routePaths)) {
45
+ const inputs = pikkuState('function', 'meta')[meta.pikkuFuncName]?.inputs
46
+ const input = inputs?.[0]
47
+ if (!input || validators.has(input)) {
48
+ continue
49
+ }
50
+ missingSchemas.push(input)
48
51
  }
49
- missingSchemas.push(input)
50
52
  }
51
53
 
52
54
  if (missingSchemas.length > 0) {
@@ -114,14 +114,13 @@ export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
114
114
  /**
115
115
  * Represents different forms of interaction within Pikku and the outside world.
116
116
  */
117
- export type PikkuInteraction = Partial<{
118
- http: PikkuHTTP
117
+ export type PikkuInteraction<In = unknown, Out = unknown> = Partial<{
118
+ http: PikkuHTTP<In>
119
119
  mcp: PikkuMCP
120
120
  rpc: PikkuRPC
121
- channel: PikkuChannel<unknown, unknown>
121
+ channel: PikkuChannel<unknown, Out>
122
122
  scheduledTask: PikkuScheduledTask
123
123
  queue: PikkuQueue
124
- s
125
124
  }>
126
125
 
127
126
  /**
@@ -138,6 +137,18 @@ export type CorePikkuMiddleware<
138
137
  next: () => Promise<void>
139
138
  ) => Promise<void>
140
139
 
140
+ /**
141
+ * Factory function for creating middleware with tree-shaking support
142
+ */
143
+ export const pikkuMiddleware = <
144
+ SingletonServices extends CoreSingletonServices = CoreSingletonServices,
145
+ UserSession extends CoreUserSession = CoreUserSession,
146
+ >(
147
+ middleware: CorePikkuMiddleware<SingletonServices, UserSession>
148
+ ): CorePikkuMiddleware<SingletonServices, UserSession> => {
149
+ return middleware
150
+ }
151
+
141
152
  /**
142
153
  * Represents the core services used by Pikku, including singleton services and the request/response interaction.
143
154
  */
package/src/utils.ts CHANGED
@@ -38,3 +38,22 @@ export const isSerializable = (data: any): boolean => {
38
38
  data instanceof Float64Array
39
39
  )
40
40
  }
41
+
42
+ const EMPTY_ARRAY = Object.freeze([])
43
+
44
+ export const freezeDedupe = <T extends Function>(
45
+ arr?: readonly T[] | T[] | undefined
46
+ ): readonly T[] => {
47
+ if (!arr || arr.length === 0) return EMPTY_ARRAY
48
+ if (arr.length === 1) return Object.freeze([arr[0]!])
49
+ const seen = new Set<T>()
50
+ const out: T[] = []
51
+ for (let i = 0; i < arr.length; i++) {
52
+ const fn = arr[i]!
53
+ if (!seen.has(fn)) {
54
+ seen.add(fn)
55
+ out.push(fn)
56
+ }
57
+ }
58
+ return Object.freeze(out)
59
+ }
@@ -2,6 +2,7 @@ import {
2
2
  CoreServices,
3
3
  JSONValue,
4
4
  CoreUserSession,
5
+ PikkuWiringTypes,
5
6
  } from '../../types/core.types.js'
6
7
  import {
7
8
  ChannelMessageMeta,
@@ -98,17 +99,22 @@ export const processMessageHandlers = (
98
99
  const middleware =
99
100
  typeof onMessage === 'function' ? [] : onMessage.middleware
100
101
 
101
- return await runPikkuFunc(pikkuFuncName, {
102
- getAllServices: () => ({
103
- ...services,
104
- channel: channelHandler.getChannel(),
105
- }),
106
- data,
107
- session,
108
- permissions,
109
- middleware,
110
- tags: channelConfig.tags,
111
- })
102
+ return await runPikkuFunc(
103
+ PikkuWiringTypes.channel,
104
+ channelConfig.name,
105
+ pikkuFuncName,
106
+ {
107
+ getAllServices: () => ({
108
+ ...services,
109
+ channel: channelHandler.getChannel(),
110
+ }),
111
+ data,
112
+ session,
113
+ permissions,
114
+ middleware,
115
+ tags: channelConfig.tags,
116
+ }
117
+ )
112
118
  }
113
119
 
114
120
  const onMessage = async (rawData): Promise<unknown> => {
@@ -3,13 +3,14 @@ import { addFunction } from '../../function/function-runner.js'
3
3
  import { pikkuState } from '../../pikku-state.js'
4
4
  import { coerceTopLevelDataFromSchema, validateSchema } from '../../schema.js'
5
5
  import { UserSessionService } from '../../services/user-session-service.js'
6
+ import { CorePikkuMiddleware } from '../../types/core.types.js'
7
+ import { httpRouter } from '../http/routers/http-router.js'
6
8
  import {
7
9
  ChannelMeta,
8
10
  CoreChannel,
9
11
  RunChannelOptions,
10
12
  RunChannelParams,
11
13
  } from './channel.types.js'
12
- import { match } from 'path-to-regexp'
13
14
 
14
15
  /**
15
16
  * Adds a channel and registers all functions referenced in it using the
@@ -91,30 +92,33 @@ export const wireChannel = <
91
92
  }
92
93
 
93
94
  const getMatchingChannelConfig = (request: string) => {
95
+ const matchedPath = httpRouter.match('get', request)
96
+ if (!matchedPath) {
97
+ return null
98
+ }
99
+
100
+ const meta = pikkuState('channel', 'meta')
101
+ const channelMeta = Object.values(meta).find(
102
+ (channelConfig) => channelConfig.route === matchedPath.route
103
+ )
104
+ if (!channelMeta) {
105
+ return null
106
+ }
107
+
94
108
  const channels = pikkuState('channel', 'channels')
95
- const channelsMeta = pikkuState('channel', 'meta')
96
- for (const channelConfig of channels.values()) {
97
- const cleanedRoute = channelConfig.route.replace(/^\/\//, '/')
98
- const cleanedRequest = request.replace(/^\/\//, '/')
99
- const matchFunc = match(cleanedRoute, {
100
- decode: decodeURIComponent,
101
- })
102
- const matchedPath = matchFunc(cleanedRequest)
103
- if (matchedPath) {
104
- const channelMeta = channelsMeta[channelConfig.name]
105
- if (!channelMeta) {
106
- throw new Error(`Channel ${channelConfig.name} not found in metadata`)
107
- }
108
- return {
109
- matchedPath,
110
- params: matchedPath.params,
111
- channelConfig,
112
- schemaName: channelMeta.input,
113
- meta: channelMeta,
114
- }
115
- }
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
+ httpMiddleware: matchedPath.middleware,
116
121
  }
117
- return null
118
122
  }
119
123
 
120
124
  export const openChannel = async ({
@@ -129,6 +133,7 @@ export const openChannel = async ({
129
133
  openingData: unknown
130
134
  channelConfig: CoreChannel<unknown, any>
131
135
  meta: ChannelMeta
136
+ httpMiddleware: CorePikkuMiddleware[] | undefined
132
137
  }> => {
133
138
  const matchingChannel = getMatchingChannelConfig(route)
134
139
  if (!matchingChannel) {
@@ -136,7 +141,8 @@ export const openChannel = async ({
136
141
  throw new NotFoundError(`Channel not found: ${route}`)
137
142
  }
138
143
 
139
- const { params, channelConfig, schemaName, meta } = matchingChannel
144
+ const { params, channelConfig, schemaName, meta, httpMiddleware } =
145
+ matchingChannel
140
146
 
141
147
  const requiresSession = channelConfig.auth !== false
142
148
  request?.setParams(params)
@@ -159,5 +165,5 @@ export const openChannel = async ({
159
165
  )
160
166
  }
161
167
 
162
- return { openingData, channelConfig, meta }
168
+ return { openingData, channelConfig, meta, httpMiddleware }
163
169
  }
@@ -10,6 +10,7 @@ import {
10
10
  PikkuQuery,
11
11
  } from '../../http/http.types.js'
12
12
  import { SerializeOptions } from 'cookie'
13
+ import { httpRouter } from '../../http/routers/http-router.js'
13
14
 
14
15
  /**
15
16
  * Minimal stubs for dependencies that runChannel expects.
@@ -139,6 +140,9 @@ test('runChannel should return a channel handler if channel matches and no auth
139
140
  auth: false,
140
141
  })
141
142
 
143
+ // Initialize router after adding channel (for tests)
144
+ httpRouter.initialize()
145
+
142
146
  const result = await runLocalChannel({
143
147
  singletonServices: mockSingletonServices,
144
148
  channelId: 'test-channel-id',
@@ -8,12 +8,9 @@ import {
8
8
  RunChannelParams,
9
9
  } from '../channel.types.js'
10
10
  import { PikkuLocalChannelHandler } from './local-channel-handler.js'
11
- import { SessionServices } from '../../../types/core.types.js'
11
+ import { PikkuWiringTypes, SessionServices } from '../../../types/core.types.js'
12
12
  import { handleHTTPError } from '../../../handle-error.js'
13
- import {
14
- addMiddlewareForTags,
15
- runMiddleware,
16
- } from '../../../middleware-runner.js'
13
+ import { combineMiddleware, runMiddleware } from '../../../middleware-runner.js'
17
14
  import { PikkuUserSessionService } from '../../../services/user-session-service.js'
18
15
  import { PikkuHTTP } from '../../http/http.types.js'
19
16
  import { runPikkuFuncDirectly } from '../../../function/function-runner.js'
@@ -45,9 +42,9 @@ export const runLocalChannel = async ({
45
42
  route = http?.request?.path()
46
43
  }
47
44
 
48
- let openingData, channelConfig, meta
45
+ let openingData, channelConfig, meta, httpMiddleware
49
46
  try {
50
- ;({ openingData, channelConfig, meta } = await openChannel({
47
+ ;({ openingData, channelConfig, meta, httpMiddleware } = await openChannel({
51
48
  channelId,
52
49
  createSessionServices,
53
50
  respondWith404,
@@ -149,7 +146,11 @@ export const runLocalChannel = async ({
149
146
  userSession,
150
147
  },
151
148
  { http },
152
- addMiddlewareForTags(channelConfig.middleware, channelConfig.tags),
149
+ combineMiddleware(PikkuWiringTypes.channel, channelConfig.name, {
150
+ wiringMiddleware: channelConfig.middleware,
151
+ wiringTags: channelConfig.tags,
152
+ httpMiddleware,
153
+ }),
153
154
  main
154
155
  )
155
156
 
@@ -1,4 +1,4 @@
1
- import { SessionServices } from '../../../types/core.types.js'
1
+ import { PikkuWiringTypes, SessionServices } from '../../../types/core.types.js'
2
2
  import { closeSessionServices } from '../../../utils.js'
3
3
  import { processMessageHandlers } from '../channel-handler.js'
4
4
  import { openChannel } from '../channel-runner.js'
@@ -12,10 +12,7 @@ import { createHTTPInteraction } from '../../http/http-runner.js'
12
12
  import { ChannelStore } from '../channel-store.js'
13
13
  import { handleHTTPError } from '../../../handle-error.js'
14
14
  import { PikkuUserSessionService } from '../../../services/user-session-service.js'
15
- import {
16
- addMiddlewareForTags,
17
- runMiddleware,
18
- } from '../../../middleware-runner.js'
15
+ import { combineMiddleware, runMiddleware } from '../../../middleware-runner.js'
19
16
  import { pikkuState } from '../../../pikku-state.js'
20
17
  import { PikkuFetchHTTPRequest } from '../../http/pikku-fetch-http-request.js'
21
18
  import { PikkuHTTP } from '../../http/http.types.js'
@@ -88,15 +85,16 @@ export const runChannelConnect = async ({
88
85
 
89
86
  const userSession = new PikkuUserSessionService(channelStore, channelId)
90
87
 
91
- const { channelConfig, openingData, meta } = await openChannel({
92
- channelId,
93
- createSessionServices,
94
- request,
95
- route,
96
- singletonServices,
97
- coerceDataFromSchema,
98
- userSession,
99
- })
88
+ const { channelConfig, openingData, meta, httpMiddleware } =
89
+ await openChannel({
90
+ channelId,
91
+ createSessionServices,
92
+ request,
93
+ route,
94
+ singletonServices,
95
+ coerceDataFromSchema,
96
+ userSession,
97
+ })
100
98
 
101
99
  const main = async () => {
102
100
  try {
@@ -149,7 +147,11 @@ export const runChannelConnect = async ({
149
147
  userSession,
150
148
  },
151
149
  { http },
152
- addMiddlewareForTags(channelConfig.middleware, channelConfig.tags),
150
+ combineMiddleware(PikkuWiringTypes.channel, channelConfig.name, {
151
+ wiringMiddleware: channelConfig.middleware,
152
+ wiringTags: channelConfig.tags,
153
+ httpMiddleware,
154
+ }),
153
155
  main
154
156
  )
155
157
  }
@@ -9,6 +9,7 @@ import {
9
9
  PikkuMockResponse,
10
10
  } from '../channel/local/local-channel-runner.test.js'
11
11
  import { addFunction } from '../../function/function-runner.js'
12
+ import { httpRouter } from './routers/http-router.js'
12
13
 
13
14
  const sessionMiddleware: CorePikkuMiddleware = async (services, _, next) => {
14
15
  services.userSession.set({ userId: 'test' } as any)
@@ -22,13 +23,21 @@ const setHTTPFunctionMap = (func: any) => {
22
23
  services: ['userSession'],
23
24
  },
24
25
  } as any)
25
- pikkuState('http', 'meta', [
26
- {
27
- pikkuFuncName: 'pikku_func_name',
28
- route: 'test',
29
- method: 'get',
26
+ pikkuState('http', 'meta', {
27
+ get: {
28
+ test: {
29
+ pikkuFuncName: 'pikku_func_name',
30
+ route: 'test',
31
+ method: 'get',
32
+ },
30
33
  },
31
- ])
34
+ post: {},
35
+ delete: {},
36
+ patch: {},
37
+ head: {},
38
+ put: {},
39
+ options: {},
40
+ })
32
41
  addFunction('pikku_func_name', { func })
33
42
  }
34
43
 
@@ -85,6 +94,9 @@ describe('fetch', () => {
85
94
  middleware: [sessionMiddleware],
86
95
  })
87
96
 
97
+ // Initialize router after adding route (for tests)
98
+ httpRouter.initialize()
99
+
88
100
  const result = await fetch(request, {
89
101
  singletonServices,
90
102
  createSessionServices,
@@ -12,8 +12,8 @@ import {
12
12
  CoreUserSession,
13
13
  CorePikkuMiddleware,
14
14
  SessionServices,
15
+ PikkuWiringTypes,
15
16
  } from '../../types/core.types.js'
16
- import { match } from 'path-to-regexp'
17
17
  import { MissingSessionError, NotFoundError } from '../../errors/errors.js'
18
18
  import {
19
19
  closeSessionServices,
@@ -24,7 +24,7 @@ import {
24
24
  PikkuUserSessionService,
25
25
  UserSessionService,
26
26
  } from '../../services/user-session-service.js'
27
- import { addMiddlewareForTags, runMiddleware } from '../../middleware-runner.js'
27
+ import { combineMiddleware, runMiddleware } from '../../middleware-runner.js'
28
28
  import { handleHTTPError } from '../../handle-error.js'
29
29
  import { pikkuState } from '../../pikku-state.js'
30
30
  import { PikkuFetchHTTPResponse } from './pikku-fetch-http-response.js'
@@ -32,6 +32,7 @@ import { PikkuFetchHTTPRequest } from './pikku-fetch-http-request.js'
32
32
  import { PikkuChannel } from '../channel/channel.types.js'
33
33
  import { addFunction, runPikkuFunc } from '../../function/function-runner.js'
34
34
  import { rpcService } from '../rpc/rpc-runner.js'
35
+ import { httpRouter } from './routers/http-router.js'
35
36
 
36
37
  /**
37
38
  * Registers middleware either globally or for a specific route.
@@ -49,32 +50,20 @@ export const addHTTPMiddleware = <PikkuMiddleware extends CorePikkuMiddleware>(
49
50
  middleware?: PikkuMiddleware[]
50
51
  ) => {
51
52
  const middlewareStore = pikkuState('http', 'middleware')
53
+ let route = '*'
52
54
 
53
- if (typeof routeOrMiddleware === 'string' && middleware) {
54
- const route = routeOrMiddleware
55
-
56
- // Remove existing entry for this route if it exists
57
- const existingIndex = middlewareStore.findIndex(
58
- (item) => item.route === route
59
- )
60
- if (existingIndex !== -1) {
61
- middlewareStore.splice(existingIndex, 1)
62
- }
63
-
64
- middlewareStore.push({ route, middleware })
55
+ if (typeof routeOrMiddleware === 'string') {
56
+ route = routeOrMiddleware
57
+ middleware = middleware!
65
58
  } else {
66
- // Handle global middleware
67
- const existingIndex = middlewareStore.findIndex(
68
- (item) => item.route === '*'
69
- )
70
- if (existingIndex !== -1) {
71
- middlewareStore.splice(existingIndex, 1)
72
- }
59
+ middleware = routeOrMiddleware
60
+ }
73
61
 
74
- middlewareStore.push({
75
- route: '*',
76
- middleware: routeOrMiddleware as any,
77
- })
62
+ const currentMiddleware = middlewareStore.get(route)
63
+ if (currentMiddleware) {
64
+ middlewareStore.set(route, [...currentMiddleware, ...middleware])
65
+ } else {
66
+ middlewareStore.set(route, middleware)
78
67
  }
79
68
  }
80
69
 
@@ -113,10 +102,7 @@ export const wireHTTP = <
113
102
  >
114
103
  ) => {
115
104
  const httpMeta = pikkuState('http', 'meta')
116
- const routeMeta = httpMeta.find(
117
- (meta) =>
118
- meta.route === httpWiring.route && meta.method === httpWiring.method
119
- )
105
+ const routeMeta = httpMeta[httpWiring.method][httpWiring.route]
120
106
  if (!routeMeta) {
121
107
  throw new Error('Route metadata not found')
122
108
  }
@@ -142,43 +128,29 @@ export const wireHTTP = <
142
128
  * @returns {Object | undefined} An object with matched route details or undefined if no match.
143
129
  */
144
130
  const getMatchingRoute = (requestType: string, requestPath: string) => {
145
- const allRoutes = pikkuState('http', 'routes')
146
- const middleware = pikkuState('http', 'middleware')
147
- const routes = allRoutes.get(requestType.toLowerCase() as HTTPMethod)
148
- if (!routes) {
149
- return undefined
150
- }
151
- for (const route of routes.values()) {
152
- // Generate a matching function from the route pattern
153
- const matchFunc = match(`/${route.route}`.replace(/^\/\//, '/'), {
154
- decode: decodeURIComponent,
155
- })
156
-
157
- // Attempt to match the request path
158
- const matchedPath = matchFunc(requestPath.replace(/^\/\//, '/'))
159
-
160
- if (matchedPath) {
161
- // Aggregate global and route-specific middleware
162
- const globalMiddleware = middleware
163
- .filter((m) => m.route === '*' || new RegExp(m.route).test(route.route))
164
- .map((m) => m.middleware)
165
- .flat()
166
-
167
- const meta = pikkuState('http', 'meta').find(
168
- (meta) => meta.route === route.route && meta.method === route.method
169
- )
131
+ const matchedPath = httpRouter.match(
132
+ requestType.toLowerCase() as HTTPMethod,
133
+ requestPath
134
+ )
170
135
 
171
- return {
172
- matchedPath,
173
- params: matchedPath.params,
174
- route,
175
- permissions: route.permissions,
176
- middleware: [...globalMiddleware, ...(route.middleware || [])],
177
- meta: meta!,
178
- }
136
+ if (matchedPath) {
137
+ const route = pikkuState('http', 'routes')
138
+ .get(requestType.toLowerCase() as HTTPMethod)!
139
+ .get(matchedPath.route)!
140
+ const meta = pikkuState('http', 'meta')[
141
+ requestType.toLowerCase() as PikkuWiringTypes
142
+ ][route.route]
143
+
144
+ return {
145
+ matchedPath,
146
+ params: matchedPath.params,
147
+ route,
148
+ permissions: route.permissions,
149
+ httpMiddleware: matchedPath.middleware,
150
+ middleware: route.middleware,
151
+ meta: meta!,
179
152
  }
180
153
  }
181
- return undefined
182
154
  }
183
155
 
184
156
  /**
@@ -251,7 +223,8 @@ const executeRouteWithMiddleware = async (
251
223
  matchedPath: any
252
224
  params: any
253
225
  route: CoreHTTPFunctionWiring<any, any, any>
254
- middleware: any[]
226
+ httpMiddleware: CorePikkuMiddleware[] | undefined
227
+ middleware: CorePikkuMiddleware[] | undefined
255
228
  meta: HTTPWiringMeta
256
229
  },
257
230
  http: PikkuHTTP,
@@ -259,7 +232,8 @@ const executeRouteWithMiddleware = async (
259
232
  coerceDataFromSchema: boolean
260
233
  }
261
234
  ) => {
262
- const { matchedPath, params, route, middleware, meta } = matchedRoute
235
+ const { matchedPath, params, route, httpMiddleware, middleware, meta } =
236
+ matchedRoute
263
237
  const {
264
238
  singletonServices,
265
239
  userSession,
@@ -347,14 +321,19 @@ const executeRouteWithMiddleware = async (
347
321
  })
348
322
  }
349
323
 
350
- result = await runPikkuFunc(meta.pikkuFuncName, {
351
- getAllServices,
352
- session,
353
- data,
354
- permissions: route.permissions,
355
- coerceDataFromSchema: options.coerceDataFromSchema,
356
- tags: route.tags,
357
- })
324
+ result = await runPikkuFunc(
325
+ PikkuWiringTypes.http,
326
+ `${meta.method}:${meta.route}`,
327
+ meta.pikkuFuncName,
328
+ {
329
+ getAllServices,
330
+ session,
331
+ data,
332
+ permissions: route.permissions,
333
+ coerceDataFromSchema: options.coerceDataFromSchema,
334
+ tags: route.tags,
335
+ }
336
+ )
358
337
 
359
338
  // Respond with either a binary or JSON response based on configuration
360
339
  if (route.returnsJSON === false) {
@@ -368,11 +347,20 @@ const executeRouteWithMiddleware = async (
368
347
  // http?.response?.end()
369
348
  }
370
349
 
350
+ // Get function config for middleware and tags
351
+ const funcConfig = pikkuState('function', 'functions').get(meta.pikkuFuncName)
352
+
371
353
  // Execute middleware, then run the main logic
372
354
  await runMiddleware(
373
355
  { ...singletonServices, userSession },
374
356
  { http },
375
- addMiddlewareForTags(middleware, route.tags),
357
+ combineMiddleware(PikkuWiringTypes.http, `${meta.method}:${meta.route}`, {
358
+ httpMiddleware,
359
+ wiringMiddleware: middleware,
360
+ wiringTags: route.tags,
361
+ funcMiddleware: funcConfig?.middleware,
362
+ funcTags: funcConfig?.tags,
363
+ }),
376
364
  runMain
377
365
  )
378
366
 
@@ -498,7 +486,8 @@ export const fetchData = async <In, Out>(
498
486
  ignoreMiddleware
499
487
  ? {
500
488
  ...matchedRoute,
501
- middleware: [],
489
+ middleware: undefined,
490
+ httpMiddleware: undefined,
502
491
  }
503
492
  : matchedRoute,
504
493
  http!,