@pikku/core 0.9.10 → 0.9.12-next.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 +12 -0
- package/dist/function/function-runner.d.ts +13 -8
- package/dist/function/function-runner.js +49 -37
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/middleware-runner.d.ts +61 -40
- package/dist/middleware-runner.js +118 -66
- package/dist/permissions.js +1 -0
- package/dist/pikku-state.d.ts +26 -1
- package/dist/pikku-state.js +10 -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/services/local-content.js +2 -2
- package/dist/types/core.types.d.ts +51 -2
- package/dist/types/core.types.js +21 -0
- package/dist/wirings/channel/channel-handler.d.ts +2 -2
- package/dist/wirings/channel/channel-handler.js +12 -9
- package/dist/wirings/channel/channel-runner.d.ts +0 -2
- package/dist/wirings/channel/channel-runner.js +2 -3
- package/dist/wirings/channel/channel.types.d.ts +5 -3
- 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 +80 -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 +32 -0
- package/dist/wirings/cli/cli-runner.js +328 -0
- package/dist/wirings/cli/cli.types.d.ts +177 -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 +373 -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 +29 -10
- package/dist/wirings/http/http-runner.js +103 -131
- package/dist/wirings/http/http.types.d.ts +10 -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-runner.d.ts +4 -5
- package/dist/wirings/mcp/mcp-runner.js +30 -34
- package/dist/wirings/mcp/mcp.types.d.ts +11 -8
- 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 +37 -35
- package/dist/wirings/scheduler/scheduler.types.d.ts +4 -3
- package/package.json +4 -3
- package/src/function/function-runner.test.ts +73 -23
- package/src/function/function-runner.ts +74 -55
- package/src/index.ts +8 -1
- package/src/middleware-runner.test.ts +24 -16
- package/src/middleware-runner.ts +136 -83
- package/src/permissions.ts +1 -0
- package/src/pikku-state.ts +47 -2
- package/src/services/content-service.ts +5 -4
- package/src/services/index.ts +0 -1
- package/src/services/local-content.ts +11 -6
- package/src/types/core.types.ts +74 -3
- package/src/wirings/channel/channel-handler.ts +9 -13
- package/src/wirings/channel/channel-runner.ts +2 -6
- package/src/wirings/channel/channel.types.ts +5 -1
- 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 +118 -0
- package/src/wirings/cli/channel/index.ts +1 -0
- package/src/wirings/cli/cli-runner.test.ts +382 -0
- package/src/wirings/cli/cli-runner.ts +503 -0
- package/src/wirings/cli/cli.types.ts +320 -0
- package/src/wirings/cli/command-parser.test.ts +440 -0
- package/src/wirings/cli/command-parser.ts +470 -0
- package/src/wirings/cli/index.ts +12 -0
- package/src/wirings/http/http-runner.test.ts +8 -7
- package/src/wirings/http/http-runner.ts +126 -159
- package/src/wirings/http/http.types.ts +56 -11
- package/src/wirings/http/index.ts +1 -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-runner.ts +56 -55
- package/src/wirings/mcp/mcp.types.ts +23 -8
- 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 +57 -56
- package/src/wirings/scheduler/scheduler.types.ts +9 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { PikkuWiringTypes, } from '../../types/core.types.js';
|
|
2
|
-
import {
|
|
2
|
+
import { NotFoundError } from '../../errors/errors.js';
|
|
3
3
|
import { closeSessionServices, createWeakUID, isSerializable, } from '../../utils.js';
|
|
4
|
-
import { PikkuUserSessionService
|
|
5
|
-
import { combineMiddleware, runMiddleware } from '../../middleware-runner.js';
|
|
4
|
+
import { PikkuUserSessionService } from '../../services/user-session-service.js';
|
|
6
5
|
import { handleHTTPError } from '../../handle-error.js';
|
|
7
6
|
import { pikkuState } from '../../pikku-state.js';
|
|
8
7
|
import { PikkuFetchHTTPResponse } from './pikku-fetch-http-response.js';
|
|
@@ -11,33 +10,38 @@ import { addFunction, runPikkuFunc } from '../../function/function-runner.js';
|
|
|
11
10
|
import { rpcService } from '../rpc/rpc-runner.js';
|
|
12
11
|
import { httpRouter } from './routers/http-router.js';
|
|
13
12
|
/**
|
|
14
|
-
* Registers middleware
|
|
13
|
+
* Registers HTTP middleware for a specific route pattern.
|
|
15
14
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
15
|
+
* This function registers middleware at runtime that will be applied to
|
|
16
|
+
* HTTP routes matching the specified pattern.
|
|
17
|
+
*
|
|
18
|
+
* For tree-shaking benefits, wrap in a factory function:
|
|
19
|
+
* `export const x = () => addHTTPMiddleware('pattern', [...])`
|
|
19
20
|
*
|
|
20
21
|
* @template PikkuMiddleware The middleware type.
|
|
21
|
-
* @param {
|
|
22
|
-
* @param {
|
|
22
|
+
* @param {string} pattern - Route pattern (e.g., '*' for all routes, '/api/*' for specific routes).
|
|
23
|
+
* @param {CorePikkuMiddlewareGroup} middleware - Array of middleware for this route pattern.
|
|
24
|
+
*
|
|
25
|
+
* @returns {CorePikkuMiddlewareGroup} The middleware array (for chaining/wrapping).
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* // Recommended: tree-shakeable
|
|
30
|
+
* export const httpGlobal = () => addHTTPMiddleware('*', [
|
|
31
|
+
* corsMiddleware,
|
|
32
|
+
* loggingMiddleware
|
|
33
|
+
* ])
|
|
34
|
+
*
|
|
35
|
+
* // Also works: no tree-shaking
|
|
36
|
+
* export const apiMiddleware = addHTTPMiddleware('/api/*', [
|
|
37
|
+
* authMiddleware
|
|
38
|
+
* ])
|
|
39
|
+
* ```
|
|
23
40
|
*/
|
|
24
|
-
export const addHTTPMiddleware = (
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
route = routeOrMiddleware;
|
|
29
|
-
middleware = middleware;
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
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);
|
|
40
|
-
}
|
|
41
|
+
export const addHTTPMiddleware = (pattern, middleware) => {
|
|
42
|
+
const httpGroups = pikkuState('middleware', 'httpGroup');
|
|
43
|
+
httpGroups[pattern] = middleware;
|
|
44
|
+
return middleware;
|
|
41
45
|
};
|
|
42
46
|
/**
|
|
43
47
|
* Adds a new route to the global HTTP route registry.
|
|
@@ -92,8 +96,6 @@ const getMatchingRoute = (requestType, requestPath) => {
|
|
|
92
96
|
params: matchedPath.params,
|
|
93
97
|
route,
|
|
94
98
|
permissions: route.permissions,
|
|
95
|
-
httpMiddleware: matchedPath.middleware,
|
|
96
|
-
middleware: route.middleware,
|
|
97
99
|
meta: meta,
|
|
98
100
|
};
|
|
99
101
|
}
|
|
@@ -122,16 +124,7 @@ export const createHTTPInteraction = (request, response) => {
|
|
|
122
124
|
return http;
|
|
123
125
|
};
|
|
124
126
|
/**
|
|
125
|
-
* Validates the input data and executes the route handler
|
|
126
|
-
*
|
|
127
|
-
* NOTE: HTTP wiring handles middleware differently from other wirings (RPC, MCP, Queue, etc.)
|
|
128
|
-
* because HTTP needs to:
|
|
129
|
-
* 1. Check session early for performance (before expensive body parsing)
|
|
130
|
-
* 2. Handle HTTP-specific concerns (headers, cookies, SSE setup)
|
|
131
|
-
* 3. Process middleware that may set up authentication/session state
|
|
132
|
-
*
|
|
133
|
-
* Other wirings (RPC/MCP/Queue/Scheduler) simply pass middleware/permissions/auth
|
|
134
|
-
* directly to runPikkuFunc without processing them.
|
|
127
|
+
* Validates the input data and executes the route handler
|
|
135
128
|
*
|
|
136
129
|
* This function performs these steps:
|
|
137
130
|
* 1. Sets URL parameters on the request.
|
|
@@ -144,103 +137,90 @@ export const createHTTPInteraction = (request, response) => {
|
|
|
144
137
|
* 8. Sends the appropriate response.
|
|
145
138
|
*
|
|
146
139
|
* @param {Object} services - A collection of shared services and utilities.
|
|
147
|
-
* @param {Object} matchedRoute - Contains route details, URL parameters,
|
|
140
|
+
* @param {Object} matchedRoute - Contains route details, URL parameters, and optional schema.
|
|
148
141
|
* @param {PikkuHTTP | undefined} http - The HTTP interaction object.
|
|
149
142
|
* @param {Object} options - Options for route execution (e.g., whether to coerce query strings to arrays).
|
|
150
143
|
* @returns {Promise<any>} An object containing the route handler result and session services (if any).
|
|
151
144
|
* @throws Throws errors like MissingSessionError or ForbiddenError on validation failures.
|
|
152
145
|
*/
|
|
153
|
-
const
|
|
154
|
-
const
|
|
155
|
-
const {
|
|
146
|
+
const executeRoute = async (services, matchedRoute, http, options) => {
|
|
147
|
+
const userSession = new PikkuUserSessionService();
|
|
148
|
+
const { params, route, meta } = matchedRoute;
|
|
149
|
+
const { singletonServices, createSessionServices, skipUserSession, requestId, } = services;
|
|
156
150
|
const requiresSession = route.auth !== false;
|
|
157
151
|
let sessionServices;
|
|
158
152
|
let result;
|
|
159
153
|
// Attach URL parameters to the request object
|
|
160
154
|
http?.request?.setParams(params);
|
|
161
155
|
singletonServices.logger.info(`Matched route: ${route.route} | method: ${route.method.toUpperCase()} | auth: ${requiresSession.toString()}`);
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
156
|
+
// Ensure session is available when required
|
|
157
|
+
if (skipUserSession && requiresSession) {
|
|
158
|
+
throw new Error("Can't skip trying to get user session if auth is required");
|
|
159
|
+
}
|
|
160
|
+
const data = () => http.request.data();
|
|
161
|
+
let channel;
|
|
162
|
+
if (matchedRoute.route.sse) {
|
|
163
|
+
const response = http?.response;
|
|
164
|
+
if (!response) {
|
|
165
|
+
throw new Error('SSE requires a valid HTTP response object');
|
|
168
166
|
}
|
|
169
|
-
if (
|
|
170
|
-
|
|
171
|
-
action: 'Rejecting route (invalid session)',
|
|
172
|
-
path: matchedPath,
|
|
173
|
-
});
|
|
174
|
-
throw new MissingSessionError();
|
|
167
|
+
if (!response.setMode) {
|
|
168
|
+
throw new Error('Response object does not support SSE mode');
|
|
175
169
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
response.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
channel = {
|
|
193
|
-
channelId: requestId,
|
|
194
|
-
openingData: data,
|
|
195
|
-
send: (data) => {
|
|
196
|
-
response.arrayBuffer(isSerializable(data) ? JSON.stringify(data) : data);
|
|
197
|
-
},
|
|
198
|
-
close: () => {
|
|
199
|
-
channel.state = 'closed';
|
|
200
|
-
response.close?.();
|
|
201
|
-
},
|
|
202
|
-
state: 'open',
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
// Create session-specific services for handling the request
|
|
206
|
-
sessionServices = await createSessionServices({ ...singletonServices, userSession, channel }, { http }, session);
|
|
207
|
-
return rpcService.injectRPCService({
|
|
208
|
-
...singletonServices,
|
|
209
|
-
...sessionServices,
|
|
210
|
-
http,
|
|
211
|
-
userSession,
|
|
212
|
-
channel,
|
|
213
|
-
});
|
|
170
|
+
response.setMode('stream');
|
|
171
|
+
response.header('Content-Type', 'text/event-stream');
|
|
172
|
+
response.header('Cache-Control', 'no-cache');
|
|
173
|
+
response.header('Connection', 'keep-alive');
|
|
174
|
+
response.header('Transfer-Encoding', 'chunked');
|
|
175
|
+
channel = {
|
|
176
|
+
channelId: requestId,
|
|
177
|
+
openingData: await data(),
|
|
178
|
+
send: (data) => {
|
|
179
|
+
response.arrayBuffer(isSerializable(data) ? JSON.stringify(data) : data);
|
|
180
|
+
},
|
|
181
|
+
close: () => {
|
|
182
|
+
channel.state = 'closed';
|
|
183
|
+
response.close?.();
|
|
184
|
+
},
|
|
185
|
+
state: 'open',
|
|
214
186
|
};
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
http?.response?.json(result);
|
|
229
|
-
}
|
|
230
|
-
http?.response?.status(200);
|
|
231
|
-
// TODO: Evaluate if the response stream should be explicitly ended.
|
|
232
|
-
// http?.response?.end()
|
|
187
|
+
}
|
|
188
|
+
const interaction = { http, channel };
|
|
189
|
+
const getAllServices = async (session) => {
|
|
190
|
+
let channel;
|
|
191
|
+
// Create session-specific services for handling the request
|
|
192
|
+
sessionServices = await createSessionServices({ ...singletonServices, userSession, channel }, { http }, session);
|
|
193
|
+
return rpcService.injectRPCService({
|
|
194
|
+
...singletonServices,
|
|
195
|
+
...sessionServices,
|
|
196
|
+
http,
|
|
197
|
+
userSession,
|
|
198
|
+
channel,
|
|
199
|
+
}, interaction, route.auth);
|
|
233
200
|
};
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
201
|
+
result = await runPikkuFunc(PikkuWiringTypes.http, `${meta.method}:${meta.route}`, meta.pikkuFuncName, {
|
|
202
|
+
singletonServices,
|
|
203
|
+
getAllServices,
|
|
204
|
+
auth: route.auth !== false,
|
|
205
|
+
userSession,
|
|
206
|
+
data,
|
|
207
|
+
permissions: route.permissions,
|
|
208
|
+
inheritedMiddleware: meta.middleware,
|
|
209
|
+
wireMiddleware: route.middleware,
|
|
210
|
+
coerceDataFromSchema: options.coerceDataFromSchema,
|
|
211
|
+
tags: route.tags,
|
|
212
|
+
interaction,
|
|
213
|
+
});
|
|
214
|
+
// Respond with either a binary or JSON response based on configuration
|
|
215
|
+
if (route.returnsJSON === false) {
|
|
216
|
+
http?.response?.arrayBuffer(result);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
http?.response?.json(result);
|
|
220
|
+
}
|
|
221
|
+
http?.response?.status(200);
|
|
222
|
+
// TODO: Evaluate if the response stream should be explicitly ended.
|
|
223
|
+
// http?.response?.end()
|
|
244
224
|
return sessionServices ? { result, sessionServices } : { result };
|
|
245
225
|
};
|
|
246
226
|
/**
|
|
@@ -298,11 +278,10 @@ export const pikkuFetch = async (request, params) => {
|
|
|
298
278
|
* @param {RunHTTPWiringOptions & RunHTTPWiringParams} options - Options such as singleton services, session handling, and error configuration.
|
|
299
279
|
* @returns {Promise<Out | void>} The output from the route handler or void if an error occurred.
|
|
300
280
|
*/
|
|
301
|
-
export const fetchData = async (request, response, { singletonServices, createSessionServices, skipUserSession = false, respondWith404 = true, logWarningsForStatusCodes = [], coerceDataFromSchema = true, bubbleErrors = false, generateRequestId,
|
|
281
|
+
export const fetchData = async (request, response, { singletonServices, createSessionServices, skipUserSession = false, respondWith404 = true, logWarningsForStatusCodes = [], coerceDataFromSchema = true, bubbleErrors = false, generateRequestId, }) => {
|
|
302
282
|
const requestId = request.getHeader?.('x-request-id') ||
|
|
303
283
|
generateRequestId?.() ||
|
|
304
284
|
createWeakUID();
|
|
305
|
-
const userSession = new PikkuUserSessionService();
|
|
306
285
|
let sessionServices;
|
|
307
286
|
let result;
|
|
308
287
|
// Combine the request and response into one interaction object
|
|
@@ -323,19 +302,12 @@ export const fetchData = async (request, response, { singletonServices, createSe
|
|
|
323
302
|
}
|
|
324
303
|
// Execute the matched route along with its middleware and session management
|
|
325
304
|
;
|
|
326
|
-
({ result, sessionServices } = await
|
|
305
|
+
({ result, sessionServices } = await executeRoute({
|
|
327
306
|
singletonServices,
|
|
328
|
-
userSession,
|
|
329
307
|
createSessionServices,
|
|
330
308
|
skipUserSession,
|
|
331
309
|
requestId,
|
|
332
|
-
},
|
|
333
|
-
? {
|
|
334
|
-
...matchedRoute,
|
|
335
|
-
middleware: undefined,
|
|
336
|
-
httpMiddleware: undefined,
|
|
337
|
-
}
|
|
338
|
-
: matchedRoute, http, { coerceDataFromSchema }));
|
|
310
|
+
}, matchedRoute, http, { coerceDataFromSchema }));
|
|
339
311
|
return result;
|
|
340
312
|
}
|
|
341
313
|
catch (e) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { SerializeOptions } from 'cookie';
|
|
2
2
|
import type { PikkuError } from '../../errors/error-handler.js';
|
|
3
|
-
import type { PikkuDocs, CoreServices, CoreSingletonServices, CoreUserSession, CreateSessionServices, CorePikkuMiddleware } from '../../types/core.types.js';
|
|
4
|
-
import type { CorePikkuFunction, CorePikkuFunctionSessionless, CorePikkuPermission, CorePermissionGroup } from '../../function/functions.types.js';
|
|
3
|
+
import type { PikkuDocs, CoreServices, CoreSingletonServices, CoreUserSession, CreateSessionServices, CorePikkuMiddleware, MiddlewareMetadata } from '../../types/core.types.js';
|
|
4
|
+
import type { CorePikkuFunction, CorePikkuFunctionSessionless, CorePikkuPermission, CorePermissionGroup, CorePikkuFunctionConfig } from '../../function/functions.types.js';
|
|
5
5
|
type ExtractHTTPWiringParams<S extends string> = S extends `${string}:${infer Param}/${infer Rest}` ? Param | ExtractHTTPWiringParams<`/${Rest}`> : S extends `${string}:${infer Param}` ? Param : never;
|
|
6
6
|
export type AssertHTTPWiringParams<In, HTTPWiring extends string> = ExtractHTTPWiringParams<HTTPWiring> extends keyof In ? unknown : [
|
|
7
7
|
'Error: HTTPWiring parameters',
|
|
@@ -16,7 +16,6 @@ export type RunHTTPWiringOptions = Partial<{
|
|
|
16
16
|
coerceDataFromSchema: boolean;
|
|
17
17
|
bubbleErrors: boolean;
|
|
18
18
|
generateRequestId: () => string;
|
|
19
|
-
ignoreMiddleware: boolean;
|
|
20
19
|
}>;
|
|
21
20
|
export type RunHTTPWiringParams = {
|
|
22
21
|
singletonServices: CoreSingletonServices;
|
|
@@ -68,10 +67,10 @@ export type PikkuQuery<T = Record<string, string | undefined>> = Record<string,
|
|
|
68
67
|
* @template PikkuFunctionSessionless - The sessionless API function type, defaults to `CorePikkuFunctionSessionless`.
|
|
69
68
|
* @template PikkuPermission - The permission function type, defaults to `CorePikkuPermission`.
|
|
70
69
|
*/
|
|
71
|
-
export type CoreHTTPFunctionWiring<In, Out, R extends string, PikkuFunction = CorePikkuFunction<In, Out>, PikkuFunctionSessionless = CorePikkuFunctionSessionless<In, Out>, PikkuPermission = CorePikkuPermission<In>, PikkuMiddleware
|
|
70
|
+
export type CoreHTTPFunctionWiring<In, Out, R extends string, PikkuFunction extends CorePikkuFunction<In, Out, any, any, any> = CorePikkuFunction<In, Out>, PikkuFunctionSessionless extends CorePikkuFunctionSessionless<In, Out, any, any, any> = CorePikkuFunctionSessionless<In, Out>, PikkuPermission extends CorePikkuPermission<In, any, any> = CorePikkuPermission<In, any, any>, PikkuMiddleware extends CorePikkuMiddleware<any, any> = CorePikkuMiddleware<any>> = (CoreHTTPFunction & {
|
|
72
71
|
route: R;
|
|
73
72
|
method: HTTPMethod;
|
|
74
|
-
func: PikkuFunction
|
|
73
|
+
func: CorePikkuFunctionConfig<PikkuFunction, PikkuPermission, PikkuMiddleware>;
|
|
75
74
|
permissions?: CorePermissionGroup<PikkuPermission>;
|
|
76
75
|
auth?: true;
|
|
77
76
|
tags?: string[];
|
|
@@ -80,7 +79,7 @@ export type CoreHTTPFunctionWiring<In, Out, R extends string, PikkuFunction = Co
|
|
|
80
79
|
}) | (CoreHTTPFunction & {
|
|
81
80
|
route: R;
|
|
82
81
|
method: HTTPMethod;
|
|
83
|
-
func: PikkuFunctionSessionless
|
|
82
|
+
func: CorePikkuFunctionConfig<PikkuFunctionSessionless, PikkuPermission, PikkuMiddleware>;
|
|
84
83
|
permissions?: undefined;
|
|
85
84
|
auth?: false;
|
|
86
85
|
tags?: string[];
|
|
@@ -89,7 +88,7 @@ export type CoreHTTPFunctionWiring<In, Out, R extends string, PikkuFunction = Co
|
|
|
89
88
|
}) | (CoreHTTPFunction & {
|
|
90
89
|
route: R;
|
|
91
90
|
method: 'get';
|
|
92
|
-
func: PikkuFunction
|
|
91
|
+
func: CorePikkuFunctionConfig<PikkuFunction, PikkuPermission, PikkuMiddleware>;
|
|
93
92
|
permissions?: CorePermissionGroup<PikkuPermission>;
|
|
94
93
|
auth?: true;
|
|
95
94
|
sse?: boolean;
|
|
@@ -98,7 +97,7 @@ export type CoreHTTPFunctionWiring<In, Out, R extends string, PikkuFunction = Co
|
|
|
98
97
|
}) | (CoreHTTPFunction & {
|
|
99
98
|
route: R;
|
|
100
99
|
method: 'get';
|
|
101
|
-
func: PikkuFunctionSessionless
|
|
100
|
+
func: CorePikkuFunctionConfig<PikkuFunctionSessionless, PikkuPermission, PikkuMiddleware>;
|
|
102
101
|
permissions?: undefined;
|
|
103
102
|
auth?: false;
|
|
104
103
|
sse?: boolean;
|
|
@@ -107,7 +106,7 @@ export type CoreHTTPFunctionWiring<In, Out, R extends string, PikkuFunction = Co
|
|
|
107
106
|
}) | (CoreHTTPFunction & {
|
|
108
107
|
route: R;
|
|
109
108
|
method: 'post';
|
|
110
|
-
func: PikkuFunction
|
|
109
|
+
func: CorePikkuFunctionConfig<PikkuFunction, PikkuPermission, PikkuMiddleware>;
|
|
111
110
|
permissions?: CorePermissionGroup<PikkuPermission>;
|
|
112
111
|
auth?: true;
|
|
113
112
|
query?: Array<keyof In>;
|
|
@@ -117,7 +116,7 @@ export type CoreHTTPFunctionWiring<In, Out, R extends string, PikkuFunction = Co
|
|
|
117
116
|
}) | (CoreHTTPFunction & {
|
|
118
117
|
route: R;
|
|
119
118
|
method: 'post';
|
|
120
|
-
func: PikkuFunctionSessionless
|
|
119
|
+
func: CorePikkuFunctionConfig<PikkuFunctionSessionless, PikkuPermission, PikkuMiddleware>;
|
|
121
120
|
permissions?: undefined;
|
|
122
121
|
auth?: false;
|
|
123
122
|
query?: Array<keyof In>;
|
|
@@ -146,6 +145,7 @@ export type HTTPWiringMeta = {
|
|
|
146
145
|
docs?: PikkuDocs;
|
|
147
146
|
tags?: string[];
|
|
148
147
|
sse?: true;
|
|
148
|
+
middleware?: MiddlewareMetadata[];
|
|
149
149
|
};
|
|
150
150
|
export type HTTPWiringsMeta = Record<HTTPMethod, Record<string, HTTPWiringMeta>>;
|
|
151
151
|
export type HTTPFunctionsMeta = Array<{
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from './pikku-fetch-http-request.js';
|
|
2
2
|
export * from './pikku-fetch-http-response.js';
|
|
3
3
|
export * from './log-http-routes.js';
|
|
4
|
-
export { fetch, fetchData, wireHTTP } from './http-runner.js';
|
|
4
|
+
export { fetch, fetchData, wireHTTP, addHTTPMiddleware } from './http-runner.js';
|
|
5
5
|
export type * from './http.types.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export * from './pikku-fetch-http-request.js';
|
|
2
2
|
export * from './pikku-fetch-http-response.js';
|
|
3
3
|
export * from './log-http-routes.js';
|
|
4
|
-
export { fetch, fetchData, wireHTTP } from './http-runner.js';
|
|
4
|
+
export { fetch, fetchData, wireHTTP, addHTTPMiddleware } from './http-runner.js';
|
|
@@ -88,7 +88,9 @@ export class PikkuFetchHTTPResponse {
|
|
|
88
88
|
}
|
|
89
89
|
toResponse(args) {
|
|
90
90
|
const cookieHeader = Array.from(this.#cookies.entries()).map(([name, { value, flags }]) => serializeCookie(name, value, flags));
|
|
91
|
-
|
|
91
|
+
// Multiple Set-Cookie headers must be appended separately, not joined with commas
|
|
92
|
+
// per HTTP specification (RFC 6265)
|
|
93
|
+
cookieHeader.forEach((cookie) => this.#headers.append('Set-Cookie', cookie));
|
|
92
94
|
return new Response(this.#body, {
|
|
93
95
|
...args,
|
|
94
96
|
status: this.#statusCode,
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { CorePikkuMiddleware } from '../../../types/core.types.js';
|
|
2
1
|
import { HTTPMethod } from '../http.types.js';
|
|
3
2
|
import { PathToRegexRouter } from './path-to-regex.js';
|
|
4
3
|
export type MatchResult = {
|
|
5
4
|
route: string;
|
|
6
5
|
params: Record<string, any>;
|
|
7
|
-
middleware?: CorePikkuMiddleware[];
|
|
8
6
|
} | null;
|
|
9
7
|
export interface Router {
|
|
10
8
|
match(method: HTTPMethod, path: string): MatchResult;
|
|
@@ -3,8 +3,8 @@ import { HTTPMethod } from '../http.types.js';
|
|
|
3
3
|
export declare class PathToRegexRouter implements Router {
|
|
4
4
|
private compiledRoutes;
|
|
5
5
|
private staticRoutes;
|
|
6
|
-
private precompiledMiddleware;
|
|
7
6
|
private isInitialized;
|
|
7
|
+
reset(): void;
|
|
8
8
|
initialize(): void;
|
|
9
9
|
match(method: HTTPMethod, path: string): MatchResult;
|
|
10
10
|
}
|
|
@@ -3,16 +3,15 @@ import { pikkuState } from '../../../pikku-state.js';
|
|
|
3
3
|
export class PathToRegexRouter {
|
|
4
4
|
compiledRoutes = new Map();
|
|
5
5
|
staticRoutes = new Map();
|
|
6
|
-
precompiledMiddleware = new Map();
|
|
7
6
|
isInitialized = false;
|
|
7
|
+
reset() {
|
|
8
|
+
this.compiledRoutes = new Map();
|
|
9
|
+
this.staticRoutes = new Map();
|
|
10
|
+
this.isInitialized = false;
|
|
11
|
+
}
|
|
8
12
|
initialize() {
|
|
9
13
|
const routes = pikkuState('http', 'routes');
|
|
10
14
|
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
15
|
// Helper function to compile routes for a given method
|
|
17
16
|
const compileRoutesForMethod = (method, routeEntries) => {
|
|
18
17
|
const methodCompiledRoutes = this.compiledRoutes.get(method) || new Map();
|
|
@@ -24,35 +23,10 @@ export class PathToRegexRouter {
|
|
|
24
23
|
: `/${routePath}`;
|
|
25
24
|
// Check if route is static (no parameters or wildcards)
|
|
26
25
|
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
26
|
if (isStaticRoute) {
|
|
52
27
|
// Store static routes for O(1) lookup
|
|
53
28
|
methodStaticRoutes.set(normalizedRoutePath, {
|
|
54
29
|
route: routePath, // Keep the original route path for lookup in pikkuState
|
|
55
|
-
middleware: routeMiddleware,
|
|
56
30
|
});
|
|
57
31
|
}
|
|
58
32
|
else {
|
|
@@ -63,7 +37,6 @@ export class PathToRegexRouter {
|
|
|
63
37
|
methodCompiledRoutes.set(normalizedRoutePath, {
|
|
64
38
|
matcher,
|
|
65
39
|
route: routePath, // Keep the original route path for lookup in pikkuState
|
|
66
|
-
middleware: routeMiddleware,
|
|
67
40
|
});
|
|
68
41
|
}
|
|
69
42
|
}
|
|
@@ -93,7 +66,6 @@ export class PathToRegexRouter {
|
|
|
93
66
|
return {
|
|
94
67
|
route: staticRoute.route,
|
|
95
68
|
params: {},
|
|
96
|
-
middleware: staticRoute.middleware,
|
|
97
69
|
};
|
|
98
70
|
}
|
|
99
71
|
}
|
|
@@ -109,7 +81,6 @@ export class PathToRegexRouter {
|
|
|
109
81
|
return {
|
|
110
82
|
route: compiledRoute.route,
|
|
111
83
|
params: result.params,
|
|
112
|
-
middleware: compiledRoute.middleware,
|
|
113
84
|
};
|
|
114
85
|
}
|
|
115
86
|
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
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
|
-
import type { CorePikkuFunctionSessionless } from '../../function/functions.types.js';
|
|
3
|
+
import type { CorePikkuFunctionConfig, CorePikkuFunctionSessionless } from '../../function/functions.types.js';
|
|
4
4
|
export declare class MCPError extends Error {
|
|
5
5
|
readonly error: JsonRpcErrorResponse;
|
|
6
6
|
constructor(error: JsonRpcErrorResponse);
|
|
7
7
|
}
|
|
8
8
|
export type RunMCPEndpointParams<Tools extends string = any> = {
|
|
9
|
-
session?: CoreUserSession;
|
|
10
9
|
singletonServices: CoreSingletonServices;
|
|
11
10
|
mcp?: PikkuMCP<Tools>;
|
|
12
11
|
createSessionServices?: CreateSessionServices<CoreSingletonServices, CoreServices<CoreSingletonServices>, CoreUserSession>;
|
|
@@ -16,9 +15,9 @@ export type JsonRpcError = {
|
|
|
16
15
|
message: string;
|
|
17
16
|
data?: any;
|
|
18
17
|
};
|
|
19
|
-
export declare const wireMCPResource: <
|
|
20
|
-
export declare const wireMCPTool: <
|
|
21
|
-
export declare const wireMCPPrompt: <
|
|
18
|
+
export declare const wireMCPResource: <PikkuFunctionConfig extends CorePikkuFunctionConfig<CorePikkuFunctionSessionless<any, any>> = CorePikkuFunctionConfig<CorePikkuFunctionSessionless<any, any>>>(mcpResource: CoreMCPResource<PikkuFunctionConfig>) => void;
|
|
19
|
+
export declare const wireMCPTool: <PikkuFunctionConfig extends CorePikkuFunctionConfig<CorePikkuFunctionSessionless<any, any>> = CorePikkuFunctionConfig<CorePikkuFunctionSessionless<any, any>>>(mcpTool: CoreMCPTool<PikkuFunctionConfig>) => void;
|
|
20
|
+
export declare const wireMCPPrompt: <PikkuFunctionConfig extends CorePikkuFunctionConfig<CorePikkuFunctionSessionless<any, any>> = CorePikkuFunctionConfig<CorePikkuFunctionSessionless<any, any>>>(mcpPrompt: CoreMCPPrompt<PikkuFunctionConfig>) => void;
|
|
22
21
|
export declare function runMCPResource(request: JsonRpcRequest, params: RunMCPEndpointParams, uri: string): Promise<JsonRpcResponse>;
|
|
23
22
|
export declare function runMCPTool(request: JsonRpcRequest, params: RunMCPEndpointParams, name: string): Promise<JsonRpcResponse>;
|
|
24
23
|
export declare function runMCPPrompt(request: JsonRpcRequest, params: RunMCPEndpointParams, name: string): Promise<JsonRpcResponse>;
|
|
@@ -5,7 +5,6 @@ import { pikkuState } from '../../pikku-state.js';
|
|
|
5
5
|
import { addFunction, runPikkuFunc } from '../../function/function-runner.js';
|
|
6
6
|
import { rpcService } from '../rpc/rpc-runner.js';
|
|
7
7
|
import { BadRequestError, NotFoundError } from '../../errors/errors.js';
|
|
8
|
-
import { combineMiddleware, runMiddleware } from '../../middleware-runner.js';
|
|
9
8
|
export class MCPError extends Error {
|
|
10
9
|
error;
|
|
11
10
|
constructor(error) {
|
|
@@ -21,7 +20,7 @@ export const wireMCPResource = (mcpResource) => {
|
|
|
21
20
|
if (!mcpResourceMeta) {
|
|
22
21
|
throw new Error(`MCP resource metadata not found for '${mcpResource.uri}'`);
|
|
23
22
|
}
|
|
24
|
-
addFunction(mcpResourceMeta.pikkuFuncName, mcpResource
|
|
23
|
+
addFunction(mcpResourceMeta.pikkuFuncName, mcpResource);
|
|
25
24
|
const resources = pikkuState('mcp', 'resources');
|
|
26
25
|
if (resources.has(mcpResource.uri)) {
|
|
27
26
|
throw new Error(`MCP resource already exists: ${mcpResource.uri}`);
|
|
@@ -101,7 +100,7 @@ export async function runMCPPrompt(request, params, name) {
|
|
|
101
100
|
/**
|
|
102
101
|
* JSON-RPC 2.0 compatible MCP endpoint runner
|
|
103
102
|
*/
|
|
104
|
-
async function runMCPPikkuFunc(request, type, name, mcp, pikkuFuncName, {
|
|
103
|
+
async function runMCPPikkuFunc(request, type, name, mcp, pikkuFuncName, { singletonServices, createSessionServices, mcp: mcpInteraction, }) {
|
|
105
104
|
let sessionServices;
|
|
106
105
|
try {
|
|
107
106
|
// Validate JSON-RPC request structure
|
|
@@ -115,38 +114,35 @@ async function runMCPPikkuFunc(request, type, name, mcp, pikkuFuncName, { sessio
|
|
|
115
114
|
throw new NotFoundError(`MCP '${type}' PikkuFunction Mapping not found for '${name}'`);
|
|
116
115
|
}
|
|
117
116
|
singletonServices.logger.debug(`Running MCP ${type}: ${name}`);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return rpcService.injectRPCService({
|
|
126
|
-
...singletonServices,
|
|
127
|
-
...services,
|
|
128
|
-
mcp: interaction,
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
return rpcService.injectRPCService({
|
|
132
|
-
...singletonServices,
|
|
133
|
-
mcp: interaction,
|
|
134
|
-
});
|
|
135
|
-
};
|
|
136
|
-
result = await runPikkuFunc(PikkuWiringTypes.mcp, `${type}:${name}`, pikkuFuncName, {
|
|
137
|
-
getAllServices,
|
|
138
|
-
session,
|
|
139
|
-
data: request.params,
|
|
140
|
-
tags: mcp.tags,
|
|
141
|
-
});
|
|
117
|
+
const interaction = { mcp: mcpInteraction };
|
|
118
|
+
const getAllServices = async () => {
|
|
119
|
+
sessionServices = await createSessionServices?.(singletonServices, interaction, undefined);
|
|
120
|
+
return rpcService.injectRPCService({
|
|
121
|
+
...singletonServices,
|
|
122
|
+
...sessionServices,
|
|
123
|
+
}, interaction);
|
|
142
124
|
};
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
125
|
+
// Get metadata for the MCP endpoint to access pre-resolved middleware
|
|
126
|
+
let meta;
|
|
127
|
+
if (type === 'resource') {
|
|
128
|
+
meta = pikkuState('mcp', 'resourcesMeta')[name];
|
|
129
|
+
}
|
|
130
|
+
else if (type === 'tool') {
|
|
131
|
+
meta = pikkuState('mcp', 'toolsMeta')[name];
|
|
132
|
+
}
|
|
133
|
+
else if (type === 'prompt') {
|
|
134
|
+
meta = pikkuState('mcp', 'promptsMeta')[name];
|
|
135
|
+
}
|
|
136
|
+
const result = await runPikkuFunc(PikkuWiringTypes.mcp, `${type}:${name}`, pikkuFuncName, {
|
|
137
|
+
singletonServices,
|
|
138
|
+
getAllServices,
|
|
139
|
+
userSession: undefined, // TODO
|
|
140
|
+
data: () => request.params,
|
|
141
|
+
inheritedMiddleware: meta?.middleware,
|
|
142
|
+
wireMiddleware: mcp.middleware,
|
|
143
|
+
tags: mcp.tags,
|
|
144
|
+
interaction,
|
|
145
|
+
});
|
|
150
146
|
return {
|
|
151
147
|
id: request.id,
|
|
152
148
|
result,
|