@pikku/core 0.9.11 → 0.10.0
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 +14 -0
- package/dist/errors/errors.d.ts +2 -0
- package/dist/errors/errors.js +6 -0
- package/dist/function/function-runner.d.ts +15 -9
- package/dist/function/function-runner.js +55 -37
- package/dist/function/functions.types.d.ts +67 -1
- package/dist/function/functions.types.js +43 -1
- package/dist/index.d.ts +2 -6
- package/dist/index.js +2 -6
- package/dist/middleware/auth-apikey.d.ts +19 -11
- package/dist/middleware/auth-apikey.js +33 -33
- package/dist/middleware/auth-bearer.d.ts +34 -7
- package/dist/middleware/auth-bearer.js +53 -36
- package/dist/middleware/auth-cookie.d.ts +30 -12
- package/dist/middleware/auth-cookie.js +51 -43
- package/dist/middleware/timeout.d.ts +6 -2
- package/dist/middleware/timeout.js +7 -9
- package/dist/middleware-runner.d.ts +46 -43
- package/dist/middleware-runner.js +100 -69
- package/dist/permissions.d.ts +82 -20
- package/dist/permissions.js +176 -62
- package/dist/pikku-state.d.ts +46 -1
- package/dist/pikku-state.js +16 -1
- package/dist/services/content-service.d.ts +2 -3
- package/dist/services/index.d.ts +0 -1
- package/dist/services/index.js +0 -1
- package/dist/services/local-content.d.ts +2 -4
- package/dist/types/core.types.d.ts +110 -4
- package/dist/types/core.types.js +44 -1
- package/dist/utils.d.ts +1 -1
- package/dist/wirings/channel/channel-handler.d.ts +2 -2
- package/dist/wirings/channel/channel-handler.js +16 -12
- package/dist/wirings/channel/channel-runner.d.ts +2 -2
- package/dist/wirings/channel/channel-runner.js +6 -24
- package/dist/wirings/channel/channel.types.d.ts +15 -14
- package/dist/wirings/channel/local/local-channel-runner.js +12 -12
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +7 -8
- package/dist/wirings/cli/channel/cli-channel-runner.d.ts +12 -0
- package/dist/wirings/cli/channel/cli-channel-runner.js +83 -0
- package/dist/wirings/cli/channel/index.d.ts +1 -0
- package/dist/wirings/cli/channel/index.js +1 -0
- package/dist/wirings/cli/cli-runner.d.ts +42 -0
- package/dist/wirings/cli/cli-runner.js +352 -0
- package/dist/wirings/cli/cli.types.d.ts +204 -0
- package/dist/wirings/cli/cli.types.js +1 -0
- package/dist/wirings/cli/command-parser.d.ts +19 -0
- package/dist/wirings/cli/command-parser.js +403 -0
- package/dist/wirings/cli/index.d.ts +5 -0
- package/dist/wirings/cli/index.js +5 -0
- package/dist/wirings/http/http-runner.d.ts +59 -10
- package/dist/wirings/http/http-runner.js +138 -131
- package/dist/wirings/http/http.types.d.ts +11 -10
- package/dist/wirings/http/index.d.ts +1 -1
- package/dist/wirings/http/index.js +1 -1
- package/dist/wirings/http/pikku-fetch-http-response.js +3 -1
- package/dist/wirings/http/routers/http-router.d.ts +0 -2
- package/dist/wirings/http/routers/path-to-regex.d.ts +1 -1
- package/dist/wirings/http/routers/path-to-regex.js +5 -34
- package/dist/wirings/mcp/mcp-endpoint-registry.d.ts +1 -1
- package/dist/wirings/mcp/mcp-runner.d.ts +4 -5
- package/dist/wirings/mcp/mcp-runner.js +32 -34
- package/dist/wirings/mcp/mcp.types.d.ts +38 -14
- package/dist/wirings/queue/queue-runner.d.ts +2 -2
- package/dist/wirings/queue/queue-runner.js +25 -27
- package/dist/wirings/queue/queue.types.d.ts +6 -5
- package/dist/wirings/rpc/index.d.ts +1 -1
- package/dist/wirings/rpc/index.js +1 -1
- package/dist/wirings/rpc/rpc-runner.d.ts +6 -18
- package/dist/wirings/rpc/rpc-runner.js +13 -8
- package/dist/wirings/scheduler/scheduler-runner.d.ts +2 -2
- package/dist/wirings/scheduler/scheduler-runner.js +38 -35
- package/dist/wirings/scheduler/scheduler.types.d.ts +5 -4
- package/package.json +9 -7
- package/src/errors/errors.ts +6 -0
- package/src/factory-functions.test.ts +60 -1
- package/src/function/function-runner.test.ts +81 -26
- package/src/function/function-runner.ts +89 -57
- package/src/function/functions.types.ts +85 -2
- package/src/index.ts +6 -19
- package/src/middleware/auth-apikey.ts +42 -57
- package/src/middleware/auth-bearer.ts +66 -49
- package/src/middleware/auth-cookie.ts +63 -82
- package/src/middleware/timeout.ts +10 -14
- package/src/middleware-runner.test.ts +24 -16
- package/src/middleware-runner.ts +115 -87
- package/src/permissions.test.ts +26 -27
- package/src/permissions.ts +216 -104
- package/src/pikku-state.ts +79 -2
- package/src/services/content-service.ts +5 -4
- package/src/services/index.ts +0 -1
- package/src/services/local-content.ts +9 -4
- package/src/types/core.types.ts +143 -5
- package/src/utils.ts +1 -1
- package/src/wirings/channel/channel-handler.ts +18 -21
- package/src/wirings/channel/channel-runner.ts +19 -32
- package/src/wirings/channel/channel.types.ts +29 -16
- package/src/wirings/channel/local/local-channel-runner.ts +25 -15
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +15 -17
- package/src/wirings/cli/channel/cli-channel-runner.ts +123 -0
- package/src/wirings/cli/channel/index.ts +1 -0
- package/src/wirings/cli/cli-runner.test.ts +403 -0
- package/src/wirings/cli/cli-runner.ts +539 -0
- package/src/wirings/cli/cli.types.ts +350 -0
- package/src/wirings/cli/command-parser.test.ts +445 -0
- package/src/wirings/cli/command-parser.ts +504 -0
- package/src/wirings/cli/index.ts +13 -0
- package/src/wirings/http/http-runner.test.ts +8 -7
- package/src/wirings/http/http-runner.ts +165 -158
- package/src/wirings/http/http.types.ts +58 -11
- package/src/wirings/http/index.ts +7 -1
- package/src/wirings/http/pikku-fetch-http-response.ts +3 -1
- package/src/wirings/http/routers/http-router.ts +0 -2
- package/src/wirings/http/routers/path-to-regex.test.ts +4 -5
- package/src/wirings/http/routers/path-to-regex.ts +6 -43
- package/src/wirings/mcp/mcp-endpoint-registry.ts +1 -1
- package/src/wirings/mcp/mcp-runner.ts +58 -55
- package/src/wirings/mcp/mcp.types.ts +65 -14
- package/src/wirings/queue/queue-runner.ts +44 -47
- package/src/wirings/queue/queue.types.ts +10 -6
- package/src/wirings/rpc/index.ts +1 -1
- package/src/wirings/rpc/rpc-runner.ts +27 -9
- package/src/wirings/scheduler/scheduler-runner.ts +58 -56
- package/src/wirings/scheduler/scheduler.types.ts +10 -3
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { test } from 'node:test'
|
|
2
2
|
import assert from 'node:assert'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
pikkuPermission,
|
|
5
|
+
pikkuMiddleware,
|
|
6
|
+
pikkuPermissionFactory,
|
|
7
|
+
pikkuMiddlewareFactory,
|
|
8
|
+
} from './index.js'
|
|
4
9
|
|
|
5
10
|
test('pikkuPermission factory function', async () => {
|
|
6
11
|
const permission = pikkuPermission(async ({ logger }, data, session) => {
|
|
@@ -35,3 +40,57 @@ test('pikkuMiddleware returns the same function', async () => {
|
|
|
35
40
|
|
|
36
41
|
assert.strictEqual(wrappedFn, originalFn)
|
|
37
42
|
})
|
|
43
|
+
|
|
44
|
+
test('pikkuPermissionFactory creates a factory function', async () => {
|
|
45
|
+
const factory = pikkuPermissionFactory<{ role: string }>(({ role }) => {
|
|
46
|
+
return pikkuPermission(async ({ logger }, data, session) => {
|
|
47
|
+
return session?.role === role
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
assert.strictEqual(typeof factory, 'function')
|
|
52
|
+
assert.strictEqual(factory.length, 1) // input parameter
|
|
53
|
+
|
|
54
|
+
// Call the factory to get a permission
|
|
55
|
+
const permission = factory({ role: 'admin' })
|
|
56
|
+
assert.strictEqual(typeof permission, 'function')
|
|
57
|
+
assert.strictEqual(permission.length, 3) // services, data, session
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('pikkuMiddlewareFactory creates a factory function', async () => {
|
|
61
|
+
const factory = pikkuMiddlewareFactory<{ message: string }>(({ message }) => {
|
|
62
|
+
return pikkuMiddleware(async ({ logger }, interactions, next) => {
|
|
63
|
+
logger.info(message)
|
|
64
|
+
await next()
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
assert.strictEqual(typeof factory, 'function')
|
|
69
|
+
assert.strictEqual(factory.length, 1) // input parameter
|
|
70
|
+
|
|
71
|
+
// Call the factory to get middleware
|
|
72
|
+
const middleware = factory({ message: 'test' })
|
|
73
|
+
assert.strictEqual(typeof middleware, 'function')
|
|
74
|
+
assert.strictEqual(middleware.length, 3) // services, interactions, next
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('pikkuPermissionFactory returns the same factory', async () => {
|
|
78
|
+
const originalFactory = ({ role }) =>
|
|
79
|
+
pikkuPermission(async ({ logger }, data, session) => {
|
|
80
|
+
return session?.role === role
|
|
81
|
+
})
|
|
82
|
+
const wrappedFactory = pikkuPermissionFactory(originalFactory)
|
|
83
|
+
|
|
84
|
+
assert.strictEqual(wrappedFactory, originalFactory)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('pikkuMiddlewareFactory returns the same factory', async () => {
|
|
88
|
+
const originalFactory = ({ message }) =>
|
|
89
|
+
pikkuMiddleware(async ({ logger }, interactions, next) => {
|
|
90
|
+
logger.info(message)
|
|
91
|
+
await next()
|
|
92
|
+
})
|
|
93
|
+
const wrappedFactory = pikkuMiddlewareFactory(originalFactory)
|
|
94
|
+
|
|
95
|
+
assert.strictEqual(wrappedFactory, originalFactory)
|
|
96
|
+
})
|
|
@@ -17,14 +17,24 @@ beforeEach(() => {
|
|
|
17
17
|
// Helper function to add function with metadata for tests
|
|
18
18
|
const addTestFunction = (funcName: string, funcConfig: any) => {
|
|
19
19
|
addFunction(funcName, funcConfig)
|
|
20
|
+
// Convert tags to middleware metadata
|
|
21
|
+
const middleware = funcConfig.tags
|
|
22
|
+
? funcConfig.tags.map((tag: string) => ({ type: 'tag' as const, tag }))
|
|
23
|
+
: undefined
|
|
24
|
+
// Convert tags to permissions metadata
|
|
25
|
+
const permissions = funcConfig.tags
|
|
26
|
+
? funcConfig.tags.map((tag: string) => ({ type: 'tag' as const, tag }))
|
|
27
|
+
: undefined
|
|
20
28
|
pikkuState('function', 'meta')[funcName] = {
|
|
21
29
|
pikkuFuncName: funcName,
|
|
22
30
|
inputSchemaName: null,
|
|
23
31
|
outputSchemaName: null,
|
|
32
|
+
middleware,
|
|
33
|
+
permissions,
|
|
24
34
|
}
|
|
25
35
|
}
|
|
26
36
|
|
|
27
|
-
const
|
|
37
|
+
const mockSingletonServices = {
|
|
28
38
|
logger: {
|
|
29
39
|
info: () => {},
|
|
30
40
|
warn: () => {},
|
|
@@ -33,8 +43,12 @@ const mockServices: CoreServices = {
|
|
|
33
43
|
},
|
|
34
44
|
} as any
|
|
35
45
|
|
|
46
|
+
const mockServices: CoreServices = {
|
|
47
|
+
...mockSingletonServices,
|
|
48
|
+
} as any
|
|
49
|
+
|
|
36
50
|
describe('runPikkuFunc - Integration Tests', () => {
|
|
37
|
-
test('should execute middleware in correct order: wiringTags → wiringMiddleware →
|
|
51
|
+
test('should execute middleware in correct order: wiringTags → wiringMiddleware → funcTags → funcMiddleware', async () => {
|
|
38
52
|
const executionOrder: string[] = []
|
|
39
53
|
const createMiddleware = (name: string): CorePikkuMiddleware => {
|
|
40
54
|
return async (services, interaction, next) => {
|
|
@@ -61,20 +75,23 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
61
75
|
Math.random().toString(),
|
|
62
76
|
'testFunc',
|
|
63
77
|
{
|
|
78
|
+
singletonServices: mockSingletonServices,
|
|
64
79
|
getAllServices: () => mockServices,
|
|
65
|
-
data: {},
|
|
66
|
-
|
|
67
|
-
|
|
80
|
+
data: () => ({}),
|
|
81
|
+
wireMiddleware: [createMiddleware('wiringMiddleware')],
|
|
82
|
+
inheritedMiddleware: [{ type: 'tag', tag: 'wiringTag' }],
|
|
83
|
+
auth: false,
|
|
84
|
+
interaction: {},
|
|
68
85
|
}
|
|
69
86
|
)
|
|
70
87
|
|
|
71
88
|
assert.equal(result, 'success')
|
|
72
|
-
// Order:
|
|
89
|
+
// Order: wireInheritedMiddleware (tags), wireMiddleware, funcInheritedMiddleware (tags), funcMiddleware
|
|
73
90
|
assert.deepEqual(executionOrder, [
|
|
74
91
|
'wiringTag',
|
|
75
92
|
'wiringMiddleware',
|
|
76
|
-
'funcMiddleware',
|
|
77
93
|
'funcTag',
|
|
94
|
+
'funcMiddleware',
|
|
78
95
|
'main',
|
|
79
96
|
])
|
|
80
97
|
})
|
|
@@ -128,10 +145,13 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
128
145
|
Math.random().toString(),
|
|
129
146
|
'testFunc',
|
|
130
147
|
{
|
|
148
|
+
singletonServices: mockSingletonServices,
|
|
131
149
|
getAllServices: () => mockServices,
|
|
132
|
-
data: {},
|
|
133
|
-
|
|
150
|
+
data: () => ({}),
|
|
151
|
+
wirePermissions: wiringPermissions,
|
|
134
152
|
tags: ['wiringTag'],
|
|
153
|
+
auth: false,
|
|
154
|
+
interaction: {},
|
|
135
155
|
}
|
|
136
156
|
)
|
|
137
157
|
|
|
@@ -157,9 +177,12 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
157
177
|
|
|
158
178
|
await assert.rejects(
|
|
159
179
|
runPikkuFunc(PikkuWiringTypes.rpc, Math.random().toString(), 'testFunc', {
|
|
180
|
+
singletonServices: mockSingletonServices,
|
|
160
181
|
getAllServices: () => mockServices,
|
|
161
|
-
data: {},
|
|
182
|
+
data: () => ({}),
|
|
162
183
|
tags: ['wiringTag'],
|
|
184
|
+
auth: false,
|
|
185
|
+
interaction: {},
|
|
163
186
|
}),
|
|
164
187
|
{
|
|
165
188
|
message: 'Permission denied',
|
|
@@ -178,9 +201,12 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
178
201
|
|
|
179
202
|
await assert.rejects(
|
|
180
203
|
runPikkuFunc(PikkuWiringTypes.rpc, Math.random().toString(), 'testFunc', {
|
|
204
|
+
singletonServices: mockSingletonServices,
|
|
181
205
|
getAllServices: () => mockServices,
|
|
182
|
-
data: {},
|
|
183
|
-
|
|
206
|
+
data: () => ({}),
|
|
207
|
+
wirePermissions: wiringPermissions,
|
|
208
|
+
auth: false,
|
|
209
|
+
interaction: {},
|
|
184
210
|
}),
|
|
185
211
|
{
|
|
186
212
|
message: 'Permission denied',
|
|
@@ -200,8 +226,11 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
200
226
|
|
|
201
227
|
await assert.rejects(
|
|
202
228
|
runPikkuFunc(PikkuWiringTypes.rpc, Math.random().toString(), 'testFunc', {
|
|
229
|
+
singletonServices: mockSingletonServices,
|
|
203
230
|
getAllServices: () => mockServices,
|
|
204
|
-
data: {},
|
|
231
|
+
data: () => ({}),
|
|
232
|
+
auth: false,
|
|
233
|
+
interaction: {},
|
|
205
234
|
}),
|
|
206
235
|
{
|
|
207
236
|
message: 'Permission denied',
|
|
@@ -221,8 +250,11 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
221
250
|
|
|
222
251
|
await assert.rejects(
|
|
223
252
|
runPikkuFunc(PikkuWiringTypes.rpc, Math.random().toString(), 'testFunc', {
|
|
253
|
+
singletonServices: mockSingletonServices,
|
|
224
254
|
getAllServices: () => mockServices,
|
|
225
|
-
data: {},
|
|
255
|
+
data: () => ({}),
|
|
256
|
+
auth: false,
|
|
257
|
+
interaction: {},
|
|
226
258
|
}),
|
|
227
259
|
{
|
|
228
260
|
message: 'Permission denied',
|
|
@@ -256,8 +288,11 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
256
288
|
Math.random().toString(),
|
|
257
289
|
'testFunc',
|
|
258
290
|
{
|
|
291
|
+
singletonServices: mockSingletonServices,
|
|
259
292
|
getAllServices: () => mockServices,
|
|
260
|
-
data: {},
|
|
293
|
+
data: () => ({}),
|
|
294
|
+
auth: false,
|
|
295
|
+
interaction: {},
|
|
261
296
|
}
|
|
262
297
|
)
|
|
263
298
|
|
|
@@ -305,8 +340,11 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
305
340
|
Math.random().toString(),
|
|
306
341
|
'testFunc',
|
|
307
342
|
{
|
|
343
|
+
singletonServices: mockSingletonServices,
|
|
308
344
|
getAllServices: () => mockServices,
|
|
309
|
-
data: {},
|
|
345
|
+
data: () => ({}),
|
|
346
|
+
auth: false,
|
|
347
|
+
interaction: {},
|
|
310
348
|
}
|
|
311
349
|
)
|
|
312
350
|
|
|
@@ -330,8 +368,11 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
330
368
|
Math.random().toString(),
|
|
331
369
|
'simpleFunc',
|
|
332
370
|
{
|
|
371
|
+
singletonServices: mockSingletonServices,
|
|
333
372
|
getAllServices: () => mockServices,
|
|
334
|
-
data: {},
|
|
373
|
+
data: () => ({}),
|
|
374
|
+
auth: false,
|
|
375
|
+
interaction: {},
|
|
335
376
|
}
|
|
336
377
|
)
|
|
337
378
|
|
|
@@ -371,17 +412,21 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
371
412
|
Math.random().toString(),
|
|
372
413
|
'testFunc',
|
|
373
414
|
{
|
|
415
|
+
singletonServices: mockSingletonServices,
|
|
374
416
|
getAllServices: () => mockServices,
|
|
375
|
-
data: {},
|
|
376
|
-
|
|
377
|
-
|
|
417
|
+
data: () => ({}),
|
|
418
|
+
wireMiddleware: [wiringMiddleware],
|
|
419
|
+
wirePermissions: wiringPermissions,
|
|
420
|
+
auth: false,
|
|
421
|
+
interaction: {},
|
|
378
422
|
}
|
|
379
423
|
)
|
|
380
424
|
|
|
381
425
|
assert.equal(result, 'success')
|
|
426
|
+
// Permissions run after middleware (middleware can set/modify session)
|
|
382
427
|
assert.deepEqual(executionOrder, [
|
|
383
|
-
'wiringPermission',
|
|
384
428
|
'wiringMiddleware',
|
|
429
|
+
'wiringPermission',
|
|
385
430
|
'main',
|
|
386
431
|
])
|
|
387
432
|
})
|
|
@@ -421,15 +466,19 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
421
466
|
Math.random().toString(),
|
|
422
467
|
'testFunc',
|
|
423
468
|
{
|
|
469
|
+
singletonServices: mockSingletonServices,
|
|
424
470
|
getAllServices: () => mockServices,
|
|
425
|
-
data: {},
|
|
471
|
+
data: () => ({}),
|
|
472
|
+
auth: false,
|
|
473
|
+
interaction: {},
|
|
426
474
|
}
|
|
427
475
|
)
|
|
428
476
|
|
|
429
477
|
assert.equal(result, 'success')
|
|
478
|
+
// Permissions run after middleware (middleware can set/modify session)
|
|
430
479
|
assert.deepEqual(executionOrder, [
|
|
431
|
-
'funcPermission',
|
|
432
480
|
'funcMiddleware',
|
|
481
|
+
'funcPermission',
|
|
433
482
|
'main',
|
|
434
483
|
])
|
|
435
484
|
})
|
|
@@ -456,9 +505,12 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
456
505
|
Math.random().toString(),
|
|
457
506
|
'testFunc',
|
|
458
507
|
{
|
|
508
|
+
singletonServices: mockSingletonServices,
|
|
459
509
|
getAllServices: () => mockServices,
|
|
460
|
-
data: testData,
|
|
461
|
-
|
|
510
|
+
data: () => testData,
|
|
511
|
+
userSession: { get: () => testSession, set: () => {} } as any,
|
|
512
|
+
auth: false,
|
|
513
|
+
interaction: {},
|
|
462
514
|
}
|
|
463
515
|
)
|
|
464
516
|
|
|
@@ -488,8 +540,11 @@ describe('runPikkuFunc - Integration Tests', () => {
|
|
|
488
540
|
Math.random().toString(),
|
|
489
541
|
'testFunc',
|
|
490
542
|
{
|
|
543
|
+
singletonServices: mockSingletonServices,
|
|
491
544
|
getAllServices: asyncGetServices,
|
|
492
|
-
data: {},
|
|
545
|
+
data: () => ({}),
|
|
546
|
+
auth: false,
|
|
547
|
+
interaction: {},
|
|
493
548
|
}
|
|
494
549
|
)
|
|
495
550
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ForbiddenError } from '../errors/errors.js'
|
|
2
1
|
import { runMiddleware, combineMiddleware } from '../middleware-runner.js'
|
|
3
2
|
import { runPermissions } from '../permissions.js'
|
|
4
3
|
import { pikkuState } from '../pikku-state.js'
|
|
@@ -8,24 +7,23 @@ import {
|
|
|
8
7
|
CoreUserSession,
|
|
9
8
|
CorePikkuMiddleware,
|
|
10
9
|
PikkuWiringTypes,
|
|
10
|
+
CoreSingletonServices,
|
|
11
|
+
PikkuInteraction,
|
|
12
|
+
MiddlewareMetadata,
|
|
13
|
+
PermissionMetadata,
|
|
11
14
|
} from '../types/core.types.js'
|
|
12
15
|
import {
|
|
13
16
|
CorePermissionGroup,
|
|
14
|
-
CorePikkuFunction,
|
|
15
17
|
CorePikkuFunctionConfig,
|
|
16
|
-
|
|
18
|
+
CorePikkuPermission,
|
|
17
19
|
} from './functions.types.js'
|
|
20
|
+
import { UserSessionService } from '../services/user-session-service.js'
|
|
21
|
+
import { ForbiddenError } from '../errors/errors.js'
|
|
18
22
|
|
|
19
23
|
export const addFunction = (
|
|
20
24
|
funcName: string,
|
|
21
|
-
funcConfig:
|
|
22
|
-
| CorePikkuFunctionConfig<any, any>
|
|
23
|
-
| CorePikkuFunctionSessionless<any, any>
|
|
24
|
-
| CorePikkuFunction<any, any>
|
|
25
|
+
funcConfig: CorePikkuFunctionConfig<any, any>
|
|
25
26
|
) => {
|
|
26
|
-
if (funcConfig instanceof Function) {
|
|
27
|
-
funcConfig = { func: funcConfig }
|
|
28
|
-
}
|
|
29
27
|
pikkuState('function', 'functions').set(funcName, funcConfig)
|
|
30
28
|
}
|
|
31
29
|
|
|
@@ -48,20 +46,32 @@ export const runPikkuFunc = async <In = any, Out = any>(
|
|
|
48
46
|
funcName: string,
|
|
49
47
|
{
|
|
50
48
|
getAllServices,
|
|
49
|
+
singletonServices,
|
|
51
50
|
data,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
userSession,
|
|
52
|
+
auth: wiringAuth,
|
|
53
|
+
inheritedMiddleware,
|
|
54
|
+
wireMiddleware,
|
|
55
|
+
inheritedPermissions,
|
|
56
|
+
wirePermissions,
|
|
55
57
|
coerceDataFromSchema,
|
|
56
58
|
tags = [],
|
|
59
|
+
interaction,
|
|
57
60
|
}: {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
singletonServices: CoreSingletonServices
|
|
62
|
+
getAllServices: (
|
|
63
|
+
session?: CoreUserSession
|
|
64
|
+
) => Promise<CoreServices> | CoreServices
|
|
65
|
+
userSession?: UserSessionService<CoreUserSession>
|
|
66
|
+
data: () => Promise<In> | In
|
|
67
|
+
auth?: boolean
|
|
68
|
+
inheritedMiddleware?: MiddlewareMetadata[]
|
|
69
|
+
wireMiddleware?: CorePikkuMiddleware[]
|
|
70
|
+
inheritedPermissions?: PermissionMetadata[]
|
|
71
|
+
wirePermissions?: CorePermissionGroup | CorePikkuPermission[]
|
|
63
72
|
coerceDataFromSchema?: boolean
|
|
64
73
|
tags?: string[]
|
|
74
|
+
interaction: PikkuInteraction
|
|
65
75
|
}
|
|
66
76
|
): Promise<Out> => {
|
|
67
77
|
const funcConfig = pikkuState('function', 'functions').get(funcName)
|
|
@@ -73,60 +83,82 @@ export const runPikkuFunc = async <In = any, Out = any>(
|
|
|
73
83
|
throw new Error(`Function meta not found: ${funcName}`)
|
|
74
84
|
}
|
|
75
85
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
)
|
|
80
|
-
|
|
86
|
+
// Convert tags to PermissionMetadata and merge with inheritedPermissions
|
|
87
|
+
const mergedInheritedPermissions: PermissionMetadata[] = [
|
|
88
|
+
...(inheritedPermissions || []),
|
|
89
|
+
...(tags?.map((tag) => ({ type: 'tag' as const, tag })) || []),
|
|
90
|
+
]
|
|
81
91
|
|
|
82
|
-
|
|
92
|
+
// Helper function to run permissions and execute the function
|
|
93
|
+
const executeFunction = async () => {
|
|
94
|
+
const session = userSession?.get()
|
|
95
|
+
if (wiringAuth === true || funcConfig.auth === true) {
|
|
96
|
+
// This means it was explicitly enabled in either wiring or function and has to be respected
|
|
97
|
+
if (!session) {
|
|
98
|
+
throw new ForbiddenError('Authentication required')
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (wiringAuth === undefined && funcConfig.auth === undefined) {
|
|
102
|
+
// We always default to requiring auth unless explicitly disabled
|
|
103
|
+
if (!session) {
|
|
104
|
+
throw new ForbiddenError('Authentication required')
|
|
105
|
+
}
|
|
106
|
+
}
|
|
83
107
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
108
|
+
// Evaluate the data from the lazy function
|
|
109
|
+
const actualData = await data()
|
|
110
|
+
|
|
111
|
+
// Validate and coerce data if schema is defined
|
|
112
|
+
const inputSchemaName = funcMeta.inputSchemaName
|
|
113
|
+
if (inputSchemaName) {
|
|
114
|
+
// Validate request data against the defined schema, if any
|
|
115
|
+
await validateSchema(
|
|
116
|
+
singletonServices.logger,
|
|
117
|
+
singletonServices.schema,
|
|
118
|
+
inputSchemaName,
|
|
119
|
+
actualData
|
|
120
|
+
)
|
|
121
|
+
// Coerce (top level) query string parameters or date objects if specified by the schema
|
|
122
|
+
if (coerceDataFromSchema) {
|
|
123
|
+
coerceTopLevelDataFromSchema(inputSchemaName, actualData)
|
|
124
|
+
}
|
|
96
125
|
}
|
|
97
|
-
}
|
|
98
126
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
127
|
+
const allServices = await getAllServices(session)
|
|
128
|
+
|
|
129
|
+
// Run permissions check with combined permissions: inheritedPermissions (including tags) → wirePermissions → funcPermissions
|
|
130
|
+
await runPermissions(wireType, wireId, {
|
|
131
|
+
wireInheritedPermissions: mergedInheritedPermissions,
|
|
132
|
+
wirePermissions: wirePermissions,
|
|
133
|
+
funcInheritedPermissions: funcMeta.permissions,
|
|
134
|
+
funcPermissions: funcConfig.permissions,
|
|
135
|
+
allServices,
|
|
136
|
+
data: actualData,
|
|
137
|
+
session,
|
|
138
|
+
})
|
|
109
139
|
|
|
110
|
-
|
|
140
|
+
return await funcConfig.func(allServices, actualData, session!)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Combine all middleware: inheritedMiddleware → wireMiddleware → funcMiddleware
|
|
111
144
|
const allMiddleware = combineMiddleware(wireType, wireId, {
|
|
112
|
-
|
|
113
|
-
|
|
145
|
+
wireInheritedMiddleware: inheritedMiddleware,
|
|
146
|
+
wireMiddleware,
|
|
147
|
+
funcInheritedMiddleware: funcMeta.middleware,
|
|
114
148
|
funcMiddleware: funcConfig.middleware,
|
|
115
|
-
funcTags: funcConfig.tags,
|
|
116
149
|
})
|
|
117
150
|
|
|
118
151
|
if (allMiddleware.length > 0) {
|
|
119
152
|
return (await runMiddleware<CorePikkuMiddleware>(
|
|
120
|
-
allServices,
|
|
121
153
|
{
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
rpc: allServices.rpc,
|
|
154
|
+
...singletonServices,
|
|
155
|
+
userSession,
|
|
125
156
|
},
|
|
157
|
+
interaction,
|
|
126
158
|
allMiddleware,
|
|
127
|
-
|
|
159
|
+
executeFunction
|
|
128
160
|
)) as Out
|
|
129
161
|
}
|
|
130
162
|
|
|
131
|
-
return (await
|
|
163
|
+
return (await executeFunction()) as Out
|
|
132
164
|
}
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
*
|
|
13
13
|
* @template In - The input type.
|
|
14
14
|
* @template Out - The output type.
|
|
15
|
+
* @template ChannelData - The channel data type.
|
|
15
16
|
* @template Services - The services type, defaults to `CoreServices`.
|
|
16
17
|
* @template Session - The session type, defaults to `CoreUserSession`.
|
|
17
18
|
*/
|
|
@@ -39,6 +40,7 @@ export type CorePikkuFunction<
|
|
|
39
40
|
*
|
|
40
41
|
* @template In - The input type.
|
|
41
42
|
* @template Out - The output type.
|
|
43
|
+
* @template ChannelData - The channel data type.
|
|
42
44
|
* @template Services - The services type, defaults to `CoreServices`.
|
|
43
45
|
* @template Session - The session type, defaults to `CoreUserSession`.
|
|
44
46
|
*/
|
|
@@ -74,17 +76,98 @@ export type CorePikkuPermission<
|
|
|
74
76
|
Session extends CoreUserSession = CoreUserSession,
|
|
75
77
|
> = (services: Services, data: In, session?: Session) => Promise<boolean>
|
|
76
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Configuration object for creating a permission with metadata
|
|
81
|
+
*
|
|
82
|
+
* @template In - The input type.
|
|
83
|
+
* @template Services - The services type, defaults to `CoreServices`.
|
|
84
|
+
* @template Session - The session type, defaults to `CoreUserSession`.
|
|
85
|
+
*/
|
|
86
|
+
export type CorePikkuPermissionConfig<
|
|
87
|
+
In = any,
|
|
88
|
+
Services extends CoreSingletonServices = CoreServices,
|
|
89
|
+
Session extends CoreUserSession = CoreUserSession,
|
|
90
|
+
> = {
|
|
91
|
+
/** The permission function */
|
|
92
|
+
func: CorePikkuPermission<In, Services, Session>
|
|
93
|
+
/** Optional human-readable name for the permission */
|
|
94
|
+
name?: string
|
|
95
|
+
/** Optional description of what the permission checks */
|
|
96
|
+
description?: string
|
|
97
|
+
}
|
|
98
|
+
|
|
77
99
|
/**
|
|
78
100
|
* Factory function for creating permissions with tree-shaking support
|
|
101
|
+
* Supports both direct function and configuration object syntax
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* // Direct function syntax
|
|
106
|
+
* export const adminPermission = pikkuPermission(
|
|
107
|
+
* async ({ logger }, data, session) => {
|
|
108
|
+
* return session?.role === 'admin'
|
|
109
|
+
* }
|
|
110
|
+
* )
|
|
111
|
+
*
|
|
112
|
+
* // Configuration object syntax with metadata
|
|
113
|
+
* export const adminPermission = pikkuPermission({
|
|
114
|
+
* name: 'Admin Permission',
|
|
115
|
+
* description: 'Checks if user has admin role',
|
|
116
|
+
* func: async ({ logger }, data, session) => {
|
|
117
|
+
* return session?.role === 'admin'
|
|
118
|
+
* }
|
|
119
|
+
* })
|
|
120
|
+
* ```
|
|
79
121
|
*/
|
|
80
122
|
export const pikkuPermission = <
|
|
81
123
|
In = any,
|
|
82
124
|
Services extends CoreSingletonServices = CoreServices,
|
|
83
125
|
Session extends CoreUserSession = CoreUserSession,
|
|
84
126
|
>(
|
|
85
|
-
permission:
|
|
127
|
+
permission:
|
|
128
|
+
| CorePikkuPermission<In, Services, Session>
|
|
129
|
+
| CorePikkuPermissionConfig<In, Services, Session>
|
|
86
130
|
): CorePikkuPermission<In, Services, Session> => {
|
|
87
|
-
return permission
|
|
131
|
+
return typeof permission === 'function' ? permission : permission.func
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* A factory function that takes input and returns a permission
|
|
136
|
+
* Used when permissions need configuration/input parameters
|
|
137
|
+
*
|
|
138
|
+
* @template In - The input type for the factory.
|
|
139
|
+
* @template Services - The services type, defaults to `CoreServices`.
|
|
140
|
+
* @template Session - The session type, defaults to `CoreUserSession`.
|
|
141
|
+
*/
|
|
142
|
+
export type CorePikkuPermissionFactory<
|
|
143
|
+
In = any,
|
|
144
|
+
Services extends CoreSingletonServices = CoreServices,
|
|
145
|
+
Session extends CoreUserSession = CoreUserSession,
|
|
146
|
+
> = (input: In) => CorePikkuPermission<any, Services, Session>
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Factory function for creating permission factories
|
|
150
|
+
* Use this when your permission needs configuration/input parameters
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```typescript
|
|
154
|
+
* export const requireRole = pikkuPermissionFactory<{ role: string }>(({
|
|
155
|
+
* role
|
|
156
|
+
* }) => {
|
|
157
|
+
* return pikkuPermission(async ({ logger }, data, session) => {
|
|
158
|
+
* if (!session || session.role !== role) {
|
|
159
|
+
* logger.warn(`Permission denied: required role '${role}'`)
|
|
160
|
+
* return false
|
|
161
|
+
* }
|
|
162
|
+
* return true
|
|
163
|
+
* })
|
|
164
|
+
* })
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
export const pikkuPermissionFactory = <In = any>(
|
|
168
|
+
factory: CorePikkuPermissionFactory<In>
|
|
169
|
+
): CorePikkuPermissionFactory<In> => {
|
|
170
|
+
return factory
|
|
88
171
|
}
|
|
89
172
|
|
|
90
173
|
export type CorePermissionGroup<PikkuPermission = CorePikkuPermission<any>> =
|
package/src/index.ts
CHANGED
|
@@ -16,28 +16,15 @@ export * from './wirings/scheduler/index.js'
|
|
|
16
16
|
export * from './wirings/rpc/index.js'
|
|
17
17
|
export * from './wirings/queue/index.js'
|
|
18
18
|
export * from './wirings/mcp/index.js'
|
|
19
|
+
export * from './wirings/cli/index.js'
|
|
19
20
|
export * from './errors/index.js'
|
|
20
21
|
export * from './middleware/index.js'
|
|
21
22
|
export * from './utils.js'
|
|
22
23
|
export * from './time-utils.js'
|
|
23
24
|
export { pikkuState } from './pikku-state.js'
|
|
24
|
-
export { addMiddleware, runMiddleware } from './middleware-runner.js'
|
|
25
|
-
export { addPermission } from './permissions.js'
|
|
26
|
-
export { wireHTTP, addHTTPMiddleware } from './wirings/http/http-runner.js'
|
|
27
|
-
export { wireChannel } from './wirings/channel/channel-runner.js'
|
|
28
|
-
export { wireScheduler } from './wirings/scheduler/scheduler-runner.js'
|
|
29
|
-
export { wireQueueWorker } from './wirings/queue/queue-runner.js'
|
|
30
25
|
export {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
runMCPPrompt,
|
|
37
|
-
getMCPTools,
|
|
38
|
-
getMCPResources,
|
|
39
|
-
getMCPPrompts,
|
|
40
|
-
getMCPResourcesMeta,
|
|
41
|
-
getMCPToolsMeta,
|
|
42
|
-
getMCPPromptsMeta,
|
|
43
|
-
} from './wirings/mcp/mcp-runner.js'
|
|
26
|
+
runMiddleware,
|
|
27
|
+
addMiddleware,
|
|
28
|
+
getMiddlewareByName,
|
|
29
|
+
} from './middleware-runner.js'
|
|
30
|
+
export { addPermission } from './permissions.js'
|