@pikku/core 0.9.11 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/errors/errors.d.ts +2 -0
  3. package/dist/errors/errors.js +6 -0
  4. package/dist/function/function-runner.d.ts +15 -9
  5. package/dist/function/function-runner.js +55 -37
  6. package/dist/function/functions.types.d.ts +67 -1
  7. package/dist/function/functions.types.js +43 -1
  8. package/dist/index.d.ts +2 -6
  9. package/dist/index.js +2 -6
  10. package/dist/middleware/auth-apikey.d.ts +19 -11
  11. package/dist/middleware/auth-apikey.js +33 -33
  12. package/dist/middleware/auth-bearer.d.ts +34 -7
  13. package/dist/middleware/auth-bearer.js +53 -36
  14. package/dist/middleware/auth-cookie.d.ts +30 -12
  15. package/dist/middleware/auth-cookie.js +51 -43
  16. package/dist/middleware/timeout.d.ts +6 -2
  17. package/dist/middleware/timeout.js +7 -9
  18. package/dist/middleware-runner.d.ts +46 -43
  19. package/dist/middleware-runner.js +100 -69
  20. package/dist/permissions.d.ts +82 -20
  21. package/dist/permissions.js +176 -62
  22. package/dist/pikku-state.d.ts +46 -1
  23. package/dist/pikku-state.js +16 -1
  24. package/dist/services/content-service.d.ts +2 -3
  25. package/dist/services/index.d.ts +0 -1
  26. package/dist/services/index.js +0 -1
  27. package/dist/services/local-content.d.ts +2 -4
  28. package/dist/types/core.types.d.ts +110 -4
  29. package/dist/types/core.types.js +44 -1
  30. package/dist/utils.d.ts +1 -1
  31. package/dist/wirings/channel/channel-handler.d.ts +2 -2
  32. package/dist/wirings/channel/channel-handler.js +16 -12
  33. package/dist/wirings/channel/channel-runner.d.ts +2 -2
  34. package/dist/wirings/channel/channel-runner.js +6 -24
  35. package/dist/wirings/channel/channel.types.d.ts +15 -14
  36. package/dist/wirings/channel/local/local-channel-runner.js +12 -12
  37. package/dist/wirings/channel/serverless/serverless-channel-runner.js +7 -8
  38. package/dist/wirings/cli/channel/cli-channel-runner.d.ts +12 -0
  39. package/dist/wirings/cli/channel/cli-channel-runner.js +83 -0
  40. package/dist/wirings/cli/channel/index.d.ts +1 -0
  41. package/dist/wirings/cli/channel/index.js +1 -0
  42. package/dist/wirings/cli/cli-runner.d.ts +42 -0
  43. package/dist/wirings/cli/cli-runner.js +352 -0
  44. package/dist/wirings/cli/cli.types.d.ts +204 -0
  45. package/dist/wirings/cli/cli.types.js +1 -0
  46. package/dist/wirings/cli/command-parser.d.ts +19 -0
  47. package/dist/wirings/cli/command-parser.js +403 -0
  48. package/dist/wirings/cli/index.d.ts +5 -0
  49. package/dist/wirings/cli/index.js +5 -0
  50. package/dist/wirings/http/http-runner.d.ts +59 -10
  51. package/dist/wirings/http/http-runner.js +138 -131
  52. package/dist/wirings/http/http.types.d.ts +11 -10
  53. package/dist/wirings/http/index.d.ts +1 -1
  54. package/dist/wirings/http/index.js +1 -1
  55. package/dist/wirings/http/pikku-fetch-http-response.js +3 -1
  56. package/dist/wirings/http/routers/http-router.d.ts +0 -2
  57. package/dist/wirings/http/routers/path-to-regex.d.ts +1 -1
  58. package/dist/wirings/http/routers/path-to-regex.js +5 -34
  59. package/dist/wirings/mcp/mcp-endpoint-registry.d.ts +1 -1
  60. package/dist/wirings/mcp/mcp-runner.d.ts +4 -5
  61. package/dist/wirings/mcp/mcp-runner.js +32 -34
  62. package/dist/wirings/mcp/mcp.types.d.ts +38 -14
  63. package/dist/wirings/queue/queue-runner.d.ts +2 -2
  64. package/dist/wirings/queue/queue-runner.js +25 -27
  65. package/dist/wirings/queue/queue.types.d.ts +6 -5
  66. package/dist/wirings/rpc/index.d.ts +1 -1
  67. package/dist/wirings/rpc/index.js +1 -1
  68. package/dist/wirings/rpc/rpc-runner.d.ts +6 -18
  69. package/dist/wirings/rpc/rpc-runner.js +13 -8
  70. package/dist/wirings/scheduler/scheduler-runner.d.ts +2 -2
  71. package/dist/wirings/scheduler/scheduler-runner.js +38 -35
  72. package/dist/wirings/scheduler/scheduler.types.d.ts +5 -4
  73. package/package.json +9 -7
  74. package/src/errors/errors.ts +6 -0
  75. package/src/factory-functions.test.ts +60 -1
  76. package/src/function/function-runner.test.ts +81 -26
  77. package/src/function/function-runner.ts +89 -57
  78. package/src/function/functions.types.ts +85 -2
  79. package/src/index.ts +6 -19
  80. package/src/middleware/auth-apikey.ts +42 -57
  81. package/src/middleware/auth-bearer.ts +66 -49
  82. package/src/middleware/auth-cookie.ts +63 -82
  83. package/src/middleware/timeout.ts +10 -14
  84. package/src/middleware-runner.test.ts +24 -16
  85. package/src/middleware-runner.ts +115 -87
  86. package/src/permissions.test.ts +26 -27
  87. package/src/permissions.ts +216 -104
  88. package/src/pikku-state.ts +79 -2
  89. package/src/services/content-service.ts +5 -4
  90. package/src/services/index.ts +0 -1
  91. package/src/services/local-content.ts +9 -4
  92. package/src/types/core.types.ts +143 -5
  93. package/src/utils.ts +1 -1
  94. package/src/wirings/channel/channel-handler.ts +18 -21
  95. package/src/wirings/channel/channel-runner.ts +19 -32
  96. package/src/wirings/channel/channel.types.ts +29 -16
  97. package/src/wirings/channel/local/local-channel-runner.ts +25 -15
  98. package/src/wirings/channel/serverless/serverless-channel-runner.ts +15 -17
  99. package/src/wirings/cli/channel/cli-channel-runner.ts +123 -0
  100. package/src/wirings/cli/channel/index.ts +1 -0
  101. package/src/wirings/cli/cli-runner.test.ts +403 -0
  102. package/src/wirings/cli/cli-runner.ts +539 -0
  103. package/src/wirings/cli/cli.types.ts +350 -0
  104. package/src/wirings/cli/command-parser.test.ts +445 -0
  105. package/src/wirings/cli/command-parser.ts +504 -0
  106. package/src/wirings/cli/index.ts +13 -0
  107. package/src/wirings/http/http-runner.test.ts +8 -7
  108. package/src/wirings/http/http-runner.ts +165 -158
  109. package/src/wirings/http/http.types.ts +58 -11
  110. package/src/wirings/http/index.ts +7 -1
  111. package/src/wirings/http/pikku-fetch-http-response.ts +3 -1
  112. package/src/wirings/http/routers/http-router.ts +0 -2
  113. package/src/wirings/http/routers/path-to-regex.test.ts +4 -5
  114. package/src/wirings/http/routers/path-to-regex.ts +6 -43
  115. package/src/wirings/mcp/mcp-endpoint-registry.ts +1 -1
  116. package/src/wirings/mcp/mcp-runner.ts +58 -55
  117. package/src/wirings/mcp/mcp.types.ts +65 -14
  118. package/src/wirings/queue/queue-runner.ts +44 -47
  119. package/src/wirings/queue/queue.types.ts +10 -6
  120. package/src/wirings/rpc/index.ts +1 -1
  121. package/src/wirings/rpc/rpc-runner.ts +27 -9
  122. package/src/wirings/scheduler/scheduler-runner.ts +58 -56
  123. package/src/wirings/scheduler/scheduler.types.ts +10 -3
  124. package/tsconfig.tsbuildinfo +1 -1
@@ -1,8 +1,7 @@
1
1
  import { PikkuWiringTypes, } from '../../types/core.types.js';
2
- import { MissingSessionError, NotFoundError } from '../../errors/errors.js';
2
+ import { NotFoundError } from '../../errors/errors.js';
3
3
  import { closeSessionServices, createWeakUID, isSerializable, } from '../../utils.js';
4
- import { PikkuUserSessionService, } from '../../services/user-session-service.js';
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,72 @@ 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 either globally or for a specific route.
13
+ * Registers HTTP middleware for a specific route pattern.
15
14
  *
16
- * When a string route pattern is provided along with middleware, the middleware
17
- * is applied only to that route. Otherwise, if an array is provided, it is treated
18
- * as global middleware (applied to all routes).
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 {PikkuMiddleware[] | string} routeOrMiddleware - Either a global middleware array or a route pattern string.
22
- * @param {PikkuMiddleware[]} [middleware] - The middleware array to apply when a route pattern is specified.
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 = (routeOrMiddleware, middleware) => {
25
- const middlewareStore = pikkuState('http', 'middleware');
26
- let route = '*';
27
- if (typeof routeOrMiddleware === 'string') {
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;
45
+ };
46
+ /**
47
+ * Registers HTTP permissions for a specific route pattern.
48
+ *
49
+ * This function registers permissions at runtime that will be applied to
50
+ * HTTP routes matching the specified pattern.
51
+ *
52
+ * For tree-shaking benefits, wrap in a factory function:
53
+ * `export const x = () => addHTTPPermission('pattern', [...])`
54
+ *
55
+ * @template PikkuPermission The permission type.
56
+ * @param {string} pattern - Route pattern (e.g., '*' for all routes, '/api/*' for specific routes).
57
+ * @param {CorePermissionGroup | CorePikkuPermission[]} permissions - Permissions for this route pattern.
58
+ *
59
+ * @returns {CorePermissionGroup | CorePikkuPermission[]} The permissions (for chaining/wrapping).
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * // Recommended: tree-shakeable
64
+ * export const httpGlobalPermissions = () => addHTTPPermission('*', [
65
+ * authenticatedPermission,
66
+ * rateLimitPermission
67
+ * ])
68
+ *
69
+ * // Also works: no tree-shaking
70
+ * export const apiPermissions = addHTTPPermission('/api/*', [
71
+ * adminPermission
72
+ * ])
73
+ * ```
74
+ */
75
+ export const addHTTPPermission = (pattern, permissions) => {
76
+ const httpGroups = pikkuState('permissions', 'httpGroup');
77
+ httpGroups[pattern] = permissions;
78
+ return permissions;
41
79
  };
42
80
  /**
43
81
  * Adds a new route to the global HTTP route registry.
@@ -92,8 +130,6 @@ const getMatchingRoute = (requestType, requestPath) => {
92
130
  params: matchedPath.params,
93
131
  route,
94
132
  permissions: route.permissions,
95
- httpMiddleware: matchedPath.middleware,
96
- middleware: route.middleware,
97
133
  meta: meta,
98
134
  };
99
135
  }
@@ -122,16 +158,7 @@ export const createHTTPInteraction = (request, response) => {
122
158
  return http;
123
159
  };
124
160
  /**
125
- * Validates the input data and executes the route handler with associated middleware.
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.
161
+ * Validates the input data and executes the route handler
135
162
  *
136
163
  * This function performs these steps:
137
164
  * 1. Sets URL parameters on the request.
@@ -144,103 +171,91 @@ export const createHTTPInteraction = (request, response) => {
144
171
  * 8. Sends the appropriate response.
145
172
  *
146
173
  * @param {Object} services - A collection of shared services and utilities.
147
- * @param {Object} matchedRoute - Contains route details, URL parameters, middleware, and optional schema.
174
+ * @param {Object} matchedRoute - Contains route details, URL parameters, and optional schema.
148
175
  * @param {PikkuHTTP | undefined} http - The HTTP interaction object.
149
176
  * @param {Object} options - Options for route execution (e.g., whether to coerce query strings to arrays).
150
177
  * @returns {Promise<any>} An object containing the route handler result and session services (if any).
151
178
  * @throws Throws errors like MissingSessionError or ForbiddenError on validation failures.
152
179
  */
153
- const executeRouteWithMiddleware = async (services, matchedRoute, http, options) => {
154
- const { matchedPath, params, route, httpMiddleware, middleware, meta } = matchedRoute;
155
- const { singletonServices, userSession, createSessionServices, skipUserSession, requestId, } = services;
180
+ const executeRoute = async (services, matchedRoute, http, options) => {
181
+ const userSession = new PikkuUserSessionService();
182
+ const { params, route, meta } = matchedRoute;
183
+ const { singletonServices, createSessionServices, skipUserSession, requestId, } = services;
156
184
  const requiresSession = route.auth !== false;
157
185
  let sessionServices;
158
186
  let result;
159
187
  // Attach URL parameters to the request object
160
188
  http?.request?.setParams(params);
161
189
  singletonServices.logger.info(`Matched route: ${route.route} | method: ${route.method.toUpperCase()} | auth: ${requiresSession.toString()}`);
162
- // Main route execution logic wrapped for middleware handling
163
- const runMain = async () => {
164
- const session = userSession.get();
165
- // Ensure session is available when required
166
- if (skipUserSession && requiresSession) {
167
- throw new Error("Can't skip trying to get user session if auth is required");
190
+ // Ensure session is available when required
191
+ if (skipUserSession && requiresSession) {
192
+ throw new Error("Can't skip trying to get user session if auth is required");
193
+ }
194
+ const data = () => http.request.data();
195
+ let channel;
196
+ if (matchedRoute.route.sse) {
197
+ const response = http?.response;
198
+ if (!response) {
199
+ throw new Error('SSE requires a valid HTTP response object');
168
200
  }
169
- if (requiresSession && !session) {
170
- singletonServices.logger.info({
171
- action: 'Rejecting route (invalid session)',
172
- path: matchedPath,
173
- });
174
- throw new MissingSessionError();
201
+ if (!response.setMode) {
202
+ throw new Error('Response object does not support SSE mode');
175
203
  }
176
- const data = await http.request.data();
177
- const getAllServices = async () => {
178
- let channel;
179
- if (matchedRoute.route.sse) {
180
- const response = http?.response;
181
- if (!response) {
182
- throw new Error('SSE requires a valid HTTP response object');
183
- }
184
- if (!response.setMode) {
185
- throw new Error('Response object does not support SSE mode');
186
- }
187
- response.setMode('stream');
188
- response.header('Content-Type', 'text/event-stream');
189
- response.header('Cache-Control', 'no-cache');
190
- response.header('Connection', 'keep-alive');
191
- response.header('Transfer-Encoding', 'chunked');
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
- });
204
+ response.setMode('stream');
205
+ response.header('Content-Type', 'text/event-stream');
206
+ response.header('Cache-Control', 'no-cache');
207
+ response.header('Connection', 'keep-alive');
208
+ response.header('Transfer-Encoding', 'chunked');
209
+ channel = {
210
+ channelId: requestId,
211
+ openingData: await data(),
212
+ send: (data) => {
213
+ response.arrayBuffer(isSerializable(data) ? JSON.stringify(data) : data);
214
+ },
215
+ close: () => {
216
+ channel.state = 'closed';
217
+ response.close?.();
218
+ },
219
+ state: 'open',
214
220
  };
215
- result = await runPikkuFunc(PikkuWiringTypes.http, `${meta.method}:${meta.route}`, meta.pikkuFuncName, {
216
- getAllServices,
217
- session,
218
- data,
219
- permissions: route.permissions,
220
- coerceDataFromSchema: options.coerceDataFromSchema,
221
- tags: route.tags,
222
- });
223
- // Respond with either a binary or JSON response based on configuration
224
- if (route.returnsJSON === false) {
225
- http?.response?.arrayBuffer(result);
226
- }
227
- else {
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()
221
+ }
222
+ const interaction = { http, channel };
223
+ const getAllServices = async (session) => {
224
+ let channel;
225
+ // Create session-specific services for handling the request
226
+ sessionServices = await createSessionServices({ ...singletonServices, userSession, channel }, { http }, session);
227
+ return rpcService.injectRPCService({
228
+ ...singletonServices,
229
+ ...sessionServices,
230
+ http,
231
+ userSession,
232
+ channel,
233
+ }, interaction, route.auth);
233
234
  };
234
- // Get function config for middleware and tags
235
- const funcConfig = pikkuState('function', 'functions').get(meta.pikkuFuncName);
236
- // Execute middleware, then run the main logic
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);
235
+ result = await runPikkuFunc(PikkuWiringTypes.http, `${meta.method}:${meta.route}`, meta.pikkuFuncName, {
236
+ singletonServices,
237
+ getAllServices,
238
+ auth: route.auth !== false,
239
+ userSession,
240
+ data,
241
+ inheritedMiddleware: meta.middleware,
242
+ wireMiddleware: route.middleware,
243
+ inheritedPermissions: meta.permissions,
244
+ wirePermissions: route.permissions,
245
+ coerceDataFromSchema: options.coerceDataFromSchema,
246
+ tags: route.tags,
247
+ interaction,
248
+ });
249
+ // Respond with either a binary or JSON response based on configuration
250
+ if (route.returnsJSON === false) {
251
+ http?.response?.arrayBuffer(result);
252
+ }
253
+ else {
254
+ http?.response?.json(result);
255
+ }
256
+ http?.response?.status(200);
257
+ // TODO: Evaluate if the response stream should be explicitly ended.
258
+ // http?.response?.end()
244
259
  return sessionServices ? { result, sessionServices } : { result };
245
260
  };
246
261
  /**
@@ -298,11 +313,10 @@ export const pikkuFetch = async (request, params) => {
298
313
  * @param {RunHTTPWiringOptions & RunHTTPWiringParams} options - Options such as singleton services, session handling, and error configuration.
299
314
  * @returns {Promise<Out | void>} The output from the route handler or void if an error occurred.
300
315
  */
301
- export const fetchData = async (request, response, { singletonServices, createSessionServices, skipUserSession = false, respondWith404 = true, logWarningsForStatusCodes = [], coerceDataFromSchema = true, bubbleErrors = false, generateRequestId, ignoreMiddleware = false, }) => {
316
+ export const fetchData = async (request, response, { singletonServices, createSessionServices, skipUserSession = false, respondWith404 = true, logWarningsForStatusCodes = [], coerceDataFromSchema = true, bubbleErrors = false, generateRequestId, }) => {
302
317
  const requestId = request.getHeader?.('x-request-id') ||
303
318
  generateRequestId?.() ||
304
319
  createWeakUID();
305
- const userSession = new PikkuUserSessionService();
306
320
  let sessionServices;
307
321
  let result;
308
322
  // Combine the request and response into one interaction object
@@ -323,19 +337,12 @@ export const fetchData = async (request, response, { singletonServices, createSe
323
337
  }
324
338
  // Execute the matched route along with its middleware and session management
325
339
  ;
326
- ({ result, sessionServices } = await executeRouteWithMiddleware({
340
+ ({ result, sessionServices } = await executeRoute({
327
341
  singletonServices,
328
- userSession,
329
342
  createSessionServices,
330
343
  skipUserSession,
331
344
  requestId,
332
- }, ignoreMiddleware
333
- ? {
334
- ...matchedRoute,
335
- middleware: undefined,
336
- httpMiddleware: undefined,
337
- }
338
- : matchedRoute, http, { coerceDataFromSchema }));
345
+ }, matchedRoute, http, { coerceDataFromSchema }));
339
346
  return result;
340
347
  }
341
348
  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, PermissionMetadata } 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 = CorePikkuMiddleware> = (CoreHTTPFunction & {
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,8 @@ export type HTTPWiringMeta = {
146
145
  docs?: PikkuDocs;
147
146
  tags?: string[];
148
147
  sse?: true;
148
+ middleware?: MiddlewareMetadata[];
149
+ permissions?: PermissionMetadata[];
149
150
  };
150
151
  export type HTTPWiringsMeta = Record<HTTPMethod, Record<string, HTTPWiringMeta>>;
151
152
  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, addHTTPPermission, } 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, addHTTPPermission, } 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
- this.#headers.set('Set-Cookie', cookieHeader.join(', '));
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,4 +1,4 @@
1
- import { MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from '@pikku/core';
1
+ import { MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from '@pikku/core/mcp';
2
2
  export interface MCPToolEndpoint {
3
3
  name: string;
4
4
  title: string;
@@ -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: <PikkuFunction extends CorePikkuFunctionSessionless<any, any>>(mcpResource: CoreMCPResource<PikkuFunction>) => void;
20
- export declare const wireMCPTool: <PikkuFunction extends CorePikkuFunctionSessionless<any, any>>(mcpTool: CoreMCPTool<PikkuFunction>) => void;
21
- export declare const wireMCPPrompt: <PikkuFunction extends CorePikkuFunctionSessionless<any, any>>(mcpPrompt: CoreMCPPrompt<PikkuFunction>) => void;
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>;