@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
@@ -1,9 +1,10 @@
1
+ import { PikkuWiringTypes, } from '../../types/core.types.js';
1
2
  import { getErrorResponse, PikkuError } from '../../errors/error-handler.js';
2
3
  import { closeSessionServices } from '../../utils.js';
3
4
  import { pikkuState } from '../../pikku-state.js';
4
5
  import { addFunction, runPikkuFunc } from '../../function/function-runner.js';
5
6
  import { rpcService } from '../rpc/rpc-runner.js';
6
- import { addMiddlewareForTags, runMiddleware } from '../../middleware-runner.js';
7
+ import { combineMiddleware, runMiddleware } from '../../middleware-runner.js';
7
8
  export const wireScheduler = (scheduledTask) => {
8
9
  const meta = pikkuState('scheduler', 'meta');
9
10
  const taskMeta = meta[scheduledTask.name];
@@ -63,15 +64,20 @@ export async function runScheduledTask({ name, session, singletonServices, creat
63
64
  }
64
65
  return singletonServices;
65
66
  };
66
- result = await runPikkuFunc(meta.pikkuFuncName, {
67
+ result = await runPikkuFunc(PikkuWiringTypes.scheduler, meta.name, meta.pikkuFuncName, {
67
68
  getAllServices,
68
69
  session,
69
70
  data: undefined,
70
71
  tags: task.tags,
71
72
  });
72
73
  };
73
- // Get middleware for tags and run middleware
74
- await runMiddleware(singletonServices, { scheduledTask }, addMiddlewareForTags(task.middleware, task.tags), runMain);
74
+ const funcConfig = pikkuState('function', 'functions').get(meta.pikkuFuncName);
75
+ await runMiddleware(singletonServices, { scheduledTask }, combineMiddleware(PikkuWiringTypes.scheduler, meta.name, {
76
+ wiringMiddleware: task.middleware,
77
+ wiringTags: task.tags,
78
+ funcMiddleware: funcConfig?.middleware,
79
+ funcTags: funcConfig?.tags,
80
+ }), runMain);
75
81
  return result;
76
82
  }
77
83
  catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/core",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "module": "dist/index.js",
@@ -0,0 +1,37 @@
1
+ import { test } from 'node:test'
2
+ import assert from 'node:assert'
3
+ import { pikkuPermission, pikkuMiddleware } from './index.js'
4
+
5
+ test('pikkuPermission factory function', async () => {
6
+ const permission = pikkuPermission(async ({ logger }, data, session) => {
7
+ return true
8
+ })
9
+
10
+ assert.strictEqual(typeof permission, 'function')
11
+ assert.strictEqual(permission.length, 3) // services, data, session
12
+ })
13
+
14
+ test('pikkuMiddleware factory function', async () => {
15
+ const middleware = pikkuMiddleware(async ({ logger }, interactions, next) => {
16
+ await next()
17
+ })
18
+
19
+ assert.strictEqual(typeof middleware, 'function')
20
+ assert.strictEqual(middleware.length, 3) // services, interactions, next
21
+ })
22
+
23
+ test('pikkuPermission returns the same function', async () => {
24
+ const originalFn = async ({ logger }, data, session) => true
25
+ const wrappedFn = pikkuPermission(originalFn)
26
+
27
+ assert.strictEqual(wrappedFn, originalFn)
28
+ })
29
+
30
+ test('pikkuMiddleware returns the same function', async () => {
31
+ const originalFn = async ({ logger }, interactions, next) => {
32
+ await next()
33
+ }
34
+ const wrappedFn = pikkuMiddleware(originalFn)
35
+
36
+ assert.strictEqual(wrappedFn, originalFn)
37
+ })
@@ -0,0 +1,499 @@
1
+ import { describe, test, beforeEach } from 'node:test'
2
+ import * as assert from 'node:assert'
3
+ import { addFunction, runPikkuFunc } from './function-runner.js'
4
+ import { addMiddleware, addPermission } from '../index.js'
5
+ import { resetPikkuState, pikkuState } from '../pikku-state.js'
6
+ import {
7
+ CoreServices,
8
+ CorePikkuMiddleware,
9
+ PikkuWiringTypes,
10
+ } from '../types/core.types.js'
11
+ import { CorePermissionGroup } from './functions.types.js'
12
+
13
+ beforeEach(() => {
14
+ resetPikkuState()
15
+ })
16
+
17
+ // Helper function to add function with metadata for tests
18
+ const addTestFunction = (funcName: string, funcConfig: any) => {
19
+ addFunction(funcName, funcConfig)
20
+ pikkuState('function', 'meta')[funcName] = {
21
+ pikkuFuncName: funcName,
22
+ inputSchemaName: null,
23
+ outputSchemaName: null,
24
+ }
25
+ }
26
+
27
+ const mockServices: CoreServices = {
28
+ logger: {
29
+ info: () => {},
30
+ warn: () => {},
31
+ error: () => {},
32
+ debug: () => {},
33
+ },
34
+ } as any
35
+
36
+ describe('runPikkuFunc - Integration Tests', () => {
37
+ test('should execute middleware in correct order: wiringTags → wiringMiddleware → funcMiddleware → funcTags', async () => {
38
+ const executionOrder: string[] = []
39
+ const createMiddleware = (name: string): CorePikkuMiddleware => {
40
+ return async (services, interaction, next) => {
41
+ executionOrder.push(name)
42
+ await next()
43
+ }
44
+ }
45
+
46
+ addMiddleware('wiringTag', [createMiddleware('wiringTag')])
47
+ addMiddleware('funcTag', [createMiddleware('funcTag')])
48
+
49
+ // Register function with middleware and tags
50
+ addTestFunction('testFunc', {
51
+ func: async () => {
52
+ executionOrder.push('main')
53
+ return 'success'
54
+ },
55
+ middleware: [createMiddleware('funcMiddleware')],
56
+ tags: ['funcTag'],
57
+ })
58
+
59
+ const result = await runPikkuFunc(
60
+ PikkuWiringTypes.rpc,
61
+ Math.random().toString(),
62
+ 'testFunc',
63
+ {
64
+ getAllServices: () => mockServices,
65
+ data: {},
66
+ middleware: [createMiddleware('wiringMiddleware')],
67
+ tags: ['wiringTag'],
68
+ }
69
+ )
70
+
71
+ assert.equal(result, 'success')
72
+ // Order: wiringTags, wiringMiddleware, funcMiddleware, funcTags
73
+ assert.deepEqual(executionOrder, [
74
+ 'wiringTag',
75
+ 'wiringMiddleware',
76
+ 'funcMiddleware',
77
+ 'funcTag',
78
+ 'main',
79
+ ])
80
+ })
81
+
82
+ test('should execute permissions in correct order: wiringTags → wiringPermissions → funcTags → funcPermissions', async () => {
83
+ const executionOrder: string[] = []
84
+
85
+ // Setup tagged permissions
86
+ const wiringTagPermission = async () => {
87
+ executionOrder.push('wiringTag')
88
+ return true
89
+ }
90
+ const funcTagPermission = async () => {
91
+ executionOrder.push('funcTag')
92
+ return true
93
+ }
94
+
95
+ addPermission('wiringTag', [wiringTagPermission])
96
+ addPermission('funcTag', [funcTagPermission])
97
+
98
+ // Setup direct permissions
99
+ const wiringPermissions: CorePermissionGroup = {
100
+ permissions: [
101
+ async () => {
102
+ executionOrder.push('wiringPermission')
103
+ return true
104
+ },
105
+ ],
106
+ }
107
+ const funcPermissions: CorePermissionGroup = {
108
+ permissions: [
109
+ async () => {
110
+ executionOrder.push('funcPermission')
111
+ return true
112
+ },
113
+ ],
114
+ }
115
+
116
+ // Register function with permissions and tags
117
+ addTestFunction('testFunc', {
118
+ func: async () => {
119
+ executionOrder.push('main')
120
+ return 'success'
121
+ },
122
+ permissions: funcPermissions,
123
+ tags: ['funcTag'],
124
+ })
125
+
126
+ const result = await runPikkuFunc(
127
+ PikkuWiringTypes.rpc,
128
+ Math.random().toString(),
129
+ 'testFunc',
130
+ {
131
+ getAllServices: () => mockServices,
132
+ data: {},
133
+ permissions: wiringPermissions,
134
+ tags: ['wiringTag'],
135
+ }
136
+ )
137
+
138
+ assert.equal(result, 'success')
139
+ // Order: wiringTags → wiringPermissions → funcTags → funcPermissions
140
+ assert.deepEqual(executionOrder, [
141
+ 'wiringTag',
142
+ 'wiringPermission',
143
+ 'funcTag',
144
+ 'funcPermission',
145
+ 'main',
146
+ ])
147
+ })
148
+
149
+ test('should throw specific error for wiring tag permission failures', async () => {
150
+ const failingWiringTagPermission = async () => false
151
+
152
+ addPermission('wiringTag', [failingWiringTagPermission])
153
+
154
+ addTestFunction('testFunc', {
155
+ func: async () => 'success',
156
+ })
157
+
158
+ await assert.rejects(
159
+ runPikkuFunc(PikkuWiringTypes.rpc, Math.random().toString(), 'testFunc', {
160
+ getAllServices: () => mockServices,
161
+ data: {},
162
+ tags: ['wiringTag'],
163
+ }),
164
+ {
165
+ message: 'Permission denied',
166
+ }
167
+ )
168
+ })
169
+
170
+ test('should throw specific error for wiring permission failures', async () => {
171
+ const wiringPermissions: CorePermissionGroup = {
172
+ permissions: [async () => false],
173
+ }
174
+
175
+ addTestFunction('testFunc', {
176
+ func: async () => 'success',
177
+ })
178
+
179
+ await assert.rejects(
180
+ runPikkuFunc(PikkuWiringTypes.rpc, Math.random().toString(), 'testFunc', {
181
+ getAllServices: () => mockServices,
182
+ data: {},
183
+ permissions: wiringPermissions,
184
+ }),
185
+ {
186
+ message: 'Permission denied',
187
+ }
188
+ )
189
+ })
190
+
191
+ test('should throw specific error for function tag permission failures', async () => {
192
+ const failingFuncTagPermission = async () => false
193
+
194
+ addPermission('funcTag', [failingFuncTagPermission])
195
+
196
+ addTestFunction('testFunc', {
197
+ func: async () => 'success',
198
+ tags: ['funcTag'],
199
+ })
200
+
201
+ await assert.rejects(
202
+ runPikkuFunc(PikkuWiringTypes.rpc, Math.random().toString(), 'testFunc', {
203
+ getAllServices: () => mockServices,
204
+ data: {},
205
+ }),
206
+ {
207
+ message: 'Permission denied',
208
+ }
209
+ )
210
+ })
211
+
212
+ test('should throw specific error for function permission failures', async () => {
213
+ const funcPermissions: CorePermissionGroup = {
214
+ permissions: [async () => false],
215
+ }
216
+
217
+ addTestFunction('testFunc', {
218
+ func: async () => 'success',
219
+ permissions: funcPermissions,
220
+ })
221
+
222
+ await assert.rejects(
223
+ runPikkuFunc(PikkuWiringTypes.rpc, Math.random().toString(), 'testFunc', {
224
+ getAllServices: () => mockServices,
225
+ data: {},
226
+ }),
227
+ {
228
+ message: 'Permission denied',
229
+ }
230
+ )
231
+ })
232
+
233
+ test('should deduplicate middleware when same middleware appears in tags and direct', async () => {
234
+ let executionCount = 0
235
+
236
+ const duplicatedMiddleware: CorePikkuMiddleware = async (
237
+ services,
238
+ interaction,
239
+ next
240
+ ) => {
241
+ executionCount++
242
+ await next()
243
+ }
244
+
245
+ // Add same middleware to tag and directly
246
+ addMiddleware('testTag', [duplicatedMiddleware])
247
+
248
+ addTestFunction('testFunc', {
249
+ func: async () => 'success',
250
+ middleware: [duplicatedMiddleware], // Same middleware directly
251
+ tags: ['testTag'], // Same middleware via tag
252
+ })
253
+
254
+ await runPikkuFunc(
255
+ PikkuWiringTypes.rpc,
256
+ Math.random().toString(),
257
+ 'testFunc',
258
+ {
259
+ getAllServices: () => mockServices,
260
+ data: {},
261
+ }
262
+ )
263
+
264
+ // Should only execute once due to deduplication
265
+ assert.equal(executionCount, 1)
266
+ })
267
+
268
+ test('should handle mixed permission types correctly', async () => {
269
+ const executionOrder: string[] = []
270
+
271
+ // Mix of array and object permissions
272
+ const arrayPermission = [
273
+ async () => {
274
+ executionOrder.push('arrayPermission1')
275
+ return false
276
+ },
277
+ async () => {
278
+ executionOrder.push('arrayPermission2')
279
+ return true
280
+ },
281
+ ]
282
+
283
+ const objectPermission: CorePermissionGroup = {
284
+ permissions: [
285
+ async () => {
286
+ executionOrder.push('objectPermission')
287
+ return true
288
+ },
289
+ ],
290
+ }
291
+
292
+ addPermission('mixedTag', arrayPermission)
293
+
294
+ addTestFunction('testFunc', {
295
+ func: async () => {
296
+ executionOrder.push('main')
297
+ return 'success'
298
+ },
299
+ permissions: objectPermission,
300
+ tags: ['mixedTag'],
301
+ })
302
+
303
+ const result = await runPikkuFunc(
304
+ PikkuWiringTypes.rpc,
305
+ Math.random().toString(),
306
+ 'testFunc',
307
+ {
308
+ getAllServices: () => mockServices,
309
+ data: {},
310
+ }
311
+ )
312
+
313
+ assert.equal(result, 'success')
314
+ // Should execute tag permissions first, then function permissions
315
+ assert.deepEqual(executionOrder, [
316
+ 'arrayPermission1',
317
+ 'arrayPermission2',
318
+ 'objectPermission',
319
+ 'main',
320
+ ])
321
+ })
322
+
323
+ test('should work without any middleware or permissions', async () => {
324
+ addTestFunction('simpleFunc', {
325
+ func: async () => 'simple success',
326
+ })
327
+
328
+ const result = await runPikkuFunc(
329
+ PikkuWiringTypes.rpc,
330
+ Math.random().toString(),
331
+ 'simpleFunc',
332
+ {
333
+ getAllServices: () => mockServices,
334
+ data: {},
335
+ }
336
+ )
337
+
338
+ assert.equal(result, 'simple success')
339
+ })
340
+
341
+ test('should work with only wiring-level middleware and permissions', async () => {
342
+ const executionOrder: string[] = []
343
+
344
+ const wiringMiddleware: CorePikkuMiddleware = async (
345
+ services,
346
+ interaction,
347
+ next
348
+ ) => {
349
+ executionOrder.push('wiringMiddleware')
350
+ await next()
351
+ }
352
+
353
+ const wiringPermissions: CorePermissionGroup = {
354
+ permissions: [
355
+ async () => {
356
+ executionOrder.push('wiringPermission')
357
+ return true
358
+ },
359
+ ],
360
+ }
361
+
362
+ addTestFunction('testFunc', {
363
+ func: async () => {
364
+ executionOrder.push('main')
365
+ return 'success'
366
+ },
367
+ })
368
+
369
+ const result = await runPikkuFunc(
370
+ PikkuWiringTypes.rpc,
371
+ Math.random().toString(),
372
+ 'testFunc',
373
+ {
374
+ getAllServices: () => mockServices,
375
+ data: {},
376
+ middleware: [wiringMiddleware],
377
+ permissions: wiringPermissions,
378
+ }
379
+ )
380
+
381
+ assert.equal(result, 'success')
382
+ assert.deepEqual(executionOrder, [
383
+ 'wiringPermission',
384
+ 'wiringMiddleware',
385
+ 'main',
386
+ ])
387
+ })
388
+
389
+ test('should work with only function-level middleware and permissions', async () => {
390
+ const executionOrder: string[] = []
391
+
392
+ const funcMiddleware: CorePikkuMiddleware = async (
393
+ services,
394
+ interaction,
395
+ next
396
+ ) => {
397
+ executionOrder.push('funcMiddleware')
398
+ await next()
399
+ }
400
+
401
+ const funcPermissions: CorePermissionGroup = {
402
+ permissions: [
403
+ async () => {
404
+ executionOrder.push('funcPermission')
405
+ return true
406
+ },
407
+ ],
408
+ }
409
+
410
+ addTestFunction('testFunc', {
411
+ func: async () => {
412
+ executionOrder.push('main')
413
+ return 'success'
414
+ },
415
+ middleware: [funcMiddleware],
416
+ permissions: funcPermissions,
417
+ })
418
+
419
+ const result = await runPikkuFunc(
420
+ PikkuWiringTypes.rpc,
421
+ Math.random().toString(),
422
+ 'testFunc',
423
+ {
424
+ getAllServices: () => mockServices,
425
+ data: {},
426
+ }
427
+ )
428
+
429
+ assert.equal(result, 'success')
430
+ assert.deepEqual(executionOrder, [
431
+ 'funcPermission',
432
+ 'funcMiddleware',
433
+ 'main',
434
+ ])
435
+ })
436
+
437
+ test('should pass correct parameters to function', async () => {
438
+ let receivedServices: any
439
+ let receivedData: any
440
+ let receivedSession: any
441
+
442
+ const testData = { test: 'data' }
443
+ const testSession = { userId: 'test-user' }
444
+
445
+ addTestFunction('testFunc', {
446
+ func: async (services, data, session) => {
447
+ receivedServices = services
448
+ receivedData = data
449
+ receivedSession = session
450
+ return 'success'
451
+ },
452
+ })
453
+
454
+ await runPikkuFunc(
455
+ PikkuWiringTypes.rpc,
456
+ Math.random().toString(),
457
+ 'testFunc',
458
+ {
459
+ getAllServices: () => mockServices,
460
+ data: testData,
461
+ session: testSession,
462
+ }
463
+ )
464
+
465
+ assert.equal(receivedServices, mockServices)
466
+ assert.equal(receivedData, testData)
467
+ assert.equal(receivedSession, testSession)
468
+ })
469
+
470
+ test('should handle async getAllServices function', async () => {
471
+ let servicesProvided: any
472
+
473
+ addTestFunction('testFunc', {
474
+ func: async (services) => {
475
+ servicesProvided = services
476
+ return 'success'
477
+ },
478
+ })
479
+
480
+ const asyncGetServices = async () => {
481
+ // Simulate async service creation
482
+ await new Promise((resolve) => setTimeout(resolve, 1))
483
+ return mockServices
484
+ }
485
+
486
+ const result = await runPikkuFunc(
487
+ PikkuWiringTypes.rpc,
488
+ Math.random().toString(),
489
+ 'testFunc',
490
+ {
491
+ getAllServices: asyncGetServices,
492
+ data: {},
493
+ }
494
+ )
495
+
496
+ assert.equal(result, 'success')
497
+ assert.equal(servicesProvided, mockServices)
498
+ })
499
+ })
@@ -1,12 +1,13 @@
1
1
  import { ForbiddenError } from '../errors/errors.js'
2
- import { runMiddleware } from '../middleware-runner.js'
3
- import { verifyPermissions, getPermissionsForTags } from '../permissions.js'
2
+ import { runMiddleware, combineMiddleware } from '../middleware-runner.js'
3
+ import { runPermissions } from '../permissions.js'
4
4
  import { pikkuState } from '../pikku-state.js'
5
5
  import { coerceTopLevelDataFromSchema, validateSchema } from '../schema.js'
6
6
  import {
7
7
  CoreServices,
8
8
  CoreUserSession,
9
9
  CorePikkuMiddleware,
10
+ PikkuWiringTypes,
10
11
  } from '../types/core.types.js'
11
12
  import {
12
13
  CorePermissionGroup,
@@ -42,13 +43,15 @@ export const runPikkuFuncDirectly = async <In, Out>(
42
43
  }
43
44
 
44
45
  export const runPikkuFunc = async <In = any, Out = any>(
46
+ wireType: PikkuWiringTypes,
47
+ wireId: string,
45
48
  funcName: string,
46
49
  {
47
50
  getAllServices,
48
51
  data,
49
52
  session,
50
- permissions: transportPermissions,
51
- middleware: transportMiddleware,
53
+ permissions: wiringPermissions,
54
+ middleware: wiringMiddleware,
52
55
  coerceDataFromSchema,
53
56
  tags = [],
54
57
  }: {
@@ -93,46 +96,26 @@ export const runPikkuFunc = async <In = any, Out = any>(
93
96
  }
94
97
  }
95
98
 
96
- let permissioned = true
97
-
98
- // Check tagged permissions first - ALL must pass
99
- const taggedPermissions = getPermissionsForTags([
100
- ...tags,
101
- ...(funcConfig.tags || []),
102
- ])
103
- if (taggedPermissions.length > 0) {
104
- const taggedResults = await Promise.all(
105
- taggedPermissions.map((permission) =>
106
- permission(allServices, data, session)
107
- )
108
- )
109
- permissioned = taggedResults.every((result) => result)
110
- }
111
-
112
- // Only check function permissions if tagged permissions passed
113
- if (permissioned && funcConfig.permissions) {
114
- permissioned = await verifyPermissions(
115
- funcConfig.permissions,
116
- allServices,
117
- data,
118
- session
119
- )
120
- }
121
-
122
- if (!permissioned && transportPermissions) {
123
- permissioned = await verifyPermissions(
124
- transportPermissions,
125
- allServices,
126
- data,
127
- session
128
- )
129
- }
99
+ // Run permission checks in the specified order
100
+ await runPermissions(wireType, wireId, {
101
+ wiringTags: tags,
102
+ wiringPermissions,
103
+ funcTags: funcConfig.tags,
104
+ funcPermissions: funcConfig.permissions,
105
+ allServices,
106
+ data,
107
+ session,
108
+ })
130
109
 
131
- if (permissioned === false) {
132
- throw new ForbiddenError('Permission denied')
133
- }
110
+ // Combine all middleware: wiring tags → wiring middleware → func middleware → func tags
111
+ const allMiddleware = combineMiddleware(wireType, wireId, {
112
+ wiringTags: tags,
113
+ wiringMiddleware,
114
+ funcMiddleware: funcConfig.middleware,
115
+ funcTags: funcConfig.tags,
116
+ })
134
117
 
135
- if (transportMiddleware || funcConfig.middleware) {
118
+ if (allMiddleware.length > 0) {
136
119
  return (await runMiddleware<CorePikkuMiddleware>(
137
120
  allServices,
138
121
  {
@@ -140,7 +123,7 @@ export const runPikkuFunc = async <In = any, Out = any>(
140
123
  mcp: allServices.mcp,
141
124
  rpc: allServices.rpc,
142
125
  },
143
- [...(transportMiddleware || []), ...(funcConfig.middleware || [])],
126
+ allMiddleware,
144
127
  async () => await funcConfig.func(allServices, data, session!)
145
128
  )) as Out
146
129
  }
@@ -74,6 +74,19 @@ export type CorePikkuPermission<
74
74
  Session extends CoreUserSession = CoreUserSession,
75
75
  > = (services: Services, data: In, session?: Session) => Promise<boolean>
76
76
 
77
+ /**
78
+ * Factory function for creating permissions with tree-shaking support
79
+ */
80
+ export const pikkuPermission = <
81
+ In = any,
82
+ Services extends CoreSingletonServices = CoreServices,
83
+ Session extends CoreUserSession = CoreUserSession,
84
+ >(
85
+ permission: CorePikkuPermission<In, Services, Session>
86
+ ): CorePikkuPermission<In, Services, Session> => {
87
+ return permission
88
+ }
89
+
77
90
  export type CorePermissionGroup<PikkuPermission = CorePikkuPermission<any>> =
78
91
  | Record<string, PikkuPermission | PikkuPermission[]>
79
92
  | undefined