@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.
- package/CHANGELOG.md +28 -0
- package/dist/function/function-runner.d.ts +2 -2
- package/dist/function/function-runner.js +22 -25
- package/dist/function/functions.types.d.ts +4 -0
- package/dist/function/functions.types.js +6 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/middleware-runner.d.ts +22 -13
- package/dist/middleware-runner.js +44 -16
- package/dist/permissions.d.ts +20 -4
- package/dist/permissions.js +84 -2
- package/dist/pikku-state.d.ts +4 -6
- package/dist/pikku-state.js +6 -5
- package/dist/schema.js +8 -6
- package/dist/types/core.types.d.ts +7 -4
- package/dist/types/core.types.js +6 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +17 -0
- package/dist/wirings/channel/channel-handler.js +2 -1
- package/dist/wirings/channel/channel-runner.d.ts +2 -0
- package/dist/wirings/channel/channel-runner.js +23 -25
- package/dist/wirings/channel/local/local-channel-runner.js +9 -4
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +8 -3
- package/dist/wirings/http/http-runner.js +44 -54
- package/dist/wirings/http/http.types.d.ts +5 -5
- package/dist/wirings/http/pikku-fetch-http-request.js +3 -0
- package/dist/wirings/http/routers/http-router.d.ts +12 -0
- package/dist/wirings/http/routers/http-router.js +2 -0
- package/dist/wirings/http/routers/path-to-regex.d.ts +10 -0
- package/dist/wirings/http/routers/path-to-regex.js +118 -0
- package/dist/wirings/mcp/mcp-runner.d.ts +1 -1
- package/dist/wirings/mcp/mcp-runner.js +10 -4
- package/dist/wirings/queue/queue-runner.js +11 -3
- package/dist/wirings/rpc/rpc-runner.d.ts +1 -2
- package/dist/wirings/rpc/rpc-runner.js +2 -1
- package/dist/wirings/scheduler/scheduler-runner.d.ts +1 -1
- package/dist/wirings/scheduler/scheduler-runner.js +10 -4
- package/package.json +1 -1
- package/src/factory-functions.test.ts +37 -0
- package/src/function/function-runner.test.ts +499 -0
- package/src/function/function-runner.ts +26 -43
- package/src/function/functions.types.ts +13 -0
- package/src/index.ts +3 -7
- package/src/middleware-runner.test.ts +357 -0
- package/src/middleware-runner.ts +68 -21
- package/src/permissions.test.ts +407 -42
- package/src/permissions.ts +159 -7
- package/src/pikku-state.ts +14 -8
- package/src/schema.ts +8 -6
- package/src/types/core.types.ts +15 -4
- package/src/utils.ts +19 -0
- package/src/wirings/channel/channel-handler.ts +17 -11
- package/src/wirings/channel/channel-runner.ts +31 -25
- package/src/wirings/channel/local/local-channel-runner.test.ts +4 -0
- package/src/wirings/channel/local/local-channel-runner.ts +9 -8
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +17 -15
- package/src/wirings/http/http-runner.test.ts +18 -6
- package/src/wirings/http/http-runner.ts +64 -75
- package/src/wirings/http/http.types.ts +5 -5
- package/src/wirings/http/pikku-fetch-http-request.ts +3 -0
- package/src/wirings/http/routers/http-router.ts +15 -0
- package/src/wirings/http/routers/path-to-regex.test.ts +309 -0
- package/src/wirings/http/routers/path-to-regex.ts +162 -0
- package/src/wirings/mcp/mcp-runner.ts +25 -14
- package/src/wirings/queue/queue-runner.ts +26 -8
- package/src/wirings/rpc/rpc-runner.ts +21 -16
- package/src/wirings/scheduler/scheduler-runner.ts +27 -14
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/pikku-function.d.ts +0 -1
- package/dist/pikku-function.js +0 -1
- 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 {
|
|
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
|
-
|
|
74
|
-
await runMiddleware(singletonServices, { scheduledTask },
|
|
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
|
@@ -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 {
|
|
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:
|
|
51
|
-
middleware:
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
132
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|