@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,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,8 +3,9 @@ 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
|
-
import {
|
|
8
|
+
import { combineMiddleware, runMiddleware } from '../../../middleware-runner.js';
|
|
8
9
|
import { PikkuUserSessionService } from '../../../services/user-session-service.js';
|
|
9
10
|
import { runPikkuFuncDirectly } from '../../../function/function-runner.js';
|
|
10
11
|
import { rpcService } from '../../rpc/rpc-runner.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,6 +78,10 @@ export const runLocalChannel = async ({ singletonServices, channelId, request, r
|
|
|
77
78
|
await runMiddleware({
|
|
78
79
|
...singletonServices,
|
|
79
80
|
userSession,
|
|
80
|
-
}, { http },
|
|
81
|
+
}, { http }, combineMiddleware(PikkuWiringTypes.channel, channelConfig.name, {
|
|
82
|
+
wiringMiddleware: channelConfig.middleware,
|
|
83
|
+
wiringTags: channelConfig.tags,
|
|
84
|
+
httpMiddleware,
|
|
85
|
+
}), main);
|
|
81
86
|
return channelHandler;
|
|
82
87
|
};
|
|
@@ -1,10 +1,11 @@
|
|
|
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';
|
|
4
5
|
import { createHTTPInteraction } from '../../http/http-runner.js';
|
|
5
6
|
import { handleHTTPError } from '../../../handle-error.js';
|
|
6
7
|
import { PikkuUserSessionService } from '../../../services/user-session-service.js';
|
|
7
|
-
import {
|
|
8
|
+
import { combineMiddleware, runMiddleware } from '../../../middleware-runner.js';
|
|
8
9
|
import { pikkuState } from '../../../pikku-state.js';
|
|
9
10
|
import { PikkuFetchHTTPRequest } from '../../http/pikku-fetch-http-request.js';
|
|
10
11
|
import { runPikkuFuncDirectly } from '../../../function/function-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,7 +77,11 @@ export const runChannelConnect = async ({ singletonServices, channelId, channelO
|
|
|
76
77
|
await runMiddleware({
|
|
77
78
|
...singletonServices,
|
|
78
79
|
userSession,
|
|
79
|
-
}, { http },
|
|
80
|
+
}, { http }, combineMiddleware(PikkuWiringTypes.channel, channelConfig.name, {
|
|
81
|
+
wiringMiddleware: channelConfig.middleware,
|
|
82
|
+
wiringTags: channelConfig.tags,
|
|
83
|
+
httpMiddleware,
|
|
84
|
+
}), main);
|
|
80
85
|
};
|
|
81
86
|
export const runChannelDisconnect = async ({ singletonServices, ...params }) => {
|
|
82
87
|
let sessionServices;
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PikkuWiringTypes, } from '../../types/core.types.js';
|
|
2
2
|
import { MissingSessionError, NotFoundError } from '../../errors/errors.js';
|
|
3
3
|
import { closeSessionServices, createWeakUID, isSerializable, } from '../../utils.js';
|
|
4
4
|
import { PikkuUserSessionService, } from '../../services/user-session-service.js';
|
|
5
|
-
import {
|
|
5
|
+
import { combineMiddleware, runMiddleware } from '../../middleware-runner.js';
|
|
6
6
|
import { handleHTTPError } from '../../handle-error.js';
|
|
7
7
|
import { pikkuState } from '../../pikku-state.js';
|
|
8
8
|
import { PikkuFetchHTTPResponse } from './pikku-fetch-http-response.js';
|
|
9
9
|
import { PikkuFetchHTTPRequest } from './pikku-fetch-http-request.js';
|
|
10
10
|
import { addFunction, runPikkuFunc } from '../../function/function-runner.js';
|
|
11
11
|
import { rpcService } from '../rpc/rpc-runner.js';
|
|
12
|
+
import { httpRouter } from './routers/http-router.js';
|
|
12
13
|
/**
|
|
13
14
|
* Registers middleware either globally or for a specific route.
|
|
14
15
|
*
|
|
@@ -22,25 +23,20 @@ import { rpcService } from '../rpc/rpc-runner.js';
|
|
|
22
23
|
*/
|
|
23
24
|
export const addHTTPMiddleware = (routeOrMiddleware, middleware) => {
|
|
24
25
|
const middlewareStore = pikkuState('http', 'middleware');
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (existingIndex !== -1) {
|
|
30
|
-
middlewareStore.splice(existingIndex, 1);
|
|
31
|
-
}
|
|
32
|
-
middlewareStore.push({ route, middleware });
|
|
26
|
+
let route = '*';
|
|
27
|
+
if (typeof routeOrMiddleware === 'string') {
|
|
28
|
+
route = routeOrMiddleware;
|
|
29
|
+
middleware = middleware;
|
|
33
30
|
}
|
|
34
31
|
else {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
});
|
|
32
|
+
middleware = routeOrMiddleware;
|
|
33
|
+
}
|
|
34
|
+
const currentMiddleware = middlewareStore.get(route);
|
|
35
|
+
if (currentMiddleware) {
|
|
36
|
+
middlewareStore.set(route, [...currentMiddleware, ...middleware]);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
middlewareStore.set(route, middleware);
|
|
44
40
|
}
|
|
45
41
|
};
|
|
46
42
|
/**
|
|
@@ -60,7 +56,7 @@ export const addHTTPMiddleware = (routeOrMiddleware, middleware) => {
|
|
|
60
56
|
*/
|
|
61
57
|
export const wireHTTP = (httpWiring) => {
|
|
62
58
|
const httpMeta = pikkuState('http', 'meta');
|
|
63
|
-
const routeMeta = httpMeta
|
|
59
|
+
const routeMeta = httpMeta[httpWiring.method][httpWiring.route];
|
|
64
60
|
if (!routeMeta) {
|
|
65
61
|
throw new Error('Route metadata not found');
|
|
66
62
|
}
|
|
@@ -85,37 +81,22 @@ export const wireHTTP = (httpWiring) => {
|
|
|
85
81
|
* @returns {Object | undefined} An object with matched route details or undefined if no match.
|
|
86
82
|
*/
|
|
87
83
|
const getMatchingRoute = (requestType, requestPath) => {
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const globalMiddleware = middleware
|
|
104
|
-
.filter((m) => m.route === '*' || new RegExp(m.route).test(route.route))
|
|
105
|
-
.map((m) => m.middleware)
|
|
106
|
-
.flat();
|
|
107
|
-
const meta = pikkuState('http', 'meta').find((meta) => meta.route === route.route && meta.method === route.method);
|
|
108
|
-
return {
|
|
109
|
-
matchedPath,
|
|
110
|
-
params: matchedPath.params,
|
|
111
|
-
route,
|
|
112
|
-
permissions: route.permissions,
|
|
113
|
-
middleware: [...globalMiddleware, ...(route.middleware || [])],
|
|
114
|
-
meta: meta,
|
|
115
|
-
};
|
|
116
|
-
}
|
|
84
|
+
const matchedPath = httpRouter.match(requestType.toLowerCase(), requestPath);
|
|
85
|
+
if (matchedPath) {
|
|
86
|
+
const route = pikkuState('http', 'routes')
|
|
87
|
+
.get(requestType.toLowerCase())
|
|
88
|
+
.get(matchedPath.route);
|
|
89
|
+
const meta = pikkuState('http', 'meta')[requestType.toLowerCase()][route.route];
|
|
90
|
+
return {
|
|
91
|
+
matchedPath,
|
|
92
|
+
params: matchedPath.params,
|
|
93
|
+
route,
|
|
94
|
+
permissions: route.permissions,
|
|
95
|
+
httpMiddleware: matchedPath.middleware,
|
|
96
|
+
middleware: route.middleware,
|
|
97
|
+
meta: meta,
|
|
98
|
+
};
|
|
117
99
|
}
|
|
118
|
-
return undefined;
|
|
119
100
|
};
|
|
120
101
|
/**
|
|
121
102
|
* Combines the request and response objects into a single HTTP interaction object.
|
|
@@ -170,7 +151,7 @@ export const createHTTPInteraction = (request, response) => {
|
|
|
170
151
|
* @throws Throws errors like MissingSessionError or ForbiddenError on validation failures.
|
|
171
152
|
*/
|
|
172
153
|
const executeRouteWithMiddleware = async (services, matchedRoute, http, options) => {
|
|
173
|
-
const { matchedPath, params, route, middleware, meta } = matchedRoute;
|
|
154
|
+
const { matchedPath, params, route, httpMiddleware, middleware, meta } = matchedRoute;
|
|
174
155
|
const { singletonServices, userSession, createSessionServices, skipUserSession, requestId, } = services;
|
|
175
156
|
const requiresSession = route.auth !== false;
|
|
176
157
|
let sessionServices;
|
|
@@ -231,7 +212,7 @@ const executeRouteWithMiddleware = async (services, matchedRoute, http, options)
|
|
|
231
212
|
channel,
|
|
232
213
|
});
|
|
233
214
|
};
|
|
234
|
-
result = await runPikkuFunc(meta.pikkuFuncName, {
|
|
215
|
+
result = await runPikkuFunc(PikkuWiringTypes.http, `${meta.method}:${meta.route}`, meta.pikkuFuncName, {
|
|
235
216
|
getAllServices,
|
|
236
217
|
session,
|
|
237
218
|
data,
|
|
@@ -250,8 +231,16 @@ const executeRouteWithMiddleware = async (services, matchedRoute, http, options)
|
|
|
250
231
|
// TODO: Evaluate if the response stream should be explicitly ended.
|
|
251
232
|
// http?.response?.end()
|
|
252
233
|
};
|
|
234
|
+
// Get function config for middleware and tags
|
|
235
|
+
const funcConfig = pikkuState('function', 'functions').get(meta.pikkuFuncName);
|
|
253
236
|
// Execute middleware, then run the main logic
|
|
254
|
-
await runMiddleware({ ...singletonServices, userSession }, { http },
|
|
237
|
+
await runMiddleware({ ...singletonServices, userSession }, { http }, combineMiddleware(PikkuWiringTypes.http, `${meta.method}:${meta.route}`, {
|
|
238
|
+
httpMiddleware,
|
|
239
|
+
wiringMiddleware: middleware,
|
|
240
|
+
wiringTags: route.tags,
|
|
241
|
+
funcMiddleware: funcConfig?.middleware,
|
|
242
|
+
funcTags: funcConfig?.tags,
|
|
243
|
+
}), runMain);
|
|
255
244
|
return sessionServices ? { result, sessionServices } : { result };
|
|
256
245
|
};
|
|
257
246
|
/**
|
|
@@ -343,7 +332,8 @@ export const fetchData = async (request, response, { singletonServices, createSe
|
|
|
343
332
|
}, ignoreMiddleware
|
|
344
333
|
? {
|
|
345
334
|
...matchedRoute,
|
|
346
|
-
middleware:
|
|
335
|
+
middleware: undefined,
|
|
336
|
+
httpMiddleware: undefined,
|
|
347
337
|
}
|
|
348
338
|
: matchedRoute, http, { coerceDataFromSchema }));
|
|
349
339
|
return result;
|
|
@@ -46,8 +46,8 @@ export type CoreHTTPFunction = {
|
|
|
46
46
|
/**
|
|
47
47
|
* Represents a http interaction within Pikku, including a request and response.
|
|
48
48
|
*/
|
|
49
|
-
export interface PikkuHTTP {
|
|
50
|
-
request?: PikkuHTTPRequest
|
|
49
|
+
export interface PikkuHTTP<In = unknown> {
|
|
50
|
+
request?: PikkuHTTPRequest<In>;
|
|
51
51
|
response?: PikkuHTTPResponse;
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
@@ -147,7 +147,7 @@ export type HTTPWiringMeta = {
|
|
|
147
147
|
tags?: string[];
|
|
148
148
|
sse?: true;
|
|
149
149
|
};
|
|
150
|
-
export type HTTPWiringsMeta =
|
|
150
|
+
export type HTTPWiringsMeta = Record<HTTPMethod, Record<string, HTTPWiringMeta>>;
|
|
151
151
|
export type HTTPFunctionsMeta = Array<{
|
|
152
152
|
name: string;
|
|
153
153
|
inputs: string[] | null;
|
|
@@ -169,12 +169,12 @@ export interface PikkuHTTPRequest<In = unknown> {
|
|
|
169
169
|
setParams(params: Record<string, string | string[] | undefined>): void;
|
|
170
170
|
query(): PikkuQuery;
|
|
171
171
|
}
|
|
172
|
-
export interface PikkuHTTPResponse {
|
|
172
|
+
export interface PikkuHTTPResponse<Out = unknown> {
|
|
173
173
|
status(code: number): this;
|
|
174
174
|
cookie(name: string, value: string | null, options: SerializeOptions): this;
|
|
175
175
|
header(name: string, value: string | string[]): this;
|
|
176
176
|
arrayBuffer(data: ArrayBuffer | ArrayBufferView | Blob | string | FormData | URLSearchParams | ReadableStream): this;
|
|
177
|
-
json(data:
|
|
177
|
+
json(data: Out): this;
|
|
178
178
|
redirect(location: string, status?: number): this;
|
|
179
179
|
close?: () => void;
|
|
180
180
|
setMode?: (mode: 'stream') => void;
|
|
@@ -48,6 +48,9 @@ export class PikkuFetchHTTPRequest {
|
|
|
48
48
|
* @returns An object containing the cookies.
|
|
49
49
|
*/
|
|
50
50
|
cookie(cookieName) {
|
|
51
|
+
if (this.#cookies?.[cookieName]) {
|
|
52
|
+
return this.#cookies[cookieName];
|
|
53
|
+
}
|
|
51
54
|
const cookieHeader = this.header('cookie');
|
|
52
55
|
this.#cookies = cookieHeader ? parseCookie(cookieHeader) : {};
|
|
53
56
|
return this.#cookies[cookieName] || null;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { CorePikkuMiddleware } from '../../../types/core.types.js';
|
|
2
|
+
import { HTTPMethod } from '../http.types.js';
|
|
3
|
+
import { PathToRegexRouter } from './path-to-regex.js';
|
|
4
|
+
export type MatchResult = {
|
|
5
|
+
route: string;
|
|
6
|
+
params: Record<string, any>;
|
|
7
|
+
middleware?: CorePikkuMiddleware[];
|
|
8
|
+
} | null;
|
|
9
|
+
export interface Router {
|
|
10
|
+
match(method: HTTPMethod, path: string): MatchResult;
|
|
11
|
+
}
|
|
12
|
+
export declare const httpRouter: PathToRegexRouter;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { MatchResult, Router } from './http-router.js';
|
|
2
|
+
import { HTTPMethod } from '../http.types.js';
|
|
3
|
+
export declare class PathToRegexRouter implements Router {
|
|
4
|
+
private compiledRoutes;
|
|
5
|
+
private staticRoutes;
|
|
6
|
+
private precompiledMiddleware;
|
|
7
|
+
private isInitialized;
|
|
8
|
+
initialize(): void;
|
|
9
|
+
match(method: HTTPMethod, path: string): MatchResult;
|
|
10
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { match } from 'path-to-regexp';
|
|
2
|
+
import { pikkuState } from '../../../pikku-state.js';
|
|
3
|
+
export class PathToRegexRouter {
|
|
4
|
+
compiledRoutes = new Map();
|
|
5
|
+
staticRoutes = new Map();
|
|
6
|
+
precompiledMiddleware = new Map();
|
|
7
|
+
isInitialized = false;
|
|
8
|
+
initialize() {
|
|
9
|
+
const routes = pikkuState('http', 'routes');
|
|
10
|
+
const channelRoutes = pikkuState('channel', 'channels');
|
|
11
|
+
const middlewareMap = pikkuState('http', 'middleware');
|
|
12
|
+
// Precompile middleware lookups
|
|
13
|
+
for (const [middlewareRoute, middlewareArray] of middlewareMap.entries()) {
|
|
14
|
+
this.precompiledMiddleware.set(middlewareRoute, middlewareArray);
|
|
15
|
+
}
|
|
16
|
+
// Helper function to compile routes for a given method
|
|
17
|
+
const compileRoutesForMethod = (method, routeEntries) => {
|
|
18
|
+
const methodCompiledRoutes = this.compiledRoutes.get(method) || new Map();
|
|
19
|
+
const methodStaticRoutes = this.staticRoutes.get(method) || new Map();
|
|
20
|
+
for (const [routePath] of routeEntries) {
|
|
21
|
+
// Normalize route path - ensure it starts with /
|
|
22
|
+
const normalizedRoutePath = routePath.startsWith('/')
|
|
23
|
+
? routePath
|
|
24
|
+
: `/${routePath}`;
|
|
25
|
+
// Check if route is static (no parameters or wildcards)
|
|
26
|
+
const isStaticRoute = !/\*|:/.test(normalizedRoutePath);
|
|
27
|
+
// Precompute middleware for this route
|
|
28
|
+
const routeMiddleware = [];
|
|
29
|
+
// Add global middleware (*)
|
|
30
|
+
const globalMiddleware = this.precompiledMiddleware.get('*');
|
|
31
|
+
if (globalMiddleware) {
|
|
32
|
+
routeMiddleware.push(...globalMiddleware);
|
|
33
|
+
}
|
|
34
|
+
// Add route-specific middleware
|
|
35
|
+
for (const [middlewareRoute, middlewareArray,] of this.precompiledMiddleware.entries()) {
|
|
36
|
+
if (middlewareRoute !== '*') {
|
|
37
|
+
// Use regex test for pattern matching
|
|
38
|
+
try {
|
|
39
|
+
if (new RegExp(middlewareRoute).test(normalizedRoutePath)) {
|
|
40
|
+
routeMiddleware.push(...middlewareArray);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// If regex is invalid, do exact match
|
|
45
|
+
if (middlewareRoute === normalizedRoutePath) {
|
|
46
|
+
routeMiddleware.push(...middlewareArray);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (isStaticRoute) {
|
|
52
|
+
// Store static routes for O(1) lookup
|
|
53
|
+
methodStaticRoutes.set(normalizedRoutePath, {
|
|
54
|
+
route: routePath, // Keep the original route path for lookup in pikkuState
|
|
55
|
+
middleware: routeMiddleware,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
// Compile dynamic routes with path-to-regexp
|
|
60
|
+
const matcher = match(normalizedRoutePath, {
|
|
61
|
+
decode: decodeURIComponent,
|
|
62
|
+
});
|
|
63
|
+
methodCompiledRoutes.set(normalizedRoutePath, {
|
|
64
|
+
matcher,
|
|
65
|
+
route: routePath, // Keep the original route path for lookup in pikkuState
|
|
66
|
+
middleware: routeMiddleware,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
this.compiledRoutes.set(method, methodCompiledRoutes);
|
|
71
|
+
this.staticRoutes.set(method, methodStaticRoutes);
|
|
72
|
+
};
|
|
73
|
+
// Precompile all HTTP route matchers
|
|
74
|
+
for (const [method, routeMap] of routes.entries()) {
|
|
75
|
+
compileRoutesForMethod(method, routeMap.entries());
|
|
76
|
+
}
|
|
77
|
+
// Precompile all channel route matchers (treating them as GET routes for WebSocket upgrades)
|
|
78
|
+
const channelRoutesArray = Array.from(channelRoutes.entries()).map(([, channel]) => [channel.route, channel]);
|
|
79
|
+
compileRoutesForMethod('get', channelRoutesArray);
|
|
80
|
+
this.isInitialized = true;
|
|
81
|
+
}
|
|
82
|
+
match(method, path) {
|
|
83
|
+
if (!this.isInitialized) {
|
|
84
|
+
this.initialize();
|
|
85
|
+
}
|
|
86
|
+
// Normalize path - ensure it starts with /
|
|
87
|
+
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
|
88
|
+
// First, try static routes for O(1) lookup
|
|
89
|
+
const methodStaticRoutes = this.staticRoutes.get(method);
|
|
90
|
+
if (methodStaticRoutes) {
|
|
91
|
+
const staticRoute = methodStaticRoutes.get(normalizedPath);
|
|
92
|
+
if (staticRoute) {
|
|
93
|
+
return {
|
|
94
|
+
route: staticRoute.route,
|
|
95
|
+
params: {},
|
|
96
|
+
middleware: staticRoute.middleware,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// If no static route matched, try dynamic routes
|
|
101
|
+
const methodRoutes = this.compiledRoutes.get(method);
|
|
102
|
+
if (!methodRoutes) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
// Try each compiled route for this method
|
|
106
|
+
for (const [, compiledRoute] of methodRoutes.entries()) {
|
|
107
|
+
const result = compiledRoute.matcher(normalizedPath);
|
|
108
|
+
if (result) {
|
|
109
|
+
return {
|
|
110
|
+
route: compiledRoute.route,
|
|
111
|
+
params: result.params,
|
|
112
|
+
middleware: compiledRoute.middleware,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type CoreServices, type CoreSingletonServices, type CoreUserSession, type CreateSessionServices } from '../../types/core.types.js';
|
|
2
2
|
import type { CoreMCPResource, CoreMCPTool, CoreMCPPrompt, JsonRpcRequest, JsonRpcResponse, JsonRpcErrorResponse, PikkuMCP } from './mcp.types.js';
|
|
3
3
|
import type { CorePikkuFunctionSessionless } from '../../function/functions.types.js';
|
|
4
4
|
export declare class MCPError extends Error {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { PikkuWiringTypes, } from '../../types/core.types.js';
|
|
1
2
|
import { getErrorResponse } 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
7
|
import { BadRequestError, NotFoundError } from '../../errors/errors.js';
|
|
7
|
-
import {
|
|
8
|
+
import { combineMiddleware, runMiddleware } from '../../middleware-runner.js';
|
|
8
9
|
export class MCPError extends Error {
|
|
9
10
|
error;
|
|
10
11
|
constructor(error) {
|
|
@@ -132,15 +133,20 @@ async function runMCPPikkuFunc(request, type, name, mcp, pikkuFuncName, { sessio
|
|
|
132
133
|
mcp: interaction,
|
|
133
134
|
});
|
|
134
135
|
};
|
|
135
|
-
result = await runPikkuFunc(pikkuFuncName, {
|
|
136
|
+
result = await runPikkuFunc(PikkuWiringTypes.mcp, `${type}:${name}`, pikkuFuncName, {
|
|
136
137
|
getAllServices,
|
|
137
138
|
session,
|
|
138
139
|
data: request.params,
|
|
139
140
|
tags: mcp.tags,
|
|
140
141
|
});
|
|
141
142
|
};
|
|
142
|
-
|
|
143
|
-
await runMiddleware(singletonServices, { mcp: interaction },
|
|
143
|
+
const funcConfig = pikkuState('function', 'functions').get(pikkuFuncName);
|
|
144
|
+
await runMiddleware(singletonServices, { mcp: interaction }, combineMiddleware(PikkuWiringTypes.mcp, `${type}:${name}`, {
|
|
145
|
+
wiringMiddleware: mcp.middleware,
|
|
146
|
+
wiringTags: mcp.tags,
|
|
147
|
+
funcMiddleware: funcConfig?.middleware,
|
|
148
|
+
funcTags: funcConfig?.tags,
|
|
149
|
+
}), runMain);
|
|
144
150
|
return {
|
|
145
151
|
id: request.id,
|
|
146
152
|
result,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { getErrorResponse, PikkuError } from '../../errors/error-handler.js';
|
|
2
2
|
import { pikkuState } from '../../pikku-state.js';
|
|
3
3
|
import { addFunction, runPikkuFunc } from '../../function/function-runner.js';
|
|
4
|
-
import {
|
|
4
|
+
import { PikkuWiringTypes, } from '../../types/core.types.js';
|
|
5
|
+
import { combineMiddleware, runMiddleware } from '../../middleware-runner.js';
|
|
5
6
|
/**
|
|
6
7
|
* Error class for queue processor not found
|
|
7
8
|
*/
|
|
@@ -106,15 +107,22 @@ export async function runQueueJob({ singletonServices, createSessionServices, jo
|
|
|
106
107
|
: {}),
|
|
107
108
|
});
|
|
108
109
|
// Execute the pikku function with the job data
|
|
109
|
-
result = await runPikkuFunc(processorMeta.pikkuFuncName, {
|
|
110
|
+
result = await runPikkuFunc(PikkuWiringTypes.queue, job.queueName, processorMeta.pikkuFuncName, {
|
|
110
111
|
getAllServices,
|
|
111
112
|
data: job.data,
|
|
112
113
|
tags: queueWorker.tags,
|
|
113
114
|
});
|
|
114
115
|
logger.debug(`Successfully processed job ${job.id} in queue ${job.queueName}`);
|
|
115
116
|
};
|
|
117
|
+
// Get function config for middleware and tags
|
|
118
|
+
const funcConfig = pikkuState('function', 'functions').get(processorMeta.pikkuFuncName);
|
|
116
119
|
// Get middleware for tags and combine with queue-specific middleware
|
|
117
|
-
await runMiddleware(singletonServices, { queue },
|
|
120
|
+
await runMiddleware(singletonServices, { queue }, combineMiddleware(PikkuWiringTypes.queue, `${job.queueName}:${job.id}`, {
|
|
121
|
+
wiringMiddleware: queueWorker.middleware,
|
|
122
|
+
wiringTags: queueWorker.tags,
|
|
123
|
+
funcMiddleware: funcConfig?.middleware,
|
|
124
|
+
funcTags: funcConfig?.tags,
|
|
125
|
+
}), runMain);
|
|
118
126
|
return result;
|
|
119
127
|
}
|
|
120
128
|
catch (error) {
|
|
@@ -13,13 +13,12 @@ export declare class PikkuRPCService {
|
|
|
13
13
|
};
|
|
14
14
|
logger: import("../../index.js").Logger;
|
|
15
15
|
variables: import("../../services/variables-service.js").VariablesService;
|
|
16
|
-
http?: import("../http/http.types.js").PikkuHTTP | undefined;
|
|
16
|
+
http?: import("../http/http.types.js").PikkuHTTP<unknown> | undefined;
|
|
17
17
|
mcp?: import("../mcp/mcp.types.js").PikkuMCP;
|
|
18
18
|
rpc?: import("./rpc-types.js").PikkuRPC;
|
|
19
19
|
channel?: import("../channel/channel.types.js").PikkuChannel<unknown, unknown>;
|
|
20
20
|
scheduledTask?: import("../scheduler/scheduler.types.js").PikkuScheduledTask | undefined;
|
|
21
21
|
queue?: import("../queue/queue.types.js").PikkuQueue | undefined;
|
|
22
|
-
s?: any;
|
|
23
22
|
userSession?: import("../../index.js").UserSessionService<import("../../types/core.types.js").CoreUserSession> | undefined;
|
|
24
23
|
};
|
|
25
24
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PikkuWiringTypes } from '../../types/core.types.js';
|
|
1
2
|
import { runPikkuFunc } from '../../function/function-runner.js';
|
|
2
3
|
import { pikkuState } from '../../pikku-state.js';
|
|
3
4
|
import { ForbiddenError } from '../../errors/errors.js';
|
|
@@ -31,7 +32,7 @@ class ContextAwareRPCService {
|
|
|
31
32
|
const session = await this.services.userSession?.get();
|
|
32
33
|
const rpcDepth = this.services.rpc?.depth || 0;
|
|
33
34
|
const pikkuFuncName = getPikkuFunctionName(funcName);
|
|
34
|
-
return runPikkuFunc(pikkuFuncName, {
|
|
35
|
+
return runPikkuFunc(PikkuWiringTypes.rpc, pikkuFuncName, pikkuFuncName, {
|
|
35
36
|
getAllServices: () => {
|
|
36
37
|
this.services.rpc = this.services.rpc
|
|
37
38
|
? {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type CoreServices, type CoreSingletonServices, type CoreUserSession, type CreateSessionServices } from '../../types/core.types.js';
|
|
2
2
|
import type { CoreScheduledTask } from './scheduler.types.js';
|
|
3
3
|
import type { CorePikkuFunctionSessionless } from '../../function/functions.types.js';
|
|
4
4
|
export type RunScheduledTasksParams = {
|