@pikku/core 0.9.5 → 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/dist/function/function-runner.d.ts +2 -2
- package/dist/function/function-runner.js +3 -3
- 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 +5 -4
- package/dist/middleware-runner.js +20 -6
- package/dist/permissions.d.ts +3 -3
- package/dist/permissions.js +30 -8
- package/dist/pikku-state.d.ts +2 -4
- package/dist/pikku-state.js +3 -2
- package/dist/schema.js +8 -6
- package/dist/types/core.types.d.ts +4 -0
- 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 +5 -3
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +4 -2
- package/dist/wirings/http/http-runner.js +36 -53
- package/dist/wirings/http/http.types.d.ts +1 -1
- 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 +3 -2
- package/dist/wirings/queue/queue-runner.js +3 -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 +3 -2
- package/package.json +1 -1
- package/src/factory-functions.test.ts +37 -0
- package/src/function/function-runner.test.ts +101 -52
- package/src/function/function-runner.ts +5 -2
- package/src/function/functions.types.ts +13 -0
- package/src/index.ts +3 -11
- package/src/middleware-runner.test.ts +71 -43
- package/src/middleware-runner.ts +43 -17
- package/src/permissions.test.ts +21 -17
- package/src/permissions.ts +68 -26
- package/src/pikku-state.ts +5 -3
- package/src/schema.ts +8 -6
- package/src/types/core.types.ts +12 -0
- 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 +5 -4
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +13 -11
- package/src/wirings/http/http-runner.test.ts +18 -6
- package/src/wirings/http/http-runner.ts +55 -74
- package/src/wirings/http/http.types.ts +1 -1
- 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 +18 -12
- package/src/wirings/queue/queue-runner.ts +15 -7
- package/src/wirings/rpc/rpc-runner.ts +21 -16
- package/src/wirings/scheduler/scheduler-runner.ts +18 -12
- 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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
## 0.9.0
|
|
2
2
|
|
|
3
|
+
## 0.9.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 6059c87: refactor: move PikkuPermission to pikkuPermission and same for middleware for api consistency to to improve future features
|
|
8
|
+
- 6db63bb: perf: changing http meta to a lookup map to reduce loops
|
|
9
|
+
- 74f8634: perf: moving router externally to be able to swap them out, similar to hono
|
|
10
|
+
- 766fef1: feat: adding caching for middleware and permissions
|
|
11
|
+
|
|
3
12
|
## 0.9.5
|
|
4
13
|
|
|
5
14
|
### Patch Changes
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { CoreServices, CoreUserSession, CorePikkuMiddleware } from '../types/core.types.js';
|
|
1
|
+
import { CoreServices, CoreUserSession, CorePikkuMiddleware, PikkuWiringTypes } from '../types/core.types.js';
|
|
2
2
|
import { CorePermissionGroup, CorePikkuFunction, CorePikkuFunctionConfig, CorePikkuFunctionSessionless } from './functions.types.js';
|
|
3
3
|
export declare const addFunction: (funcName: string, funcConfig: CorePikkuFunctionConfig<any, any> | CorePikkuFunctionSessionless<any, any> | CorePikkuFunction<any, any>) => void;
|
|
4
4
|
export declare const runPikkuFuncDirectly: <In, Out>(funcName: string, allServices: CoreServices, data: In, session?: CoreUserSession) => Promise<Out>;
|
|
5
|
-
export declare const runPikkuFunc: <In = any, Out = any>(funcName: string, { getAllServices, data, session, permissions: wiringPermissions, middleware: wiringMiddleware, coerceDataFromSchema, tags, }: {
|
|
5
|
+
export declare const runPikkuFunc: <In = any, Out = any>(wireType: PikkuWiringTypes, wireId: string, funcName: string, { getAllServices, data, session, permissions: wiringPermissions, middleware: wiringMiddleware, coerceDataFromSchema, tags, }: {
|
|
6
6
|
getAllServices: () => Promise<CoreServices> | CoreServices;
|
|
7
7
|
data: In;
|
|
8
8
|
session?: CoreUserSession;
|
|
@@ -16,7 +16,7 @@ export const runPikkuFuncDirectly = async (funcName, allServices, data, session)
|
|
|
16
16
|
}
|
|
17
17
|
return (await funcConfig.func(allServices, data, session));
|
|
18
18
|
};
|
|
19
|
-
export const runPikkuFunc = async (funcName, { getAllServices, data, session, permissions: wiringPermissions, middleware: wiringMiddleware, coerceDataFromSchema, tags = [], }) => {
|
|
19
|
+
export const runPikkuFunc = async (wireType, wireId, funcName, { getAllServices, data, session, permissions: wiringPermissions, middleware: wiringMiddleware, coerceDataFromSchema, tags = [], }) => {
|
|
20
20
|
const funcConfig = pikkuState('function', 'functions').get(funcName);
|
|
21
21
|
if (!funcConfig) {
|
|
22
22
|
throw new Error(`Function not found: ${funcName}`);
|
|
@@ -39,7 +39,7 @@ export const runPikkuFunc = async (funcName, { getAllServices, data, session, pe
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
// Run permission checks in the specified order
|
|
42
|
-
await runPermissions({
|
|
42
|
+
await runPermissions(wireType, wireId, {
|
|
43
43
|
wiringTags: tags,
|
|
44
44
|
wiringPermissions,
|
|
45
45
|
funcTags: funcConfig.tags,
|
|
@@ -49,7 +49,7 @@ export const runPikkuFunc = async (funcName, { getAllServices, data, session, pe
|
|
|
49
49
|
session,
|
|
50
50
|
});
|
|
51
51
|
// Combine all middleware: wiring tags → wiring middleware → func middleware → func tags
|
|
52
|
-
const allMiddleware = combineMiddleware({
|
|
52
|
+
const allMiddleware = combineMiddleware(wireType, wireId, {
|
|
53
53
|
wiringTags: tags,
|
|
54
54
|
wiringMiddleware,
|
|
55
55
|
funcMiddleware: funcConfig.middleware,
|
|
@@ -34,6 +34,10 @@ export type CorePikkuFunctionSessionless<In, Out, ChannelData extends unknown |
|
|
|
34
34
|
* @template Session - The session type, defaults to `CoreUserSession`.
|
|
35
35
|
*/
|
|
36
36
|
export type CorePikkuPermission<In = any, Services extends CoreSingletonServices = CoreServices, Session extends CoreUserSession = CoreUserSession> = (services: Services, data: In, session?: Session) => Promise<boolean>;
|
|
37
|
+
/**
|
|
38
|
+
* Factory function for creating permissions with tree-shaking support
|
|
39
|
+
*/
|
|
40
|
+
export declare const pikkuPermission: <In = any, Services extends CoreSingletonServices = CoreServices, Session extends CoreUserSession = CoreUserSession>(permission: CorePikkuPermission<In, Services, Session>) => CorePikkuPermission<In, Services, Session>;
|
|
37
41
|
export type CorePermissionGroup<PikkuPermission = CorePikkuPermission<any>> = Record<string, PikkuPermission | PikkuPermission[]> | undefined;
|
|
38
42
|
export type CorePikkuFunctionConfig<PikkuFunction extends CorePikkuFunction<any, any, any, any, any> | CorePikkuFunctionSessionless<any, any, any, any, any>, PikkuPermission extends CorePikkuPermission<any, any, any> = CorePikkuPermission<any>, PikkuMiddleware extends CorePikkuMiddleware<any> = CorePikkuMiddleware<any>> = {
|
|
39
43
|
name?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -18,11 +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
|
-
export { addPermission
|
|
24
|
+
export { addMiddleware, runMiddleware } from './middleware-runner.js';
|
|
25
|
+
export { addPermission } from './permissions.js';
|
|
26
26
|
export { wireHTTP, addHTTPMiddleware } from './wirings/http/http-runner.js';
|
|
27
27
|
export { wireChannel } from './wirings/channel/channel-runner.js';
|
|
28
28
|
export { wireScheduler } from './wirings/scheduler/scheduler-runner.js';
|
package/dist/index.js
CHANGED
|
@@ -18,11 +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
|
-
export { addPermission
|
|
24
|
+
export { addMiddleware, runMiddleware } from './middleware-runner.js';
|
|
25
|
+
export { addPermission } from './permissions.js';
|
|
26
26
|
export { wireHTTP, addHTTPMiddleware } from './wirings/http/http-runner.js';
|
|
27
27
|
export { wireChannel } from './wirings/channel/channel-runner.js';
|
|
28
28
|
export { wireScheduler } from './wirings/scheduler/scheduler-runner.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { UserSessionService } from './services/user-session-service.js';
|
|
2
|
-
import { CoreSingletonServices, PikkuInteraction, CorePikkuMiddleware } from './types/core.types.js';
|
|
2
|
+
import { CoreSingletonServices, PikkuInteraction, CorePikkuMiddleware, PikkuWiringTypes } from './types/core.types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Runs a chain of middleware functions in sequence before executing the main function.
|
|
5
5
|
*
|
|
@@ -19,7 +19,7 @@ import { CoreSingletonServices, PikkuInteraction, CorePikkuMiddleware } from './
|
|
|
19
19
|
*/
|
|
20
20
|
export declare const runMiddleware: <Middleware extends CorePikkuMiddleware>(services: CoreSingletonServices & {
|
|
21
21
|
userSession?: UserSessionService<any>;
|
|
22
|
-
}, interaction: PikkuInteraction, middlewares: Middleware[], main?: () => Promise<unknown>) => Promise<unknown>;
|
|
22
|
+
}, interaction: PikkuInteraction, middlewares: readonly Middleware[], main?: () => Promise<unknown>) => Promise<unknown>;
|
|
23
23
|
/**
|
|
24
24
|
* Adds global middleware for a specific tag.
|
|
25
25
|
*
|
|
@@ -84,9 +84,10 @@ export declare const getMiddlewareForTags: (tags?: string[]) => CorePikkuMiddlew
|
|
|
84
84
|
* })
|
|
85
85
|
* ```
|
|
86
86
|
*/
|
|
87
|
-
export declare const combineMiddleware: ({ wiringMiddleware, wiringTags, funcMiddleware, funcTags, }?: {
|
|
87
|
+
export declare const combineMiddleware: (wireType: PikkuWiringTypes, uid: string, { httpMiddleware, wiringMiddleware, wiringTags, funcMiddleware, funcTags, }?: {
|
|
88
|
+
httpMiddleware?: CorePikkuMiddleware[];
|
|
88
89
|
wiringMiddleware?: CorePikkuMiddleware[];
|
|
89
90
|
wiringTags?: string[];
|
|
90
91
|
funcMiddleware?: CorePikkuMiddleware[];
|
|
91
92
|
funcTags?: string[];
|
|
92
|
-
}) => CorePikkuMiddleware[];
|
|
93
|
+
}) => readonly CorePikkuMiddleware[];
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { PikkuWiringTypes, } from './types/core.types.js';
|
|
1
2
|
import { pikkuState } from './pikku-state.js';
|
|
3
|
+
import { freezeDedupe } from './utils.js';
|
|
2
4
|
/**
|
|
3
5
|
* Runs a chain of middleware functions in sequence before executing the main function.
|
|
4
6
|
*
|
|
@@ -18,11 +20,10 @@ import { pikkuState } from './pikku-state.js';
|
|
|
18
20
|
*/
|
|
19
21
|
export const runMiddleware = async (services, interaction, middlewares, main) => {
|
|
20
22
|
// Deduplicate middleware using Set to avoid running the same middleware multiple times
|
|
21
|
-
const uniqueMiddleware = Array.from(new Set(middlewares));
|
|
22
23
|
let result;
|
|
23
24
|
const dispatch = async (index) => {
|
|
24
|
-
if (
|
|
25
|
-
return await
|
|
25
|
+
if (middlewares && index < middlewares.length) {
|
|
26
|
+
return await middlewares[index](services, interaction, () => dispatch(index + 1));
|
|
26
27
|
}
|
|
27
28
|
else if (main) {
|
|
28
29
|
result = await main();
|
|
@@ -93,6 +94,14 @@ export const getMiddlewareForTags = (tags) => {
|
|
|
93
94
|
}
|
|
94
95
|
return applicableMiddleware;
|
|
95
96
|
};
|
|
97
|
+
const middlewareCache = {
|
|
98
|
+
[PikkuWiringTypes.http]: {},
|
|
99
|
+
[PikkuWiringTypes.rpc]: {},
|
|
100
|
+
[PikkuWiringTypes.channel]: {},
|
|
101
|
+
[PikkuWiringTypes.queue]: {},
|
|
102
|
+
[PikkuWiringTypes.scheduler]: {},
|
|
103
|
+
[PikkuWiringTypes.mcp]: {},
|
|
104
|
+
};
|
|
96
105
|
/**
|
|
97
106
|
* Combines tag-based middleware with wiring-specific middleware and function-level middleware.
|
|
98
107
|
*
|
|
@@ -116,7 +125,10 @@ export const getMiddlewareForTags = (tags) => {
|
|
|
116
125
|
* })
|
|
117
126
|
* ```
|
|
118
127
|
*/
|
|
119
|
-
export const combineMiddleware = ({ wiringMiddleware, wiringTags, funcMiddleware, funcTags, } = {}) => {
|
|
128
|
+
export const combineMiddleware = (wireType, uid, { httpMiddleware, wiringMiddleware, wiringTags, funcMiddleware, funcTags, } = {}) => {
|
|
129
|
+
if (middlewareCache[wireType][uid]) {
|
|
130
|
+
return middlewareCache[wireType][uid];
|
|
131
|
+
}
|
|
120
132
|
// Run middleware in specific order:
|
|
121
133
|
// 1) wiringTags middleware
|
|
122
134
|
// 2) wiringMiddleware
|
|
@@ -124,10 +136,12 @@ export const combineMiddleware = ({ wiringMiddleware, wiringTags, funcMiddleware
|
|
|
124
136
|
// 4) funcTags middleware
|
|
125
137
|
const wiringTaggedMiddleware = getMiddlewareForTags(wiringTags);
|
|
126
138
|
const funcTaggedMiddleware = getMiddlewareForTags(funcTags);
|
|
127
|
-
|
|
139
|
+
middlewareCache[wireType][uid] = freezeDedupe([
|
|
140
|
+
...(httpMiddleware || []),
|
|
128
141
|
...wiringTaggedMiddleware,
|
|
129
142
|
...(wiringMiddleware || []),
|
|
130
143
|
...(funcMiddleware || []),
|
|
131
144
|
...funcTaggedMiddleware,
|
|
132
|
-
];
|
|
145
|
+
]);
|
|
146
|
+
return middlewareCache[wireType][uid];
|
|
133
147
|
};
|
package/dist/permissions.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CoreServices, CoreUserSession } from './types/core.types.js';
|
|
1
|
+
import { CoreServices, CoreUserSession, PikkuWiringTypes } from './types/core.types.js';
|
|
2
2
|
import { CorePermissionGroup, CorePikkuPermission } from './function/functions.types.js';
|
|
3
3
|
/**
|
|
4
4
|
* This function validates permissions by iterating over permission groups and executing the corresponding permission functions. If all functions in at least one group return true, the permission is considered valid.
|
|
@@ -47,7 +47,7 @@ export declare const addPermission: (tag: string, permissions: CorePermissionGro
|
|
|
47
47
|
* const permissions = getPermissionsForTags(['api', 'auth'])
|
|
48
48
|
* ```
|
|
49
49
|
*/
|
|
50
|
-
export declare const getPermissionsForTags: (tags?: string[]) =>
|
|
50
|
+
export declare const getPermissionsForTags: (tags?: string[]) => readonly (CorePermissionGroup | CorePikkuPermission)[];
|
|
51
51
|
/**
|
|
52
52
|
* Runs permission checks in the specified order:
|
|
53
53
|
* 1) wiring tag permissions - at least one must pass if any exist
|
|
@@ -55,7 +55,7 @@ export declare const getPermissionsForTags: (tags?: string[]) => Array<CorePermi
|
|
|
55
55
|
* 3) function tag permissions - at least one must pass if any exist
|
|
56
56
|
* 4) function permissions - must pass if defined
|
|
57
57
|
*/
|
|
58
|
-
export declare const runPermissions: ({ wiringTags, wiringPermissions, funcTags, funcPermissions, allServices, data, session, }: {
|
|
58
|
+
export declare const runPermissions: (wireType: PikkuWiringTypes, uid: string, { wiringTags, wiringPermissions, funcTags, funcPermissions, allServices, data, session, }: {
|
|
59
59
|
wiringTags?: string[];
|
|
60
60
|
wiringPermissions?: CorePermissionGroup;
|
|
61
61
|
funcTags?: string[];
|
package/dist/permissions.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PikkuWiringTypes, } from './types/core.types.js';
|
|
1
2
|
import { pikkuState } from './pikku-state.js';
|
|
2
3
|
import { ForbiddenError } from './errors/errors.js';
|
|
3
4
|
/**
|
|
@@ -63,6 +64,7 @@ export const addPermission = (tag, permissions) => {
|
|
|
63
64
|
}
|
|
64
65
|
permissionsStore[tag] = permissions;
|
|
65
66
|
};
|
|
67
|
+
const EMPTY = [];
|
|
66
68
|
/**
|
|
67
69
|
* Retrieves permissions for a given set of tags.
|
|
68
70
|
*
|
|
@@ -80,7 +82,7 @@ export const addPermission = (tag, permissions) => {
|
|
|
80
82
|
*/
|
|
81
83
|
export const getPermissionsForTags = (tags) => {
|
|
82
84
|
if (!tags || tags.length === 0) {
|
|
83
|
-
return
|
|
85
|
+
return EMPTY;
|
|
84
86
|
}
|
|
85
87
|
const permissionsStore = pikkuState('misc', 'permissions');
|
|
86
88
|
const applicablePermissions = [];
|
|
@@ -98,6 +100,14 @@ export const getPermissionsForTags = (tags) => {
|
|
|
98
100
|
}
|
|
99
101
|
return applicablePermissions;
|
|
100
102
|
};
|
|
103
|
+
const permissionCache = {
|
|
104
|
+
[PikkuWiringTypes.http]: {},
|
|
105
|
+
[PikkuWiringTypes.rpc]: {},
|
|
106
|
+
[PikkuWiringTypes.channel]: {},
|
|
107
|
+
[PikkuWiringTypes.queue]: {},
|
|
108
|
+
[PikkuWiringTypes.scheduler]: {},
|
|
109
|
+
[PikkuWiringTypes.mcp]: {},
|
|
110
|
+
};
|
|
101
111
|
/**
|
|
102
112
|
* Runs permission checks in the specified order:
|
|
103
113
|
* 1) wiring tag permissions - at least one must pass if any exist
|
|
@@ -105,10 +115,18 @@ export const getPermissionsForTags = (tags) => {
|
|
|
105
115
|
* 3) function tag permissions - at least one must pass if any exist
|
|
106
116
|
* 4) function permissions - must pass if defined
|
|
107
117
|
*/
|
|
108
|
-
export const runPermissions = async ({ wiringTags, wiringPermissions, funcTags, funcPermissions, allServices, data, session, }) => {
|
|
118
|
+
export const runPermissions = async (wireType, uid, { wiringTags, wiringPermissions, funcTags, funcPermissions, allServices, data, session, }) => {
|
|
119
|
+
let cachedPermission = permissionCache[wireType][uid];
|
|
120
|
+
if (!cachedPermission) {
|
|
121
|
+
cachedPermission = {
|
|
122
|
+
wiringTags: getPermissionsForTags(wiringTags),
|
|
123
|
+
funcTags: getPermissionsForTags(funcTags),
|
|
124
|
+
};
|
|
125
|
+
permissionCache[wireType][uid] = cachedPermission;
|
|
126
|
+
}
|
|
109
127
|
let permissioned = true;
|
|
110
128
|
// 1. Wiring tag permissions - at least one must pass if any exist
|
|
111
|
-
const wiringTaggedPermissions =
|
|
129
|
+
const wiringTaggedPermissions = cachedPermission.wiringTags;
|
|
112
130
|
if (wiringTaggedPermissions.length > 0) {
|
|
113
131
|
permissioned = false; // Start false, need at least one to pass
|
|
114
132
|
for (const permissions of wiringTaggedPermissions) {
|
|
@@ -119,18 +137,20 @@ export const runPermissions = async ({ wiringTags, wiringPermissions, funcTags,
|
|
|
119
137
|
}
|
|
120
138
|
}
|
|
121
139
|
if (!permissioned) {
|
|
122
|
-
|
|
140
|
+
allServices.logger.debug('Permission denied - wiring tag permissions');
|
|
141
|
+
throw new ForbiddenError('Permission denied');
|
|
123
142
|
}
|
|
124
143
|
}
|
|
125
144
|
// 2. Wiring permissions - must pass if defined
|
|
126
145
|
if (wiringPermissions) {
|
|
127
146
|
permissioned = await verifyPermissions(wiringPermissions, allServices, data, session);
|
|
128
147
|
if (!permissioned) {
|
|
129
|
-
|
|
148
|
+
allServices.logger.debug('Permission denied - wiring permissions');
|
|
149
|
+
throw new ForbiddenError('Permission denied');
|
|
130
150
|
}
|
|
131
151
|
}
|
|
132
152
|
// 3. Function tag permissions - at least one must pass if any exist
|
|
133
|
-
const funcTaggedPermissions =
|
|
153
|
+
const funcTaggedPermissions = cachedPermission.funcTags;
|
|
134
154
|
if (funcTaggedPermissions.length > 0) {
|
|
135
155
|
permissioned = false; // Start false, need at least one to pass
|
|
136
156
|
for (const permissions of funcTaggedPermissions) {
|
|
@@ -141,14 +161,16 @@ export const runPermissions = async ({ wiringTags, wiringPermissions, funcTags,
|
|
|
141
161
|
}
|
|
142
162
|
}
|
|
143
163
|
if (!permissioned) {
|
|
144
|
-
|
|
164
|
+
allServices.logger.debug('Permission denied - function tag permissions');
|
|
165
|
+
throw new ForbiddenError('Permission denied');
|
|
145
166
|
}
|
|
146
167
|
}
|
|
147
168
|
// 4. Function permissions - must pass if defined
|
|
148
169
|
if (funcPermissions) {
|
|
149
170
|
permissioned = await verifyPermissions(funcPermissions, allServices, data, session);
|
|
150
171
|
if (!permissioned) {
|
|
151
|
-
|
|
172
|
+
allServices.logger.debug('Permission denied - function permissions');
|
|
173
|
+
throw new ForbiddenError('Permission denied');
|
|
152
174
|
}
|
|
153
175
|
}
|
|
154
176
|
};
|
package/dist/pikku-state.d.ts
CHANGED
|
@@ -19,10 +19,8 @@ interface PikkuState {
|
|
|
19
19
|
}>;
|
|
20
20
|
};
|
|
21
21
|
http: {
|
|
22
|
-
middleware:
|
|
23
|
-
|
|
24
|
-
middleware: CorePikkuMiddleware[];
|
|
25
|
-
}>;
|
|
22
|
+
middleware: Map<string, CorePikkuMiddleware<any, any>[]>;
|
|
23
|
+
permissions: Map<string, CorePermissionGroup | CorePikkuPermission[]>;
|
|
26
24
|
routes: Map<HTTPMethod, Map<string, CoreHTTPFunctionWiring<any, any, any>>>;
|
|
27
25
|
meta: HTTPWiringsMeta;
|
|
28
26
|
};
|
package/dist/pikku-state.js
CHANGED
package/dist/schema.js
CHANGED
|
@@ -27,13 +27,15 @@ const validateAllSchemasLoaded = (logger, schemaService) => {
|
|
|
27
27
|
const routesMeta = pikkuState('http', 'meta');
|
|
28
28
|
const validators = schemaService.getSchemaNames();
|
|
29
29
|
const missingSchemas = [];
|
|
30
|
-
for (const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
for (const routePaths of Object.values(routesMeta)) {
|
|
31
|
+
for (const meta of Object.values(routePaths)) {
|
|
32
|
+
const inputs = pikkuState('function', 'meta')[meta.pikkuFuncName]?.inputs;
|
|
33
|
+
const input = inputs?.[0];
|
|
34
|
+
if (!input || validators.has(input)) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
missingSchemas.push(input);
|
|
35
38
|
}
|
|
36
|
-
missingSchemas.push(input);
|
|
37
39
|
}
|
|
38
40
|
if (missingSchemas.length > 0) {
|
|
39
41
|
logger.error(`Error: Failed to load schemas:\n.${missingSchemas.join('\n')}`);
|
|
@@ -106,6 +106,10 @@ export type PikkuInteraction<In = unknown, Out = unknown> = Partial<{
|
|
|
106
106
|
export type CorePikkuMiddleware<SingletonServices extends CoreSingletonServices = CoreSingletonServices, UserSession extends CoreUserSession = CoreUserSession> = (services: SingletonServices & {
|
|
107
107
|
userSession: UserSessionService<UserSession>;
|
|
108
108
|
}, interactions: PikkuInteraction, next: () => Promise<void>) => Promise<void>;
|
|
109
|
+
/**
|
|
110
|
+
* Factory function for creating middleware with tree-shaking support
|
|
111
|
+
*/
|
|
112
|
+
export declare const pikkuMiddleware: <SingletonServices extends CoreSingletonServices = CoreSingletonServices, UserSession extends CoreUserSession = CoreUserSession>(middleware: CorePikkuMiddleware<SingletonServices, UserSession>) => CorePikkuMiddleware<SingletonServices, UserSession>;
|
|
109
113
|
/**
|
|
110
114
|
* Represents the core services used by Pikku, including singleton services and the request/response interaction.
|
|
111
115
|
*/
|
package/dist/types/core.types.js
CHANGED
|
@@ -7,3 +7,9 @@ export var PikkuWiringTypes;
|
|
|
7
7
|
PikkuWiringTypes["queue"] = "queue";
|
|
8
8
|
PikkuWiringTypes["mcp"] = "mcp";
|
|
9
9
|
})(PikkuWiringTypes || (PikkuWiringTypes = {}));
|
|
10
|
+
/**
|
|
11
|
+
* Factory function for creating middleware with tree-shaking support
|
|
12
|
+
*/
|
|
13
|
+
export const pikkuMiddleware = (middleware) => {
|
|
14
|
+
return middleware;
|
|
15
|
+
};
|
package/dist/utils.d.ts
CHANGED
|
@@ -2,3 +2,4 @@ import { Logger } from './services/logger.js';
|
|
|
2
2
|
export declare const closeSessionServices: (logger: Logger, sessionServices: Record<string, any>) => Promise<void>;
|
|
3
3
|
export declare const createWeakUID: () => string;
|
|
4
4
|
export declare const isSerializable: (data: any) => boolean;
|
|
5
|
+
export declare const freezeDedupe: <T extends Function>(arr?: readonly T[] | T[] | undefined) => readonly T[];
|
package/dist/utils.js
CHANGED
|
@@ -28,3 +28,20 @@ export const isSerializable = (data) => {
|
|
|
28
28
|
data instanceof Float32Array ||
|
|
29
29
|
data instanceof Float64Array);
|
|
30
30
|
};
|
|
31
|
+
const EMPTY_ARRAY = Object.freeze([]);
|
|
32
|
+
export const freezeDedupe = (arr) => {
|
|
33
|
+
if (!arr || arr.length === 0)
|
|
34
|
+
return EMPTY_ARRAY;
|
|
35
|
+
if (arr.length === 1)
|
|
36
|
+
return Object.freeze([arr[0]]);
|
|
37
|
+
const seen = new Set();
|
|
38
|
+
const out = [];
|
|
39
|
+
for (let i = 0; i < arr.length; i++) {
|
|
40
|
+
const fn = arr[i];
|
|
41
|
+
if (!seen.has(fn)) {
|
|
42
|
+
seen.add(fn);
|
|
43
|
+
out.push(fn);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return Object.freeze(out);
|
|
47
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PikkuWiringTypes, } from '../../types/core.types.js';
|
|
1
2
|
import { pikkuState } from '../../pikku-state.js';
|
|
2
3
|
import { runPikkuFunc } from '../../function/function-runner.js';
|
|
3
4
|
const getRouteMeta = (channelName, routingProperty, routerValue) => {
|
|
@@ -47,7 +48,7 @@ export const processMessageHandlers = (services, session, channelConfig, channel
|
|
|
47
48
|
const { pikkuFuncName } = getRouteMeta(channelConfig.name, routingProperty, routerValue);
|
|
48
49
|
const permissions = typeof onMessage === 'function' ? {} : onMessage.permissions;
|
|
49
50
|
const middleware = typeof onMessage === 'function' ? [] : onMessage.middleware;
|
|
50
|
-
return await runPikkuFunc(pikkuFuncName, {
|
|
51
|
+
return await runPikkuFunc(PikkuWiringTypes.channel, channelConfig.name, pikkuFuncName, {
|
|
51
52
|
getAllServices: () => ({
|
|
52
53
|
...services,
|
|
53
54
|
channel: channelHandler.getChannel(),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { UserSessionService } from '../../services/user-session-service.js';
|
|
2
|
+
import { CorePikkuMiddleware } from '../../types/core.types.js';
|
|
2
3
|
import { ChannelMeta, CoreChannel, RunChannelOptions, RunChannelParams } from './channel.types.js';
|
|
3
4
|
/**
|
|
4
5
|
* Adds a channel and registers all functions referenced in it using the
|
|
@@ -11,4 +12,5 @@ export declare const openChannel: ({ route, singletonServices, coerceDataFromSch
|
|
|
11
12
|
openingData: unknown;
|
|
12
13
|
channelConfig: CoreChannel<unknown, any>;
|
|
13
14
|
meta: ChannelMeta;
|
|
15
|
+
httpMiddleware: CorePikkuMiddleware[] | undefined;
|
|
14
16
|
}>;
|
|
@@ -2,7 +2,7 @@ import { NotFoundError } from '../../errors/errors.js';
|
|
|
2
2
|
import { addFunction } from '../../function/function-runner.js';
|
|
3
3
|
import { pikkuState } from '../../pikku-state.js';
|
|
4
4
|
import { coerceTopLevelDataFromSchema, validateSchema } from '../../schema.js';
|
|
5
|
-
import {
|
|
5
|
+
import { httpRouter } from '../http/routers/http-router.js';
|
|
6
6
|
/**
|
|
7
7
|
* Adds a channel and registers all functions referenced in it using the
|
|
8
8
|
* function names already stored in the channel metadata
|
|
@@ -65,30 +65,28 @@ export const wireChannel = (channel) => {
|
|
|
65
65
|
pikkuState('channel', 'channels').set(channel.name, channel);
|
|
66
66
|
};
|
|
67
67
|
const getMatchingChannelConfig = (request) => {
|
|
68
|
+
const matchedPath = httpRouter.match('get', request);
|
|
69
|
+
if (!matchedPath) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const meta = pikkuState('channel', 'meta');
|
|
73
|
+
const channelMeta = Object.values(meta).find((channelConfig) => channelConfig.route === matchedPath.route);
|
|
74
|
+
if (!channelMeta) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
68
77
|
const channels = pikkuState('channel', 'channels');
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const cleanedRequest = request.replace(/^\/\//, '/');
|
|
73
|
-
const matchFunc = match(cleanedRoute, {
|
|
74
|
-
decode: decodeURIComponent,
|
|
75
|
-
});
|
|
76
|
-
const matchedPath = matchFunc(cleanedRequest);
|
|
77
|
-
if (matchedPath) {
|
|
78
|
-
const channelMeta = channelsMeta[channelConfig.name];
|
|
79
|
-
if (!channelMeta) {
|
|
80
|
-
throw new Error(`Channel ${channelConfig.name} not found in metadata`);
|
|
81
|
-
}
|
|
82
|
-
return {
|
|
83
|
-
matchedPath,
|
|
84
|
-
params: matchedPath.params,
|
|
85
|
-
channelConfig,
|
|
86
|
-
schemaName: channelMeta.input,
|
|
87
|
-
meta: channelMeta,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
78
|
+
const channelConfig = channels.get(channelMeta.name);
|
|
79
|
+
if (!channelConfig) {
|
|
80
|
+
return null;
|
|
90
81
|
}
|
|
91
|
-
return
|
|
82
|
+
return {
|
|
83
|
+
matchedPath,
|
|
84
|
+
params: matchedPath.params,
|
|
85
|
+
channelConfig,
|
|
86
|
+
schemaName: channelMeta.input,
|
|
87
|
+
meta: channelMeta,
|
|
88
|
+
httpMiddleware: matchedPath.middleware,
|
|
89
|
+
};
|
|
92
90
|
};
|
|
93
91
|
export const openChannel = async ({ route, singletonServices, coerceDataFromSchema = true, request, }) => {
|
|
94
92
|
const matchingChannel = getMatchingChannelConfig(route);
|
|
@@ -96,7 +94,7 @@ export const openChannel = async ({ route, singletonServices, coerceDataFromSche
|
|
|
96
94
|
singletonServices.logger.info(`Channel not found: ${route}`);
|
|
97
95
|
throw new NotFoundError(`Channel not found: ${route}`);
|
|
98
96
|
}
|
|
99
|
-
const { params, channelConfig, schemaName, meta } = matchingChannel;
|
|
97
|
+
const { params, channelConfig, schemaName, meta, httpMiddleware } = matchingChannel;
|
|
100
98
|
const requiresSession = channelConfig.auth !== false;
|
|
101
99
|
request?.setParams(params);
|
|
102
100
|
singletonServices.logger.info(`Matched channel: ${channelConfig.name} | route: ${channelConfig.route} | auth: ${requiresSession.toString()}`);
|
|
@@ -108,5 +106,5 @@ export const openChannel = async ({ route, singletonServices, coerceDataFromSche
|
|
|
108
106
|
}
|
|
109
107
|
await validateSchema(singletonServices.logger, singletonServices.schema, schemaName, openingData);
|
|
110
108
|
}
|
|
111
|
-
return { openingData, channelConfig, meta };
|
|
109
|
+
return { openingData, channelConfig, meta, httpMiddleware };
|
|
112
110
|
};
|
|
@@ -3,6 +3,7 @@ import { createHTTPInteraction } from '../../http/http-runner.js';
|
|
|
3
3
|
import { closeSessionServices } from '../../../utils.js';
|
|
4
4
|
import { processMessageHandlers } from '../channel-handler.js';
|
|
5
5
|
import { PikkuLocalChannelHandler } from './local-channel-handler.js';
|
|
6
|
+
import { PikkuWiringTypes } from '../../../types/core.types.js';
|
|
6
7
|
import { handleHTTPError } from '../../../handle-error.js';
|
|
7
8
|
import { combineMiddleware, runMiddleware } from '../../../middleware-runner.js';
|
|
8
9
|
import { PikkuUserSessionService } from '../../../services/user-session-service.js';
|
|
@@ -17,10 +18,10 @@ export const runLocalChannel = async ({ singletonServices, channelId, request, r
|
|
|
17
18
|
http = createHTTPInteraction(request, response);
|
|
18
19
|
route = http?.request?.path();
|
|
19
20
|
}
|
|
20
|
-
let openingData, channelConfig, meta;
|
|
21
|
+
let openingData, channelConfig, meta, httpMiddleware;
|
|
21
22
|
try {
|
|
22
23
|
;
|
|
23
|
-
({ openingData, channelConfig, meta } = await openChannel({
|
|
24
|
+
({ openingData, channelConfig, meta, httpMiddleware } = await openChannel({
|
|
24
25
|
channelId,
|
|
25
26
|
createSessionServices,
|
|
26
27
|
respondWith404,
|
|
@@ -77,9 +78,10 @@ export const runLocalChannel = async ({ singletonServices, channelId, request, r
|
|
|
77
78
|
await runMiddleware({
|
|
78
79
|
...singletonServices,
|
|
79
80
|
userSession,
|
|
80
|
-
}, { http }, combineMiddleware({
|
|
81
|
+
}, { http }, combineMiddleware(PikkuWiringTypes.channel, channelConfig.name, {
|
|
81
82
|
wiringMiddleware: channelConfig.middleware,
|
|
82
83
|
wiringTags: channelConfig.tags,
|
|
84
|
+
httpMiddleware,
|
|
83
85
|
}), main);
|
|
84
86
|
return channelHandler;
|
|
85
87
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PikkuWiringTypes } from '../../../types/core.types.js';
|
|
1
2
|
import { closeSessionServices } from '../../../utils.js';
|
|
2
3
|
import { processMessageHandlers } from '../channel-handler.js';
|
|
3
4
|
import { openChannel } from '../channel-runner.js';
|
|
@@ -34,7 +35,7 @@ export const runChannelConnect = async ({ singletonServices, channelId, channelO
|
|
|
34
35
|
http = createHTTPInteraction(new PikkuFetchHTTPRequest(request), response);
|
|
35
36
|
}
|
|
36
37
|
const userSession = new PikkuUserSessionService(channelStore, channelId);
|
|
37
|
-
const { channelConfig, openingData, meta } = await openChannel({
|
|
38
|
+
const { channelConfig, openingData, meta, httpMiddleware } = await openChannel({
|
|
38
39
|
channelId,
|
|
39
40
|
createSessionServices,
|
|
40
41
|
request,
|
|
@@ -76,9 +77,10 @@ export const runChannelConnect = async ({ singletonServices, channelId, channelO
|
|
|
76
77
|
await runMiddleware({
|
|
77
78
|
...singletonServices,
|
|
78
79
|
userSession,
|
|
79
|
-
}, { http }, combineMiddleware({
|
|
80
|
+
}, { http }, combineMiddleware(PikkuWiringTypes.channel, channelConfig.name, {
|
|
80
81
|
wiringMiddleware: channelConfig.middleware,
|
|
81
82
|
wiringTags: channelConfig.tags,
|
|
83
|
+
httpMiddleware,
|
|
82
84
|
}), main);
|
|
83
85
|
};
|
|
84
86
|
export const runChannelDisconnect = async ({ singletonServices, ...params }) => {
|