@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
package/src/index.ts CHANGED
@@ -18,15 +18,11 @@ export * from './wirings/queue/index.js'
18
18
  export * from './wirings/mcp/index.js'
19
19
  export * from './errors/index.js'
20
20
  export * from './middleware/index.js'
21
- export * from './time-utils.js'
22
21
  export * from './utils.js'
22
+ export * from './time-utils.js'
23
23
  export { pikkuState } from './pikku-state.js'
24
- export {
25
- runMiddleware,
26
- addMiddleware,
27
- addMiddlewareForTags,
28
- } from './middleware-runner.js'
29
- export { addPermission, getPermissionsForTags } from './permissions.js'
24
+ export { addMiddleware, runMiddleware } from './middleware-runner.js'
25
+ export { addPermission } from './permissions.js'
30
26
  export { wireHTTP, addHTTPMiddleware } from './wirings/http/http-runner.js'
31
27
  export { wireChannel } from './wirings/channel/channel-runner.js'
32
28
  export { wireScheduler } from './wirings/scheduler/scheduler-runner.js'
@@ -0,0 +1,357 @@
1
+ import { describe, test, beforeEach } from 'node:test'
2
+ import assert from 'node:assert'
3
+ import {
4
+ combineMiddleware,
5
+ addMiddleware,
6
+ runMiddleware,
7
+ } from './middleware-runner.js'
8
+ import { resetPikkuState } from './pikku-state.js'
9
+ import { CorePikkuMiddleware, PikkuWiringTypes } from './types/core.types.js'
10
+
11
+ beforeEach(() => {
12
+ resetPikkuState()
13
+ })
14
+
15
+ describe('combineMiddleware', () => {
16
+ test('should return empty array when no parameters provided', () => {
17
+ const result = combineMiddleware(
18
+ PikkuWiringTypes.http,
19
+ Math.random().toString()
20
+ )
21
+ assert.deepEqual(result, [])
22
+ })
23
+
24
+ test('should return empty array when all parameters are undefined', () => {
25
+ const result = combineMiddleware(
26
+ PikkuWiringTypes.http,
27
+ Math.random().toString(),
28
+ {
29
+ wiringMiddleware: undefined,
30
+ wiringTags: undefined,
31
+ funcMiddleware: undefined,
32
+ funcTags: undefined,
33
+ }
34
+ )
35
+ assert.deepEqual(result, [])
36
+ })
37
+
38
+ test('should combine wiring middleware only', () => {
39
+ const mockMiddleware1: CorePikkuMiddleware = async (
40
+ services,
41
+ interaction,
42
+ next
43
+ ) => {
44
+ await next()
45
+ }
46
+ const mockMiddleware2: CorePikkuMiddleware = async (
47
+ services,
48
+ interaction,
49
+ next
50
+ ) => {
51
+ await next()
52
+ }
53
+
54
+ const result = combineMiddleware(
55
+ PikkuWiringTypes.http,
56
+ Math.random().toString(),
57
+ {
58
+ wiringMiddleware: [mockMiddleware1, mockMiddleware2],
59
+ }
60
+ )
61
+
62
+ assert.equal(result.length, 2)
63
+ assert.equal(result[0], mockMiddleware1)
64
+ assert.equal(result[1], mockMiddleware2)
65
+ })
66
+
67
+ test('should combine function middleware only', () => {
68
+ const mockMiddleware1: CorePikkuMiddleware = async (
69
+ services,
70
+ interaction,
71
+ next
72
+ ) => {
73
+ await next()
74
+ }
75
+ const mockMiddleware2: CorePikkuMiddleware = async (
76
+ services,
77
+ interaction,
78
+ next
79
+ ) => {
80
+ await next()
81
+ }
82
+
83
+ const result = combineMiddleware(
84
+ PikkuWiringTypes.http,
85
+ Math.random().toString(),
86
+ {
87
+ funcMiddleware: [mockMiddleware1, mockMiddleware2],
88
+ }
89
+ )
90
+
91
+ assert.equal(result.length, 2)
92
+ assert.equal(result[0], mockMiddleware1)
93
+ assert.equal(result[1], mockMiddleware2)
94
+ })
95
+
96
+ test('should execute middleware in correct order: wiringTags → wiringMiddleware → funcMiddleware → funcTags', () => {
97
+ // Setup tagged middleware
98
+ const wiringTagMiddleware: CorePikkuMiddleware = async (
99
+ services,
100
+ interaction,
101
+ next
102
+ ) => {
103
+ await next()
104
+ }
105
+ const funcTagMiddleware: CorePikkuMiddleware = async (
106
+ services,
107
+ interaction,
108
+ next
109
+ ) => {
110
+ await next()
111
+ }
112
+
113
+ addMiddleware('wiringTag', [wiringTagMiddleware])
114
+ addMiddleware('funcTag', [funcTagMiddleware])
115
+
116
+ const wiringMiddleware: CorePikkuMiddleware = async (
117
+ services,
118
+ interaction,
119
+ next
120
+ ) => {
121
+ await next()
122
+ }
123
+ const funcMiddleware: CorePikkuMiddleware = async (
124
+ services,
125
+ interaction,
126
+ next
127
+ ) => {
128
+ await next()
129
+ }
130
+
131
+ const result = combineMiddleware(
132
+ PikkuWiringTypes.http,
133
+ Math.random().toString(),
134
+ {
135
+ wiringMiddleware: [wiringMiddleware],
136
+ wiringTags: ['wiringTag'],
137
+ funcMiddleware: [funcMiddleware],
138
+ funcTags: ['funcTag'],
139
+ }
140
+ )
141
+
142
+ assert.equal(result.length, 4)
143
+ // Order: wiringTags, wiringMiddleware, funcMiddleware, funcTags
144
+ assert.equal(result[0], wiringTagMiddleware) // wiringTags first
145
+ assert.equal(result[1], wiringMiddleware) // wiringMiddleware second
146
+ assert.equal(result[2], funcMiddleware) // funcMiddleware third
147
+ assert.equal(result[3], funcTagMiddleware) // funcTags last
148
+ })
149
+
150
+ test('should handle wiring tags only', () => {
151
+ const taggedMiddleware: CorePikkuMiddleware = async (
152
+ services,
153
+ interaction,
154
+ next
155
+ ) => {
156
+ await next()
157
+ }
158
+
159
+ addMiddleware('testTag', [taggedMiddleware])
160
+
161
+ const result = combineMiddleware(
162
+ PikkuWiringTypes.http,
163
+ Math.random().toString(),
164
+ {
165
+ wiringTags: ['testTag'],
166
+ }
167
+ )
168
+
169
+ assert.equal(result.length, 1)
170
+ assert.equal(result[0], taggedMiddleware)
171
+ })
172
+
173
+ test('should handle function tags only', () => {
174
+ const taggedMiddleware: CorePikkuMiddleware = async (
175
+ services,
176
+ interaction,
177
+ next
178
+ ) => {
179
+ await next()
180
+ }
181
+
182
+ addMiddleware('funcTestTag', [taggedMiddleware])
183
+
184
+ const result = combineMiddleware(
185
+ PikkuWiringTypes.http,
186
+ Math.random().toString(),
187
+ {
188
+ funcTags: ['funcTestTag'],
189
+ }
190
+ )
191
+
192
+ assert.equal(result.length, 1)
193
+ assert.equal(result[0], taggedMiddleware)
194
+ })
195
+
196
+ test('should handle multiple tags', () => {
197
+ const middleware1: CorePikkuMiddleware = async (
198
+ services,
199
+ interaction,
200
+ next
201
+ ) => {
202
+ await next()
203
+ }
204
+ const middleware2: CorePikkuMiddleware = async (
205
+ services,
206
+ interaction,
207
+ next
208
+ ) => {
209
+ await next()
210
+ }
211
+
212
+ addMiddleware('tag1', [middleware1])
213
+ addMiddleware('tag2', [middleware2])
214
+
215
+ const result = combineMiddleware(
216
+ PikkuWiringTypes.http,
217
+ Math.random().toString(),
218
+ {
219
+ wiringTags: ['tag1', 'tag2'],
220
+ }
221
+ )
222
+
223
+ assert.equal(result.length, 2)
224
+ assert.equal(result[0], middleware1)
225
+ assert.equal(result[1], middleware2)
226
+ })
227
+
228
+ test('should ignore non-existent tags', () => {
229
+ const middleware: CorePikkuMiddleware = async (
230
+ services,
231
+ interaction,
232
+ next
233
+ ) => {
234
+ await next()
235
+ }
236
+
237
+ addMiddleware('existingTag', [middleware])
238
+
239
+ const result = combineMiddleware(
240
+ PikkuWiringTypes.http,
241
+ Math.random().toString(),
242
+ {
243
+ wiringTags: ['existingTag', 'nonExistentTag'],
244
+ funcTags: ['anotherNonExistentTag'],
245
+ }
246
+ )
247
+
248
+ assert.equal(result.length, 1)
249
+ assert.equal(result[0], middleware)
250
+ })
251
+ })
252
+
253
+ describe('runMiddleware', () => {
254
+ test('should deduplicate middleware using Set', async () => {
255
+ const executionOrder: string[] = []
256
+
257
+ const middleware1: CorePikkuMiddleware = async (
258
+ services,
259
+ interaction,
260
+ next
261
+ ) => {
262
+ executionOrder.push('middleware1')
263
+ await next()
264
+ }
265
+
266
+ const middleware2: CorePikkuMiddleware = async (
267
+ services,
268
+ interaction,
269
+ next
270
+ ) => {
271
+ executionOrder.push('middleware2')
272
+ await next()
273
+ }
274
+
275
+ await runMiddleware(
276
+ {} as any,
277
+ {} as any,
278
+ combineMiddleware(PikkuWiringTypes.rpc, Math.random().toString(), {
279
+ wiringMiddleware: [middleware1, middleware2, middleware1, middleware2],
280
+ }),
281
+ async () => {
282
+ executionOrder.push('main')
283
+ }
284
+ )
285
+
286
+ // Should only execute each middleware once
287
+ assert.deepEqual(executionOrder, ['middleware1', 'middleware2', 'main'])
288
+ })
289
+
290
+ test('should execute middleware in correct order', async () => {
291
+ const executionOrder: string[] = []
292
+
293
+ const middleware1: CorePikkuMiddleware = async (
294
+ services,
295
+ interaction,
296
+ next
297
+ ) => {
298
+ executionOrder.push('start1')
299
+ await next()
300
+ executionOrder.push('end1')
301
+ }
302
+
303
+ const middleware2: CorePikkuMiddleware = async (
304
+ services,
305
+ interaction,
306
+ next
307
+ ) => {
308
+ executionOrder.push('start2')
309
+ await next()
310
+ executionOrder.push('end2')
311
+ }
312
+
313
+ await runMiddleware(
314
+ {} as any,
315
+ {} as any,
316
+ [middleware1, middleware2],
317
+ async () => {
318
+ executionOrder.push('main')
319
+ }
320
+ )
321
+
322
+ assert.deepEqual(executionOrder, [
323
+ 'start1',
324
+ 'start2',
325
+ 'main',
326
+ 'end2',
327
+ 'end1',
328
+ ])
329
+ })
330
+
331
+ test('should work with empty middleware array', async () => {
332
+ let mainExecuted = false
333
+
334
+ await runMiddleware({} as any, {} as any, [], async () => {
335
+ mainExecuted = true
336
+ })
337
+
338
+ assert.equal(mainExecuted, true)
339
+ })
340
+
341
+ test('should work without main function', async () => {
342
+ const executionOrder: string[] = []
343
+
344
+ const middleware: CorePikkuMiddleware = async (
345
+ services,
346
+ interaction,
347
+ next
348
+ ) => {
349
+ executionOrder.push('middleware')
350
+ await next()
351
+ }
352
+
353
+ await runMiddleware({} as any, {} as any, [middleware])
354
+
355
+ assert.deepEqual(executionOrder, ['middleware'])
356
+ })
357
+ })
@@ -3,8 +3,10 @@ import {
3
3
  CoreSingletonServices,
4
4
  PikkuInteraction,
5
5
  CorePikkuMiddleware,
6
+ PikkuWiringTypes,
6
7
  } from './types/core.types.js'
7
8
  import { pikkuState } from './pikku-state.js'
9
+ import { freezeDedupe } from './utils.js'
8
10
 
9
11
  /**
10
12
  * Runs a chain of middleware functions in sequence before executing the main function.
@@ -28,16 +30,14 @@ export const runMiddleware = async <Middleware extends CorePikkuMiddleware>(
28
30
  userSession?: UserSessionService<any>
29
31
  },
30
32
  interaction: PikkuInteraction,
31
- middlewares: Middleware[],
33
+ middlewares: readonly Middleware[],
32
34
  main?: () => Promise<unknown>
33
35
  ): Promise<unknown> => {
34
36
  // Deduplicate middleware using Set to avoid running the same middleware multiple times
35
- const uniqueMiddleware = Array.from(new Set(middlewares))
36
-
37
37
  let result: any
38
38
  const dispatch = async (index: number): Promise<any> => {
39
- if (uniqueMiddleware && index < uniqueMiddleware.length) {
40
- return await uniqueMiddleware[index]!(services as any, interaction, () =>
39
+ if (middlewares && index < middlewares.length) {
40
+ return await middlewares[index]!(services as any, interaction, () =>
41
41
  dispatch(index + 1)
42
42
  )
43
43
  } else if (main) {
@@ -124,30 +124,77 @@ export const getMiddlewareForTags = (
124
124
  return applicableMiddleware
125
125
  }
126
126
 
127
+ const middlewareCache: Record<
128
+ PikkuWiringTypes,
129
+ Record<string, readonly CorePikkuMiddleware[]>
130
+ > = {
131
+ [PikkuWiringTypes.http]: {},
132
+ [PikkuWiringTypes.rpc]: {},
133
+ [PikkuWiringTypes.channel]: {},
134
+ [PikkuWiringTypes.queue]: {},
135
+ [PikkuWiringTypes.scheduler]: {},
136
+ [PikkuWiringTypes.mcp]: {},
137
+ }
138
+
127
139
  /**
128
- * Combines tag-based middleware with wiring-specific middleware.
140
+ * Combines tag-based middleware with wiring-specific middleware and function-level middleware.
129
141
  *
130
142
  * This helper function gets middleware for tags and combines it with any
131
- * wiring-specific middleware, avoiding the need for manual spreading.
143
+ * wiring-specific middleware and function-level middleware, avoiding the need for manual spreading.
132
144
  *
133
- * @param {CorePikkuMiddleware[] | undefined} wiringMiddleware - Wiring-specific middleware.
134
- * @param {string[] | undefined} tags - Array of tags to look up middleware for.
145
+ * @param {object} options - Configuration object for combining middleware.
146
+ * @param {CorePikkuMiddleware[] | undefined} options.wiringMiddleware - Wiring-specific middleware.
147
+ * @param {string[] | undefined} options.wiringTags - Array of wiring-level tags to look up middleware for.
148
+ * @param {CorePikkuMiddleware[] | undefined} options.funcMiddleware - Function-level middleware.
149
+ * @param {string[] | undefined} options.funcTags - Array of function-level tags to look up middleware for.
135
150
  * @returns {CorePikkuMiddleware[]} Combined array of tag-based and wiring-specific middleware.
136
151
  *
137
152
  * @example
138
153
  * ```typescript
139
- * // Instead of:
140
- * const taggedMiddleware = getMiddlewareForTags(tags)
141
- * const combined = [...taggedMiddleware, ...(middleware || [])]
142
- *
143
- * // Use:
144
- * const combined = addMiddlewareForTags(middleware, tags)
154
+ * const combined = combineMiddleware({
155
+ * wiringMiddleware: httpRoute.middleware,
156
+ * wiringTags: httpRoute.tags,
157
+ * funcMiddleware: funcConfig.middleware,
158
+ * funcTags: funcConfig.tags
159
+ * })
145
160
  * ```
146
161
  */
147
- export const addMiddlewareForTags = (
148
- wiringMiddleware?: CorePikkuMiddleware[],
149
- tags?: string[]
150
- ): CorePikkuMiddleware[] => {
151
- const taggedMiddleware = getMiddlewareForTags(tags)
152
- return [...taggedMiddleware, ...(wiringMiddleware || [])]
162
+ export const combineMiddleware = (
163
+ wireType: PikkuWiringTypes,
164
+ uid: string,
165
+ {
166
+ httpMiddleware,
167
+ wiringMiddleware,
168
+ wiringTags,
169
+ funcMiddleware,
170
+ funcTags,
171
+ }: {
172
+ httpMiddleware?: CorePikkuMiddleware[]
173
+ wiringMiddleware?: CorePikkuMiddleware[]
174
+ wiringTags?: string[]
175
+ funcMiddleware?: CorePikkuMiddleware[]
176
+ funcTags?: string[]
177
+ } = {}
178
+ ): readonly CorePikkuMiddleware[] => {
179
+ if (middlewareCache[wireType][uid]) {
180
+ return middlewareCache[wireType][uid]
181
+ }
182
+
183
+ // Run middleware in specific order:
184
+ // 1) wiringTags middleware
185
+ // 2) wiringMiddleware
186
+ // 3) funcMiddleware
187
+ // 4) funcTags middleware
188
+ const wiringTaggedMiddleware = getMiddlewareForTags(wiringTags)
189
+ const funcTaggedMiddleware = getMiddlewareForTags(funcTags)
190
+
191
+ middlewareCache[wireType][uid] = freezeDedupe([
192
+ ...(httpMiddleware || []),
193
+ ...wiringTaggedMiddleware,
194
+ ...(wiringMiddleware || []),
195
+ ...(funcMiddleware || []),
196
+ ...funcTaggedMiddleware,
197
+ ]) as readonly CorePikkuMiddleware[]
198
+
199
+ return middlewareCache[wireType][uid]
153
200
  }