@pikku/core 0.9.11 → 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 +6 -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/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 +9 -4
- 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
|
@@ -8,23 +8,26 @@ import {
|
|
|
8
8
|
HTTPWiringMeta,
|
|
9
9
|
HTTPMethod,
|
|
10
10
|
} from './http.types.js'
|
|
11
|
+
import {
|
|
12
|
+
CorePikkuFunction,
|
|
13
|
+
CorePikkuFunctionSessionless,
|
|
14
|
+
CorePikkuPermission,
|
|
15
|
+
} from '../../function/functions.types.js'
|
|
11
16
|
import {
|
|
12
17
|
CoreUserSession,
|
|
13
18
|
CorePikkuMiddleware,
|
|
19
|
+
CorePikkuMiddlewareGroup,
|
|
14
20
|
SessionServices,
|
|
15
21
|
PikkuWiringTypes,
|
|
22
|
+
PikkuInteraction,
|
|
16
23
|
} from '../../types/core.types.js'
|
|
17
|
-
import {
|
|
24
|
+
import { NotFoundError } from '../../errors/errors.js'
|
|
18
25
|
import {
|
|
19
26
|
closeSessionServices,
|
|
20
27
|
createWeakUID,
|
|
21
28
|
isSerializable,
|
|
22
29
|
} from '../../utils.js'
|
|
23
|
-
import {
|
|
24
|
-
PikkuUserSessionService,
|
|
25
|
-
UserSessionService,
|
|
26
|
-
} from '../../services/user-session-service.js'
|
|
27
|
-
import { combineMiddleware, runMiddleware } from '../../middleware-runner.js'
|
|
30
|
+
import { PikkuUserSessionService } from '../../services/user-session-service.js'
|
|
28
31
|
import { handleHTTPError } from '../../handle-error.js'
|
|
29
32
|
import { pikkuState } from '../../pikku-state.js'
|
|
30
33
|
import { PikkuFetchHTTPResponse } from './pikku-fetch-http-response.js'
|
|
@@ -35,36 +38,41 @@ import { rpcService } from '../rpc/rpc-runner.js'
|
|
|
35
38
|
import { httpRouter } from './routers/http-router.js'
|
|
36
39
|
|
|
37
40
|
/**
|
|
38
|
-
* Registers middleware
|
|
41
|
+
* Registers HTTP middleware for a specific route pattern.
|
|
42
|
+
*
|
|
43
|
+
* This function registers middleware at runtime that will be applied to
|
|
44
|
+
* HTTP routes matching the specified pattern.
|
|
39
45
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* as global middleware (applied to all routes).
|
|
46
|
+
* For tree-shaking benefits, wrap in a factory function:
|
|
47
|
+
* `export const x = () => addHTTPMiddleware('pattern', [...])`
|
|
43
48
|
*
|
|
44
49
|
* @template PikkuMiddleware The middleware type.
|
|
45
|
-
* @param {
|
|
46
|
-
* @param {
|
|
50
|
+
* @param {string} pattern - Route pattern (e.g., '*' for all routes, '/api/*' for specific routes).
|
|
51
|
+
* @param {CorePikkuMiddlewareGroup} middleware - Array of middleware for this route pattern.
|
|
52
|
+
*
|
|
53
|
+
* @returns {CorePikkuMiddlewareGroup} The middleware array (for chaining/wrapping).
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```typescript
|
|
57
|
+
* // Recommended: tree-shakeable
|
|
58
|
+
* export const httpGlobal = () => addHTTPMiddleware('*', [
|
|
59
|
+
* corsMiddleware,
|
|
60
|
+
* loggingMiddleware
|
|
61
|
+
* ])
|
|
62
|
+
*
|
|
63
|
+
* // Also works: no tree-shaking
|
|
64
|
+
* export const apiMiddleware = addHTTPMiddleware('/api/*', [
|
|
65
|
+
* authMiddleware
|
|
66
|
+
* ])
|
|
67
|
+
* ```
|
|
47
68
|
*/
|
|
48
69
|
export const addHTTPMiddleware = <PikkuMiddleware extends CorePikkuMiddleware>(
|
|
49
|
-
|
|
50
|
-
middleware
|
|
51
|
-
) => {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (typeof routeOrMiddleware === 'string') {
|
|
56
|
-
route = routeOrMiddleware
|
|
57
|
-
middleware = middleware!
|
|
58
|
-
} else {
|
|
59
|
-
middleware = routeOrMiddleware
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const currentMiddleware = middlewareStore.get(route)
|
|
63
|
-
if (currentMiddleware) {
|
|
64
|
-
middlewareStore.set(route, [...currentMiddleware, ...middleware])
|
|
65
|
-
} else {
|
|
66
|
-
middlewareStore.set(route, middleware)
|
|
67
|
-
}
|
|
70
|
+
pattern: string,
|
|
71
|
+
middleware: CorePikkuMiddlewareGroup
|
|
72
|
+
): CorePikkuMiddlewareGroup => {
|
|
73
|
+
const httpGroups = pikkuState('middleware', 'httpGroup')
|
|
74
|
+
httpGroups[pattern] = middleware
|
|
75
|
+
return middleware
|
|
68
76
|
}
|
|
69
77
|
|
|
70
78
|
/**
|
|
@@ -86,10 +94,14 @@ export const wireHTTP = <
|
|
|
86
94
|
In,
|
|
87
95
|
Out,
|
|
88
96
|
Route extends string,
|
|
89
|
-
PikkuFunction,
|
|
90
|
-
PikkuFunctionSessionless
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
PikkuFunction extends CorePikkuFunction<In, Out> = CorePikkuFunction<In, Out>,
|
|
98
|
+
PikkuFunctionSessionless extends CorePikkuFunctionSessionless<
|
|
99
|
+
In,
|
|
100
|
+
Out
|
|
101
|
+
> = CorePikkuFunctionSessionless<In, Out>,
|
|
102
|
+
PikkuPermissionGroup extends
|
|
103
|
+
CorePikkuPermission<In> = CorePikkuPermission<In>,
|
|
104
|
+
PikkuMiddleware extends CorePikkuMiddleware = CorePikkuMiddleware,
|
|
93
105
|
>(
|
|
94
106
|
httpWiring: CoreHTTPFunctionWiring<
|
|
95
107
|
In,
|
|
@@ -106,7 +118,7 @@ export const wireHTTP = <
|
|
|
106
118
|
if (!routeMeta) {
|
|
107
119
|
throw new Error('Route metadata not found')
|
|
108
120
|
}
|
|
109
|
-
addFunction(routeMeta.pikkuFuncName, httpWiring.func
|
|
121
|
+
addFunction(routeMeta.pikkuFuncName, httpWiring.func)
|
|
110
122
|
const routes = pikkuState('http', 'routes')
|
|
111
123
|
if (!routes.has(httpWiring.method)) {
|
|
112
124
|
routes.set(httpWiring.method, new Map())
|
|
@@ -146,8 +158,6 @@ const getMatchingRoute = (requestType: string, requestPath: string) => {
|
|
|
146
158
|
params: matchedPath.params,
|
|
147
159
|
route,
|
|
148
160
|
permissions: route.permissions,
|
|
149
|
-
httpMiddleware: matchedPath.middleware,
|
|
150
|
-
middleware: route.middleware,
|
|
151
161
|
meta: meta!,
|
|
152
162
|
}
|
|
153
163
|
}
|
|
@@ -183,16 +193,7 @@ export const createHTTPInteraction = (
|
|
|
183
193
|
}
|
|
184
194
|
|
|
185
195
|
/**
|
|
186
|
-
* Validates the input data and executes the route handler
|
|
187
|
-
*
|
|
188
|
-
* NOTE: HTTP wiring handles middleware differently from other wirings (RPC, MCP, Queue, etc.)
|
|
189
|
-
* because HTTP needs to:
|
|
190
|
-
* 1. Check session early for performance (before expensive body parsing)
|
|
191
|
-
* 2. Handle HTTP-specific concerns (headers, cookies, SSE setup)
|
|
192
|
-
* 3. Process middleware that may set up authentication/session state
|
|
193
|
-
*
|
|
194
|
-
* Other wirings (RPC/MCP/Queue/Scheduler) simply pass middleware/permissions/auth
|
|
195
|
-
* directly to runPikkuFunc without processing them.
|
|
196
|
+
* Validates the input data and executes the route handler
|
|
196
197
|
*
|
|
197
198
|
* This function performs these steps:
|
|
198
199
|
* 1. Sets URL parameters on the request.
|
|
@@ -205,16 +206,15 @@ export const createHTTPInteraction = (
|
|
|
205
206
|
* 8. Sends the appropriate response.
|
|
206
207
|
*
|
|
207
208
|
* @param {Object} services - A collection of shared services and utilities.
|
|
208
|
-
* @param {Object} matchedRoute - Contains route details, URL parameters,
|
|
209
|
+
* @param {Object} matchedRoute - Contains route details, URL parameters, and optional schema.
|
|
209
210
|
* @param {PikkuHTTP | undefined} http - The HTTP interaction object.
|
|
210
211
|
* @param {Object} options - Options for route execution (e.g., whether to coerce query strings to arrays).
|
|
211
212
|
* @returns {Promise<any>} An object containing the route handler result and session services (if any).
|
|
212
213
|
* @throws Throws errors like MissingSessionError or ForbiddenError on validation failures.
|
|
213
214
|
*/
|
|
214
|
-
const
|
|
215
|
+
const executeRoute = async (
|
|
215
216
|
services: {
|
|
216
217
|
singletonServices: any
|
|
217
|
-
userSession: UserSessionService<CoreUserSession>
|
|
218
218
|
createSessionServices: Function
|
|
219
219
|
skipUserSession: boolean
|
|
220
220
|
requestId: string
|
|
@@ -223,8 +223,6 @@ const executeRouteWithMiddleware = async (
|
|
|
223
223
|
matchedPath: any
|
|
224
224
|
params: any
|
|
225
225
|
route: CoreHTTPFunctionWiring<any, any, any>
|
|
226
|
-
httpMiddleware: CorePikkuMiddleware[] | undefined
|
|
227
|
-
middleware: CorePikkuMiddleware[] | undefined
|
|
228
226
|
meta: HTTPWiringMeta
|
|
229
227
|
},
|
|
230
228
|
http: PikkuHTTP,
|
|
@@ -232,11 +230,10 @@ const executeRouteWithMiddleware = async (
|
|
|
232
230
|
coerceDataFromSchema: boolean
|
|
233
231
|
}
|
|
234
232
|
) => {
|
|
235
|
-
const
|
|
236
|
-
|
|
233
|
+
const userSession = new PikkuUserSessionService<CoreUserSession>()
|
|
234
|
+
const { params, route, meta } = matchedRoute
|
|
237
235
|
const {
|
|
238
236
|
singletonServices,
|
|
239
|
-
userSession,
|
|
240
237
|
createSessionServices,
|
|
241
238
|
skipUserSession,
|
|
242
239
|
requestId,
|
|
@@ -253,116 +250,95 @@ const executeRouteWithMiddleware = async (
|
|
|
253
250
|
`Matched route: ${route.route} | method: ${route.method.toUpperCase()} | auth: ${requiresSession.toString()}`
|
|
254
251
|
)
|
|
255
252
|
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
|
|
253
|
+
// Ensure session is available when required
|
|
254
|
+
if (skipUserSession && requiresSession) {
|
|
255
|
+
throw new Error("Can't skip trying to get user session if auth is required")
|
|
256
|
+
}
|
|
259
257
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
throw new Error(
|
|
263
|
-
"Can't skip trying to get user session if auth is required"
|
|
264
|
-
)
|
|
265
|
-
}
|
|
258
|
+
const data = () => http.request!.data()
|
|
259
|
+
let channel: PikkuChannel<unknown, unknown> | undefined
|
|
266
260
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
})
|
|
272
|
-
throw new MissingSessionError()
|
|
261
|
+
if (matchedRoute.route.sse) {
|
|
262
|
+
const response = http?.response
|
|
263
|
+
if (!response) {
|
|
264
|
+
throw new Error('SSE requires a valid HTTP response object')
|
|
273
265
|
}
|
|
266
|
+
if (!response.setMode) {
|
|
267
|
+
throw new Error('Response object does not support SSE mode')
|
|
268
|
+
}
|
|
269
|
+
response.setMode('stream')
|
|
270
|
+
response.header('Content-Type', 'text/event-stream')
|
|
271
|
+
response.header('Cache-Control', 'no-cache')
|
|
272
|
+
response.header('Connection', 'keep-alive')
|
|
273
|
+
response.header('Transfer-Encoding', 'chunked')
|
|
274
|
+
channel = {
|
|
275
|
+
channelId: requestId,
|
|
276
|
+
openingData: await data(),
|
|
277
|
+
send: (data: any) => {
|
|
278
|
+
response.arrayBuffer(isSerializable(data) ? JSON.stringify(data) : data)
|
|
279
|
+
},
|
|
280
|
+
close: () => {
|
|
281
|
+
channel!.state = 'closed'
|
|
282
|
+
response.close?.()
|
|
283
|
+
},
|
|
284
|
+
state: 'open',
|
|
285
|
+
}
|
|
286
|
+
}
|
|
274
287
|
|
|
275
|
-
|
|
288
|
+
const interaction: PikkuInteraction = { http, channel }
|
|
276
289
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
if (matchedRoute.route.sse) {
|
|
280
|
-
const response = http?.response
|
|
281
|
-
if (!response) {
|
|
282
|
-
throw new Error('SSE requires a valid HTTP response object')
|
|
283
|
-
}
|
|
284
|
-
if (!response.setMode) {
|
|
285
|
-
throw new Error('Response object does not support SSE mode')
|
|
286
|
-
}
|
|
287
|
-
response.setMode('stream')
|
|
288
|
-
response.header('Content-Type', 'text/event-stream')
|
|
289
|
-
response.header('Cache-Control', 'no-cache')
|
|
290
|
-
response.header('Connection', 'keep-alive')
|
|
291
|
-
response.header('Transfer-Encoding', 'chunked')
|
|
292
|
-
channel = {
|
|
293
|
-
channelId: requestId,
|
|
294
|
-
openingData: data,
|
|
295
|
-
send: (data: any) => {
|
|
296
|
-
response.arrayBuffer(
|
|
297
|
-
isSerializable(data) ? JSON.stringify(data) : data
|
|
298
|
-
)
|
|
299
|
-
},
|
|
300
|
-
close: () => {
|
|
301
|
-
channel!.state = 'closed'
|
|
302
|
-
response.close?.()
|
|
303
|
-
},
|
|
304
|
-
state: 'open',
|
|
305
|
-
}
|
|
306
|
-
}
|
|
290
|
+
const getAllServices = async (session?: CoreUserSession) => {
|
|
291
|
+
let channel: PikkuChannel<unknown, unknown> | undefined
|
|
307
292
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
293
|
+
// Create session-specific services for handling the request
|
|
294
|
+
sessionServices = await createSessionServices(
|
|
295
|
+
{ ...singletonServices, userSession, channel },
|
|
296
|
+
{ http },
|
|
297
|
+
session
|
|
298
|
+
)
|
|
314
299
|
|
|
315
|
-
|
|
300
|
+
return rpcService.injectRPCService(
|
|
301
|
+
{
|
|
316
302
|
...singletonServices,
|
|
317
303
|
...sessionServices,
|
|
318
304
|
http,
|
|
319
305
|
userSession,
|
|
320
306
|
channel,
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
result = await runPikkuFunc(
|
|
325
|
-
PikkuWiringTypes.http,
|
|
326
|
-
`${meta.method}:${meta.route}`,
|
|
327
|
-
meta.pikkuFuncName,
|
|
328
|
-
{
|
|
329
|
-
getAllServices,
|
|
330
|
-
session,
|
|
331
|
-
data,
|
|
332
|
-
permissions: route.permissions,
|
|
333
|
-
coerceDataFromSchema: options.coerceDataFromSchema,
|
|
334
|
-
tags: route.tags,
|
|
335
|
-
}
|
|
307
|
+
},
|
|
308
|
+
interaction,
|
|
309
|
+
route.auth
|
|
336
310
|
)
|
|
311
|
+
}
|
|
337
312
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
313
|
+
result = await runPikkuFunc(
|
|
314
|
+
PikkuWiringTypes.http,
|
|
315
|
+
`${meta.method}:${meta.route}`,
|
|
316
|
+
meta.pikkuFuncName,
|
|
317
|
+
{
|
|
318
|
+
singletonServices,
|
|
319
|
+
getAllServices,
|
|
320
|
+
auth: route.auth !== false,
|
|
321
|
+
userSession,
|
|
322
|
+
data,
|
|
323
|
+
permissions: route.permissions,
|
|
324
|
+
inheritedMiddleware: meta.middleware,
|
|
325
|
+
wireMiddleware: route.middleware,
|
|
326
|
+
coerceDataFromSchema: options.coerceDataFromSchema,
|
|
327
|
+
tags: route.tags,
|
|
328
|
+
interaction,
|
|
343
329
|
}
|
|
330
|
+
)
|
|
344
331
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
332
|
+
// Respond with either a binary or JSON response based on configuration
|
|
333
|
+
if (route.returnsJSON === false) {
|
|
334
|
+
http?.response?.arrayBuffer(result)
|
|
335
|
+
} else {
|
|
336
|
+
http?.response?.json(result)
|
|
348
337
|
}
|
|
349
338
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
// Execute middleware, then run the main logic
|
|
354
|
-
await runMiddleware(
|
|
355
|
-
{ ...singletonServices, userSession },
|
|
356
|
-
{ http },
|
|
357
|
-
combineMiddleware(PikkuWiringTypes.http, `${meta.method}:${meta.route}`, {
|
|
358
|
-
httpMiddleware,
|
|
359
|
-
wiringMiddleware: middleware,
|
|
360
|
-
wiringTags: route.tags,
|
|
361
|
-
funcMiddleware: funcConfig?.middleware,
|
|
362
|
-
funcTags: funcConfig?.tags,
|
|
363
|
-
}),
|
|
364
|
-
runMain
|
|
365
|
-
)
|
|
339
|
+
http?.response?.status(200)
|
|
340
|
+
// TODO: Evaluate if the response stream should be explicitly ended.
|
|
341
|
+
// http?.response?.end()
|
|
366
342
|
|
|
367
343
|
return sessionServices ? { result, sessionServices } : { result }
|
|
368
344
|
}
|
|
@@ -442,14 +418,12 @@ export const fetchData = async <In, Out>(
|
|
|
442
418
|
coerceDataFromSchema = true,
|
|
443
419
|
bubbleErrors = false,
|
|
444
420
|
generateRequestId,
|
|
445
|
-
ignoreMiddleware = false,
|
|
446
421
|
}: RunHTTPWiringOptions & RunHTTPWiringParams
|
|
447
422
|
): Promise<Out | void> => {
|
|
448
423
|
const requestId =
|
|
449
424
|
(request as any).getHeader?.('x-request-id') ||
|
|
450
425
|
generateRequestId?.() ||
|
|
451
426
|
createWeakUID()
|
|
452
|
-
const userSession = new PikkuUserSessionService()
|
|
453
427
|
let sessionServices: SessionServices<typeof singletonServices> | undefined
|
|
454
428
|
let result: Out
|
|
455
429
|
|
|
@@ -475,21 +449,14 @@ export const fetchData = async <In, Out>(
|
|
|
475
449
|
}
|
|
476
450
|
|
|
477
451
|
// Execute the matched route along with its middleware and session management
|
|
478
|
-
;({ result, sessionServices } = await
|
|
452
|
+
;({ result, sessionServices } = await executeRoute(
|
|
479
453
|
{
|
|
480
454
|
singletonServices,
|
|
481
|
-
userSession,
|
|
482
455
|
createSessionServices,
|
|
483
456
|
skipUserSession,
|
|
484
457
|
requestId,
|
|
485
458
|
},
|
|
486
|
-
|
|
487
|
-
? {
|
|
488
|
-
...matchedRoute,
|
|
489
|
-
middleware: undefined,
|
|
490
|
-
httpMiddleware: undefined,
|
|
491
|
-
}
|
|
492
|
-
: matchedRoute,
|
|
459
|
+
matchedRoute,
|
|
493
460
|
http!,
|
|
494
461
|
{ coerceDataFromSchema }
|
|
495
462
|
))
|
|
@@ -7,12 +7,14 @@ import type {
|
|
|
7
7
|
CoreUserSession,
|
|
8
8
|
CreateSessionServices,
|
|
9
9
|
CorePikkuMiddleware,
|
|
10
|
+
MiddlewareMetadata,
|
|
10
11
|
} from '../../types/core.types.js'
|
|
11
12
|
import type {
|
|
12
13
|
CorePikkuFunction,
|
|
13
14
|
CorePikkuFunctionSessionless,
|
|
14
15
|
CorePikkuPermission,
|
|
15
16
|
CorePermissionGroup,
|
|
17
|
+
CorePikkuFunctionConfig,
|
|
16
18
|
} from '../../function/functions.types.js'
|
|
17
19
|
|
|
18
20
|
type ExtractHTTPWiringParams<S extends string> =
|
|
@@ -39,7 +41,6 @@ export type RunHTTPWiringOptions = Partial<{
|
|
|
39
41
|
coerceDataFromSchema: boolean
|
|
40
42
|
bubbleErrors: boolean
|
|
41
43
|
generateRequestId: () => string
|
|
42
|
-
ignoreMiddleware: boolean
|
|
43
44
|
}>
|
|
44
45
|
|
|
45
46
|
export type RunHTTPWiringParams = {
|
|
@@ -117,15 +118,38 @@ export type CoreHTTPFunctionWiring<
|
|
|
117
118
|
In,
|
|
118
119
|
Out,
|
|
119
120
|
R extends string,
|
|
120
|
-
PikkuFunction
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
PikkuFunction extends CorePikkuFunction<
|
|
122
|
+
In,
|
|
123
|
+
Out,
|
|
124
|
+
any,
|
|
125
|
+
any,
|
|
126
|
+
any
|
|
127
|
+
> = CorePikkuFunction<In, Out>,
|
|
128
|
+
PikkuFunctionSessionless extends CorePikkuFunctionSessionless<
|
|
129
|
+
In,
|
|
130
|
+
Out,
|
|
131
|
+
any,
|
|
132
|
+
any,
|
|
133
|
+
any
|
|
134
|
+
> = CorePikkuFunctionSessionless<In, Out>,
|
|
135
|
+
PikkuPermission extends CorePikkuPermission<
|
|
136
|
+
In,
|
|
137
|
+
any,
|
|
138
|
+
any
|
|
139
|
+
> = CorePikkuPermission<In, any, any>,
|
|
140
|
+
PikkuMiddleware extends CorePikkuMiddleware<
|
|
141
|
+
any,
|
|
142
|
+
any
|
|
143
|
+
> = CorePikkuMiddleware<any>,
|
|
124
144
|
> =
|
|
125
145
|
| (CoreHTTPFunction & {
|
|
126
146
|
route: R
|
|
127
147
|
method: HTTPMethod
|
|
128
|
-
func:
|
|
148
|
+
func: CorePikkuFunctionConfig<
|
|
149
|
+
PikkuFunction,
|
|
150
|
+
PikkuPermission,
|
|
151
|
+
PikkuMiddleware
|
|
152
|
+
>
|
|
129
153
|
permissions?: CorePermissionGroup<PikkuPermission>
|
|
130
154
|
auth?: true
|
|
131
155
|
tags?: string[]
|
|
@@ -135,7 +159,11 @@ export type CoreHTTPFunctionWiring<
|
|
|
135
159
|
| (CoreHTTPFunction & {
|
|
136
160
|
route: R
|
|
137
161
|
method: HTTPMethod
|
|
138
|
-
func:
|
|
162
|
+
func: CorePikkuFunctionConfig<
|
|
163
|
+
PikkuFunctionSessionless,
|
|
164
|
+
PikkuPermission,
|
|
165
|
+
PikkuMiddleware
|
|
166
|
+
>
|
|
139
167
|
permissions?: undefined
|
|
140
168
|
auth?: false
|
|
141
169
|
tags?: string[]
|
|
@@ -145,7 +173,11 @@ export type CoreHTTPFunctionWiring<
|
|
|
145
173
|
| (CoreHTTPFunction & {
|
|
146
174
|
route: R
|
|
147
175
|
method: 'get'
|
|
148
|
-
func:
|
|
176
|
+
func: CorePikkuFunctionConfig<
|
|
177
|
+
PikkuFunction,
|
|
178
|
+
PikkuPermission,
|
|
179
|
+
PikkuMiddleware
|
|
180
|
+
>
|
|
149
181
|
permissions?: CorePermissionGroup<PikkuPermission>
|
|
150
182
|
auth?: true
|
|
151
183
|
sse?: boolean
|
|
@@ -155,7 +187,11 @@ export type CoreHTTPFunctionWiring<
|
|
|
155
187
|
| (CoreHTTPFunction & {
|
|
156
188
|
route: R
|
|
157
189
|
method: 'get'
|
|
158
|
-
func:
|
|
190
|
+
func: CorePikkuFunctionConfig<
|
|
191
|
+
PikkuFunctionSessionless,
|
|
192
|
+
PikkuPermission,
|
|
193
|
+
PikkuMiddleware
|
|
194
|
+
>
|
|
159
195
|
permissions?: undefined
|
|
160
196
|
auth?: false
|
|
161
197
|
sse?: boolean
|
|
@@ -165,7 +201,11 @@ export type CoreHTTPFunctionWiring<
|
|
|
165
201
|
| (CoreHTTPFunction & {
|
|
166
202
|
route: R
|
|
167
203
|
method: 'post'
|
|
168
|
-
func:
|
|
204
|
+
func: CorePikkuFunctionConfig<
|
|
205
|
+
PikkuFunction,
|
|
206
|
+
PikkuPermission,
|
|
207
|
+
PikkuMiddleware
|
|
208
|
+
>
|
|
169
209
|
permissions?: CorePermissionGroup<PikkuPermission>
|
|
170
210
|
auth?: true
|
|
171
211
|
query?: Array<keyof In>
|
|
@@ -176,7 +216,11 @@ export type CoreHTTPFunctionWiring<
|
|
|
176
216
|
| (CoreHTTPFunction & {
|
|
177
217
|
route: R
|
|
178
218
|
method: 'post'
|
|
179
|
-
func:
|
|
219
|
+
func: CorePikkuFunctionConfig<
|
|
220
|
+
PikkuFunctionSessionless,
|
|
221
|
+
PikkuPermission,
|
|
222
|
+
PikkuMiddleware
|
|
223
|
+
>
|
|
180
224
|
permissions?: undefined
|
|
181
225
|
auth?: false
|
|
182
226
|
query?: Array<keyof In>
|
|
@@ -207,6 +251,7 @@ export type HTTPWiringMeta = {
|
|
|
207
251
|
docs?: PikkuDocs
|
|
208
252
|
tags?: string[]
|
|
209
253
|
sse?: true
|
|
254
|
+
middleware?: MiddlewareMetadata[] // Pre-resolved middleware chain (global + route + tag + explicit + efunc)
|
|
210
255
|
}
|
|
211
256
|
export type HTTPWiringsMeta = Record<HTTPMethod, Record<string, HTTPWiringMeta>>
|
|
212
257
|
|
|
@@ -2,6 +2,6 @@ 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
4
|
|
|
5
|
-
export { fetch, fetchData, wireHTTP } from './http-runner.js'
|
|
5
|
+
export { fetch, fetchData, wireHTTP, addHTTPMiddleware } from './http-runner.js'
|
|
6
6
|
|
|
7
7
|
export type * from './http.types.js'
|
|
@@ -107,7 +107,9 @@ export class PikkuFetchHTTPResponse implements PikkuHTTPResponse {
|
|
|
107
107
|
const cookieHeader = Array.from(this.#cookies.entries()).map(
|
|
108
108
|
([name, { value, flags }]) => serializeCookie(name, value, flags)
|
|
109
109
|
)
|
|
110
|
-
|
|
110
|
+
// Multiple Set-Cookie headers must be appended separately, not joined with commas
|
|
111
|
+
// per HTTP specification (RFC 6265)
|
|
112
|
+
cookieHeader.forEach((cookie) => this.#headers.append('Set-Cookie', cookie))
|
|
111
113
|
return new Response(this.#body, {
|
|
112
114
|
...args,
|
|
113
115
|
status: this.#statusCode,
|
|
@@ -1,11 +1,9 @@
|
|
|
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
|
|
|
5
4
|
export type MatchResult = {
|
|
6
5
|
route: string
|
|
7
6
|
params: Record<string, any>
|
|
8
|
-
middleware?: CorePikkuMiddleware[]
|
|
9
7
|
} | null
|
|
10
8
|
|
|
11
9
|
export interface Router {
|
|
@@ -46,7 +46,6 @@ describe('PathToRegexRouter', () => {
|
|
|
46
46
|
assert.ok(staticResult, 'Static route should match')
|
|
47
47
|
assert.strictEqual(staticResult.route, '/static')
|
|
48
48
|
assert.deepStrictEqual(staticResult.params, {})
|
|
49
|
-
assert.strictEqual(staticResult.middleware?.length, 0)
|
|
50
49
|
|
|
51
50
|
const aboutResult = router.match('get', '/api/about')
|
|
52
51
|
assert.ok(aboutResult, 'Static route /api/about should match')
|
|
@@ -229,7 +228,7 @@ describe('PathToRegexRouter', () => {
|
|
|
229
228
|
})
|
|
230
229
|
|
|
231
230
|
describe('Middleware Integration', () => {
|
|
232
|
-
test('should
|
|
231
|
+
test('should match route without returning middleware', () => {
|
|
233
232
|
const globalMiddleware = [async () => {}]
|
|
234
233
|
const routeMiddleware = [async () => {}]
|
|
235
234
|
|
|
@@ -252,9 +251,9 @@ describe('PathToRegexRouter', () => {
|
|
|
252
251
|
|
|
253
252
|
const result = router.match('get', '/api/test')
|
|
254
253
|
assert.ok(result, 'Route should match')
|
|
255
|
-
|
|
256
|
-
assert.strictEqual(result.
|
|
257
|
-
assert.
|
|
254
|
+
// Router only returns route and params, middleware is handled separately
|
|
255
|
+
assert.strictEqual(result.route, '/api/test')
|
|
256
|
+
assert.deepStrictEqual(result.params, {})
|
|
258
257
|
})
|
|
259
258
|
})
|
|
260
259
|
|