@pikku/core 0.9.5 → 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 (70) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/function/function-runner.d.ts +2 -2
  3. package/dist/function/function-runner.js +3 -3
  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 +5 -4
  9. package/dist/middleware-runner.js +20 -6
  10. package/dist/permissions.d.ts +3 -3
  11. package/dist/permissions.js +30 -8
  12. package/dist/pikku-state.d.ts +2 -4
  13. package/dist/pikku-state.js +3 -2
  14. package/dist/schema.js +8 -6
  15. package/dist/types/core.types.d.ts +4 -0
  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 +5 -3
  23. package/dist/wirings/channel/serverless/serverless-channel-runner.js +4 -2
  24. package/dist/wirings/http/http-runner.js +36 -53
  25. package/dist/wirings/http/http.types.d.ts +1 -1
  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 +3 -2
  33. package/dist/wirings/queue/queue-runner.js +3 -2
  34. package/dist/wirings/rpc/rpc-runner.js +2 -1
  35. package/dist/wirings/scheduler/scheduler-runner.d.ts +1 -1
  36. package/dist/wirings/scheduler/scheduler-runner.js +3 -2
  37. package/package.json +1 -1
  38. package/src/factory-functions.test.ts +37 -0
  39. package/src/function/function-runner.test.ts +101 -52
  40. package/src/function/function-runner.ts +5 -2
  41. package/src/function/functions.types.ts +13 -0
  42. package/src/index.ts +3 -11
  43. package/src/middleware-runner.test.ts +71 -43
  44. package/src/middleware-runner.ts +43 -17
  45. package/src/permissions.test.ts +21 -17
  46. package/src/permissions.ts +68 -26
  47. package/src/pikku-state.ts +5 -3
  48. package/src/schema.ts +8 -6
  49. package/src/types/core.types.ts +12 -0
  50. package/src/utils.ts +19 -0
  51. package/src/wirings/channel/channel-handler.ts +17 -11
  52. package/src/wirings/channel/channel-runner.ts +31 -25
  53. package/src/wirings/channel/local/local-channel-runner.test.ts +4 -0
  54. package/src/wirings/channel/local/local-channel-runner.ts +5 -4
  55. package/src/wirings/channel/serverless/serverless-channel-runner.ts +13 -11
  56. package/src/wirings/http/http-runner.test.ts +18 -6
  57. package/src/wirings/http/http-runner.ts +55 -74
  58. package/src/wirings/http/http.types.ts +1 -1
  59. package/src/wirings/http/pikku-fetch-http-request.ts +3 -0
  60. package/src/wirings/http/routers/http-router.ts +15 -0
  61. package/src/wirings/http/routers/path-to-regex.test.ts +309 -0
  62. package/src/wirings/http/routers/path-to-regex.ts +162 -0
  63. package/src/wirings/mcp/mcp-runner.ts +18 -12
  64. package/src/wirings/queue/queue-runner.ts +15 -7
  65. package/src/wirings/rpc/rpc-runner.ts +21 -16
  66. package/src/wirings/scheduler/scheduler-runner.ts +18 -12
  67. package/tsconfig.tsbuildinfo +1 -1
  68. package/dist/pikku-function.d.ts +0 -1
  69. package/dist/pikku-function.js +0 -1
  70. package/src/pikku-function.ts +0 -1
@@ -6,7 +6,11 @@ import {
6
6
  runPermissions,
7
7
  } from './permissions.js'
8
8
  import { resetPikkuState } from './pikku-state.js'
9
- import { CoreServices, CoreUserSession } from './types/core.types.js'
9
+ import {
10
+ CoreServices,
11
+ CoreUserSession,
12
+ PikkuWiringTypes,
13
+ } from './types/core.types.js'
10
14
  import { CorePermissionGroup } from './function/functions.types.js'
11
15
 
12
16
  beforeEach(() => {
@@ -160,7 +164,7 @@ describe('getPermissionsForTags', () => {
160
164
  describe('runPermissions', () => {
161
165
  test('should pass when no permissions are defined', async () => {
162
166
  // Should not throw
163
- await runPermissions({
167
+ await runPermissions(PikkuWiringTypes.rpc, Math.random().toString(), {
164
168
  allServices: mockServices,
165
169
  data: {},
166
170
  session: mockSession,
@@ -176,7 +180,7 @@ describe('runPermissions', () => {
176
180
 
177
181
  addPermission('wiringTag', [wiringTagPermission])
178
182
 
179
- await runPermissions({
183
+ await runPermissions(PikkuWiringTypes.rpc, Math.random().toString(), {
180
184
  wiringTags: ['wiringTag'],
181
185
  allServices: mockServices,
182
186
  data: {},
@@ -219,7 +223,7 @@ describe('runPermissions', () => {
219
223
  ],
220
224
  }
221
225
 
222
- await runPermissions({
226
+ await runPermissions(PikkuWiringTypes.rpc, Math.random().toString(), {
223
227
  wiringTags: ['wiringTag'],
224
228
  wiringPermissions,
225
229
  funcTags: ['funcTag'],
@@ -245,7 +249,7 @@ describe('runPermissions', () => {
245
249
  addPermission('atLeastOneTestTag', [failingPermission, passingPermission])
246
250
 
247
251
  // Should not throw because at least one permission passes
248
- await runPermissions({
252
+ await runPermissions(PikkuWiringTypes.rpc, Math.random().toString(), {
249
253
  wiringTags: ['atLeastOneTestTag'],
250
254
  allServices: mockServices,
251
255
  data: {},
@@ -260,14 +264,14 @@ describe('runPermissions', () => {
260
264
  addPermission('allFailTestTag', [failingPermission1, failingPermission2])
261
265
 
262
266
  await assert.rejects(
263
- runPermissions({
267
+ runPermissions(PikkuWiringTypes.rpc, Math.random().toString(), {
264
268
  wiringTags: ['allFailTestTag'],
265
269
  allServices: mockServices,
266
270
  data: {},
267
271
  session: mockSession,
268
272
  }),
269
273
  {
270
- message: 'Permission denied - wiring tag permissions',
274
+ message: 'Permission denied',
271
275
  }
272
276
  )
273
277
  })
@@ -278,14 +282,14 @@ describe('runPermissions', () => {
278
282
  }
279
283
 
280
284
  await assert.rejects(
281
- runPermissions({
285
+ runPermissions(PikkuWiringTypes.rpc, Math.random().toString(), {
282
286
  wiringPermissions,
283
287
  allServices: mockServices,
284
288
  data: {},
285
289
  session: mockSession,
286
290
  }),
287
291
  {
288
- message: 'Permission denied - wiring permissions',
292
+ message: 'Permission denied',
289
293
  }
290
294
  )
291
295
  })
@@ -296,14 +300,14 @@ describe('runPermissions', () => {
296
300
  addPermission('funcTag', [failingPermission])
297
301
 
298
302
  await assert.rejects(
299
- runPermissions({
303
+ runPermissions(PikkuWiringTypes.rpc, Math.random().toString(), {
300
304
  funcTags: ['funcTag'],
301
305
  allServices: mockServices,
302
306
  data: {},
303
307
  session: mockSession,
304
308
  }),
305
309
  {
306
- message: 'Permission denied - function tag permissions',
310
+ message: 'Permission denied',
307
311
  }
308
312
  )
309
313
  })
@@ -314,14 +318,14 @@ describe('runPermissions', () => {
314
318
  }
315
319
 
316
320
  await assert.rejects(
317
- runPermissions({
321
+ runPermissions(PikkuWiringTypes.rpc, Math.random().toString(), {
318
322
  funcPermissions,
319
323
  allServices: mockServices,
320
324
  data: {},
321
325
  session: mockSession,
322
326
  }),
323
327
  {
324
- message: 'Permission denied - function permissions',
328
+ message: 'Permission denied',
325
329
  }
326
330
  )
327
331
  })
@@ -346,7 +350,7 @@ describe('runPermissions', () => {
346
350
  }
347
351
 
348
352
  await assert.rejects(
349
- runPermissions({
353
+ runPermissions(PikkuWiringTypes.rpc, Math.random().toString(), {
350
354
  wiringTags: ['failingWiringTag'],
351
355
  wiringPermissions,
352
356
  allServices: mockServices,
@@ -365,7 +369,7 @@ describe('runPermissions', () => {
365
369
  addPermission('arrayTestTag', arrayPermission)
366
370
 
367
371
  // Should not throw because verifyPermissions handles array properly
368
- await runPermissions({
372
+ await runPermissions(PikkuWiringTypes.rpc, Math.random().toString(), {
369
373
  wiringTags: ['arrayTestTag'],
370
374
  allServices: mockServices,
371
375
  data: {},
@@ -381,7 +385,7 @@ describe('runPermissions', () => {
381
385
  addPermission('objectTestTag', permissionGroup)
382
386
 
383
387
  // Should not throw because verifyPermissions handles objects properly
384
- await runPermissions({
388
+ await runPermissions(PikkuWiringTypes.rpc, Math.random().toString(), {
385
389
  wiringTags: ['objectTestTag'],
386
390
  allServices: mockServices,
387
391
  data: {},
@@ -405,7 +409,7 @@ describe('runPermissions', () => {
405
409
 
406
410
  addPermission('paramTestTag', [testPermission])
407
411
 
408
- await runPermissions({
412
+ await runPermissions(PikkuWiringTypes.rpc, Math.random().toString(), {
409
413
  wiringTags: ['paramTestTag'],
410
414
  allServices: mockServices,
411
415
  data: testData,
@@ -1,4 +1,8 @@
1
- import { CoreServices, CoreUserSession } from './types/core.types.js'
1
+ import {
2
+ CoreServices,
3
+ CoreUserSession,
4
+ PikkuWiringTypes,
5
+ } from './types/core.types.js'
2
6
  import {
3
7
  CorePermissionGroup,
4
8
  CorePikkuPermission,
@@ -86,6 +90,8 @@ export const addPermission = (
86
90
  permissionsStore[tag] = permissions
87
91
  }
88
92
 
93
+ const EMPTY: readonly (CorePermissionGroup | CorePikkuPermission)[] = []
94
+
89
95
  /**
90
96
  * Retrieves permissions for a given set of tags.
91
97
  *
@@ -103,9 +109,9 @@ export const addPermission = (
103
109
  */
104
110
  export const getPermissionsForTags = (
105
111
  tags?: string[]
106
- ): Array<CorePermissionGroup | CorePikkuPermission> => {
112
+ ): readonly (CorePermissionGroup | CorePikkuPermission)[] => {
107
113
  if (!tags || tags.length === 0) {
108
- return []
114
+ return EMPTY
109
115
  }
110
116
 
111
117
  const permissionsStore = pikkuState('misc', 'permissions')
@@ -128,6 +134,26 @@ export const getPermissionsForTags = (
128
134
  return applicablePermissions
129
135
  }
130
136
 
137
+ const permissionCache: Record<
138
+ PikkuWiringTypes,
139
+ Partial<
140
+ Record<
141
+ string,
142
+ {
143
+ wiringTags: readonly (CorePermissionGroup | CorePikkuPermission)[]
144
+ funcTags: readonly (CorePermissionGroup | CorePikkuPermission)[]
145
+ }
146
+ >
147
+ >
148
+ > = {
149
+ [PikkuWiringTypes.http]: {},
150
+ [PikkuWiringTypes.rpc]: {},
151
+ [PikkuWiringTypes.channel]: {},
152
+ [PikkuWiringTypes.queue]: {},
153
+ [PikkuWiringTypes.scheduler]: {},
154
+ [PikkuWiringTypes.mcp]: {},
155
+ }
156
+
131
157
  /**
132
158
  * Runs permission checks in the specified order:
133
159
  * 1) wiring tag permissions - at least one must pass if any exist
@@ -135,27 +161,39 @@ export const getPermissionsForTags = (
135
161
  * 3) function tag permissions - at least one must pass if any exist
136
162
  * 4) function permissions - must pass if defined
137
163
  */
138
- export const runPermissions = async ({
139
- wiringTags,
140
- wiringPermissions,
141
- funcTags,
142
- funcPermissions,
143
- allServices,
144
- data,
145
- session,
146
- }: {
147
- wiringTags?: string[]
148
- wiringPermissions?: CorePermissionGroup
149
- funcTags?: string[]
150
- funcPermissions?: CorePermissionGroup
151
- allServices: CoreServices
152
- data: any
153
- session?: CoreUserSession
154
- }) => {
164
+ export const runPermissions = async (
165
+ wireType: PikkuWiringTypes,
166
+ uid: string,
167
+ {
168
+ wiringTags,
169
+ wiringPermissions,
170
+ funcTags,
171
+ funcPermissions,
172
+ allServices,
173
+ data,
174
+ session,
175
+ }: {
176
+ wiringTags?: string[]
177
+ wiringPermissions?: CorePermissionGroup
178
+ funcTags?: string[]
179
+ funcPermissions?: CorePermissionGroup
180
+ allServices: CoreServices
181
+ data: any
182
+ session?: CoreUserSession
183
+ }
184
+ ) => {
185
+ let cachedPermission = permissionCache[wireType][uid]
186
+ if (!cachedPermission) {
187
+ cachedPermission = {
188
+ wiringTags: getPermissionsForTags(wiringTags),
189
+ funcTags: getPermissionsForTags(funcTags),
190
+ }
191
+ permissionCache[wireType][uid] = cachedPermission
192
+ }
155
193
  let permissioned = true
156
194
 
157
195
  // 1. Wiring tag permissions - at least one must pass if any exist
158
- const wiringTaggedPermissions = getPermissionsForTags(wiringTags || [])
196
+ const wiringTaggedPermissions = cachedPermission.wiringTags
159
197
  if (wiringTaggedPermissions.length > 0) {
160
198
  permissioned = false // Start false, need at least one to pass
161
199
  for (const permissions of wiringTaggedPermissions) {
@@ -171,7 +209,8 @@ export const runPermissions = async ({
171
209
  }
172
210
  }
173
211
  if (!permissioned) {
174
- throw new ForbiddenError('Permission denied - wiring tag permissions')
212
+ allServices.logger.debug('Permission denied - wiring tag permissions')
213
+ throw new ForbiddenError('Permission denied')
175
214
  }
176
215
  }
177
216
 
@@ -184,12 +223,13 @@ export const runPermissions = async ({
184
223
  session
185
224
  )
186
225
  if (!permissioned) {
187
- throw new ForbiddenError('Permission denied - wiring permissions')
226
+ allServices.logger.debug('Permission denied - wiring permissions')
227
+ throw new ForbiddenError('Permission denied')
188
228
  }
189
229
  }
190
230
 
191
231
  // 3. Function tag permissions - at least one must pass if any exist
192
- const funcTaggedPermissions = getPermissionsForTags(funcTags || [])
232
+ const funcTaggedPermissions = cachedPermission.funcTags
193
233
  if (funcTaggedPermissions.length > 0) {
194
234
  permissioned = false // Start false, need at least one to pass
195
235
  for (const permissions of funcTaggedPermissions) {
@@ -205,7 +245,8 @@ export const runPermissions = async ({
205
245
  }
206
246
  }
207
247
  if (!permissioned) {
208
- throw new ForbiddenError('Permission denied - function tag permissions')
248
+ allServices.logger.debug('Permission denied - function tag permissions')
249
+ throw new ForbiddenError('Permission denied')
209
250
  }
210
251
  }
211
252
 
@@ -218,7 +259,8 @@ export const runPermissions = async ({
218
259
  session
219
260
  )
220
261
  if (!permissioned) {
221
- throw new ForbiddenError('Permission denied - function permissions')
262
+ allServices.logger.debug('Permission denied - function permissions')
263
+ throw new ForbiddenError('Permission denied')
222
264
  }
223
265
  }
224
266
  }
@@ -44,7 +44,8 @@ interface PikkuState {
44
44
  >
45
45
  }
46
46
  http: {
47
- middleware: Array<{ route: string; middleware: CorePikkuMiddleware[] }>
47
+ middleware: Map<string, CorePikkuMiddleware<any, any>[]>
48
+ permissions: Map<string, CorePermissionGroup | CorePikkuPermission[]>
48
49
  routes: Map<HTTPMethod, Map<string, CoreHTTPFunctionWiring<any, any, any>>>
49
50
  meta: HTTPWiringsMeta
50
51
  }
@@ -87,9 +88,10 @@ export const resetPikkuState = () => {
87
88
  files: new Map(),
88
89
  },
89
90
  http: {
90
- middleware: [],
91
+ permissions: new Map(),
92
+ middleware: new Map(),
91
93
  routes: new Map(),
92
- meta: [],
94
+ meta: {},
93
95
  },
94
96
  channel: {
95
97
  channels: new Map(),
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) {
@@ -137,6 +137,18 @@ export type CorePikkuMiddleware<
137
137
  next: () => Promise<void>
138
138
  ) => Promise<void>
139
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
+
140
152
  /**
141
153
  * Represents the core services used by Pikku, including singleton services and the request/response interaction.
142
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,7 +8,7 @@ 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
13
  import { combineMiddleware, runMiddleware } from '../../../middleware-runner.js'
14
14
  import { PikkuUserSessionService } from '../../../services/user-session-service.js'
@@ -42,9 +42,9 @@ export const runLocalChannel = async ({
42
42
  route = http?.request?.path()
43
43
  }
44
44
 
45
- let openingData, channelConfig, meta
45
+ let openingData, channelConfig, meta, httpMiddleware
46
46
  try {
47
- ;({ openingData, channelConfig, meta } = await openChannel({
47
+ ;({ openingData, channelConfig, meta, httpMiddleware } = await openChannel({
48
48
  channelId,
49
49
  createSessionServices,
50
50
  respondWith404,
@@ -146,9 +146,10 @@ export const runLocalChannel = async ({
146
146
  userSession,
147
147
  },
148
148
  { http },
149
- combineMiddleware({
149
+ combineMiddleware(PikkuWiringTypes.channel, channelConfig.name, {
150
150
  wiringMiddleware: channelConfig.middleware,
151
151
  wiringTags: channelConfig.tags,
152
+ httpMiddleware,
152
153
  }),
153
154
  main
154
155
  )
@@ -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'
@@ -85,15 +85,16 @@ export const runChannelConnect = async ({
85
85
 
86
86
  const userSession = new PikkuUserSessionService(channelStore, channelId)
87
87
 
88
- const { channelConfig, openingData, meta } = await openChannel({
89
- channelId,
90
- createSessionServices,
91
- request,
92
- route,
93
- singletonServices,
94
- coerceDataFromSchema,
95
- userSession,
96
- })
88
+ const { channelConfig, openingData, meta, httpMiddleware } =
89
+ await openChannel({
90
+ channelId,
91
+ createSessionServices,
92
+ request,
93
+ route,
94
+ singletonServices,
95
+ coerceDataFromSchema,
96
+ userSession,
97
+ })
97
98
 
98
99
  const main = async () => {
99
100
  try {
@@ -146,9 +147,10 @@ export const runChannelConnect = async ({
146
147
  userSession,
147
148
  },
148
149
  { http },
149
- combineMiddleware({
150
+ combineMiddleware(PikkuWiringTypes.channel, channelConfig.name, {
150
151
  wiringMiddleware: channelConfig.middleware,
151
152
  wiringTags: channelConfig.tags,
153
+ httpMiddleware,
152
154
  }),
153
155
  main
154
156
  )
@@ -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,