@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.
- package/CHANGELOG.md +14 -0
- package/dist/errors/errors.d.ts +2 -0
- package/dist/errors/errors.js +6 -0
- package/dist/function/function-runner.d.ts +15 -9
- package/dist/function/function-runner.js +55 -37
- package/dist/function/functions.types.d.ts +67 -1
- package/dist/function/functions.types.js +43 -1
- package/dist/index.d.ts +2 -6
- package/dist/index.js +2 -6
- package/dist/middleware/auth-apikey.d.ts +19 -11
- package/dist/middleware/auth-apikey.js +33 -33
- package/dist/middleware/auth-bearer.d.ts +34 -7
- package/dist/middleware/auth-bearer.js +53 -36
- package/dist/middleware/auth-cookie.d.ts +30 -12
- package/dist/middleware/auth-cookie.js +51 -43
- package/dist/middleware/timeout.d.ts +6 -2
- package/dist/middleware/timeout.js +7 -9
- package/dist/middleware-runner.d.ts +46 -43
- package/dist/middleware-runner.js +100 -69
- package/dist/permissions.d.ts +82 -20
- package/dist/permissions.js +176 -62
- package/dist/pikku-state.d.ts +46 -1
- package/dist/pikku-state.js +16 -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 +110 -4
- package/dist/types/core.types.js +44 -1
- package/dist/utils.d.ts +1 -1
- package/dist/wirings/channel/channel-handler.d.ts +2 -2
- package/dist/wirings/channel/channel-handler.js +16 -12
- package/dist/wirings/channel/channel-runner.d.ts +2 -2
- package/dist/wirings/channel/channel-runner.js +6 -24
- package/dist/wirings/channel/channel.types.d.ts +15 -14
- 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 +83 -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 +42 -0
- package/dist/wirings/cli/cli-runner.js +352 -0
- package/dist/wirings/cli/cli.types.d.ts +204 -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 +403 -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 +59 -10
- package/dist/wirings/http/http-runner.js +138 -131
- package/dist/wirings/http/http.types.d.ts +11 -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-endpoint-registry.d.ts +1 -1
- package/dist/wirings/mcp/mcp-runner.d.ts +4 -5
- package/dist/wirings/mcp/mcp-runner.js +32 -34
- package/dist/wirings/mcp/mcp.types.d.ts +38 -14
- 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 +38 -35
- package/dist/wirings/scheduler/scheduler.types.d.ts +5 -4
- package/package.json +9 -7
- package/src/errors/errors.ts +6 -0
- package/src/factory-functions.test.ts +60 -1
- package/src/function/function-runner.test.ts +81 -26
- package/src/function/function-runner.ts +89 -57
- package/src/function/functions.types.ts +85 -2
- package/src/index.ts +6 -19
- package/src/middleware/auth-apikey.ts +42 -57
- package/src/middleware/auth-bearer.ts +66 -49
- package/src/middleware/auth-cookie.ts +63 -82
- package/src/middleware/timeout.ts +10 -14
- package/src/middleware-runner.test.ts +24 -16
- package/src/middleware-runner.ts +115 -87
- package/src/permissions.test.ts +26 -27
- package/src/permissions.ts +216 -104
- package/src/pikku-state.ts +79 -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 +143 -5
- package/src/utils.ts +1 -1
- package/src/wirings/channel/channel-handler.ts +18 -21
- package/src/wirings/channel/channel-runner.ts +19 -32
- package/src/wirings/channel/channel.types.ts +29 -16
- 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 +123 -0
- package/src/wirings/cli/channel/index.ts +1 -0
- package/src/wirings/cli/cli-runner.test.ts +403 -0
- package/src/wirings/cli/cli-runner.ts +539 -0
- package/src/wirings/cli/cli.types.ts +350 -0
- package/src/wirings/cli/command-parser.test.ts +445 -0
- package/src/wirings/cli/command-parser.ts +504 -0
- package/src/wirings/cli/index.ts +13 -0
- package/src/wirings/http/http-runner.test.ts +8 -7
- package/src/wirings/http/http-runner.ts +165 -158
- package/src/wirings/http/http.types.ts +58 -11
- package/src/wirings/http/index.ts +7 -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-endpoint-registry.ts +1 -1
- package/src/wirings/mcp/mcp-runner.ts +58 -55
- package/src/wirings/mcp/mcp.types.ts +65 -14
- 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 +58 -56
- package/src/wirings/scheduler/scheduler.types.ts +10 -3
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -8,23 +8,27 @@ import {
|
|
|
8
8
|
HTTPWiringMeta,
|
|
9
9
|
HTTPMethod,
|
|
10
10
|
} from './http.types.js'
|
|
11
|
+
import {
|
|
12
|
+
CorePikkuFunction,
|
|
13
|
+
CorePikkuFunctionSessionless,
|
|
14
|
+
CorePikkuPermission,
|
|
15
|
+
CorePermissionGroup,
|
|
16
|
+
} from '../../function/functions.types.js'
|
|
11
17
|
import {
|
|
12
18
|
CoreUserSession,
|
|
13
19
|
CorePikkuMiddleware,
|
|
20
|
+
CorePikkuMiddlewareGroup,
|
|
14
21
|
SessionServices,
|
|
15
22
|
PikkuWiringTypes,
|
|
23
|
+
PikkuInteraction,
|
|
16
24
|
} from '../../types/core.types.js'
|
|
17
|
-
import {
|
|
25
|
+
import { NotFoundError } from '../../errors/errors.js'
|
|
18
26
|
import {
|
|
19
27
|
closeSessionServices,
|
|
20
28
|
createWeakUID,
|
|
21
29
|
isSerializable,
|
|
22
30
|
} from '../../utils.js'
|
|
23
|
-
import {
|
|
24
|
-
PikkuUserSessionService,
|
|
25
|
-
UserSessionService,
|
|
26
|
-
} from '../../services/user-session-service.js'
|
|
27
|
-
import { combineMiddleware, runMiddleware } from '../../middleware-runner.js'
|
|
31
|
+
import { PikkuUserSessionService } from '../../services/user-session-service.js'
|
|
28
32
|
import { handleHTTPError } from '../../handle-error.js'
|
|
29
33
|
import { pikkuState } from '../../pikku-state.js'
|
|
30
34
|
import { PikkuFetchHTTPResponse } from './pikku-fetch-http-response.js'
|
|
@@ -35,36 +39,79 @@ import { rpcService } from '../rpc/rpc-runner.js'
|
|
|
35
39
|
import { httpRouter } from './routers/http-router.js'
|
|
36
40
|
|
|
37
41
|
/**
|
|
38
|
-
* Registers middleware
|
|
42
|
+
* Registers HTTP middleware for a specific route pattern.
|
|
43
|
+
*
|
|
44
|
+
* This function registers middleware at runtime that will be applied to
|
|
45
|
+
* HTTP routes matching the specified pattern.
|
|
39
46
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* as global middleware (applied to all routes).
|
|
47
|
+
* For tree-shaking benefits, wrap in a factory function:
|
|
48
|
+
* `export const x = () => addHTTPMiddleware('pattern', [...])`
|
|
43
49
|
*
|
|
44
50
|
* @template PikkuMiddleware The middleware type.
|
|
45
|
-
* @param {
|
|
46
|
-
* @param {
|
|
51
|
+
* @param {string} pattern - Route pattern (e.g., '*' for all routes, '/api/*' for specific routes).
|
|
52
|
+
* @param {CorePikkuMiddlewareGroup} middleware - Array of middleware for this route pattern.
|
|
53
|
+
*
|
|
54
|
+
* @returns {CorePikkuMiddlewareGroup} The middleware array (for chaining/wrapping).
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* // Recommended: tree-shakeable
|
|
59
|
+
* export const httpGlobal = () => addHTTPMiddleware('*', [
|
|
60
|
+
* corsMiddleware,
|
|
61
|
+
* loggingMiddleware
|
|
62
|
+
* ])
|
|
63
|
+
*
|
|
64
|
+
* // Also works: no tree-shaking
|
|
65
|
+
* export const apiMiddleware = addHTTPMiddleware('/api/*', [
|
|
66
|
+
* authMiddleware
|
|
67
|
+
* ])
|
|
68
|
+
* ```
|
|
47
69
|
*/
|
|
48
70
|
export const addHTTPMiddleware = <PikkuMiddleware extends CorePikkuMiddleware>(
|
|
49
|
-
|
|
50
|
-
middleware
|
|
51
|
-
) => {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
route = routeOrMiddleware
|
|
57
|
-
middleware = middleware!
|
|
58
|
-
} else {
|
|
59
|
-
middleware = routeOrMiddleware
|
|
60
|
-
}
|
|
71
|
+
pattern: string,
|
|
72
|
+
middleware: CorePikkuMiddlewareGroup
|
|
73
|
+
): CorePikkuMiddlewareGroup => {
|
|
74
|
+
const httpGroups = pikkuState('middleware', 'httpGroup')
|
|
75
|
+
httpGroups[pattern] = middleware
|
|
76
|
+
return middleware
|
|
77
|
+
}
|
|
61
78
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Registers HTTP permissions for a specific route pattern.
|
|
81
|
+
*
|
|
82
|
+
* This function registers permissions at runtime that will be applied to
|
|
83
|
+
* HTTP routes matching the specified pattern.
|
|
84
|
+
*
|
|
85
|
+
* For tree-shaking benefits, wrap in a factory function:
|
|
86
|
+
* `export const x = () => addHTTPPermission('pattern', [...])`
|
|
87
|
+
*
|
|
88
|
+
* @template PikkuPermission The permission type.
|
|
89
|
+
* @param {string} pattern - Route pattern (e.g., '*' for all routes, '/api/*' for specific routes).
|
|
90
|
+
* @param {CorePermissionGroup | CorePikkuPermission[]} permissions - Permissions for this route pattern.
|
|
91
|
+
*
|
|
92
|
+
* @returns {CorePermissionGroup | CorePikkuPermission[]} The permissions (for chaining/wrapping).
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```typescript
|
|
96
|
+
* // Recommended: tree-shakeable
|
|
97
|
+
* export const httpGlobalPermissions = () => addHTTPPermission('*', [
|
|
98
|
+
* authenticatedPermission,
|
|
99
|
+
* rateLimitPermission
|
|
100
|
+
* ])
|
|
101
|
+
*
|
|
102
|
+
* // Also works: no tree-shaking
|
|
103
|
+
* export const apiPermissions = addHTTPPermission('/api/*', [
|
|
104
|
+
* adminPermission
|
|
105
|
+
* ])
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export const addHTTPPermission = <PikkuPermission extends CorePikkuPermission>(
|
|
109
|
+
pattern: string,
|
|
110
|
+
permissions: CorePermissionGroup | CorePikkuPermission[]
|
|
111
|
+
): CorePermissionGroup | CorePikkuPermission[] => {
|
|
112
|
+
const httpGroups = pikkuState('permissions', 'httpGroup')
|
|
113
|
+
httpGroups[pattern] = permissions
|
|
114
|
+
return permissions
|
|
68
115
|
}
|
|
69
116
|
|
|
70
117
|
/**
|
|
@@ -86,10 +133,14 @@ export const wireHTTP = <
|
|
|
86
133
|
In,
|
|
87
134
|
Out,
|
|
88
135
|
Route extends string,
|
|
89
|
-
PikkuFunction,
|
|
90
|
-
PikkuFunctionSessionless
|
|
91
|
-
|
|
92
|
-
|
|
136
|
+
PikkuFunction extends CorePikkuFunction<In, Out> = CorePikkuFunction<In, Out>,
|
|
137
|
+
PikkuFunctionSessionless extends CorePikkuFunctionSessionless<
|
|
138
|
+
In,
|
|
139
|
+
Out
|
|
140
|
+
> = CorePikkuFunctionSessionless<In, Out>,
|
|
141
|
+
PikkuPermissionGroup extends
|
|
142
|
+
CorePikkuPermission<In> = CorePikkuPermission<In>,
|
|
143
|
+
PikkuMiddleware extends CorePikkuMiddleware = CorePikkuMiddleware,
|
|
93
144
|
>(
|
|
94
145
|
httpWiring: CoreHTTPFunctionWiring<
|
|
95
146
|
In,
|
|
@@ -106,7 +157,7 @@ export const wireHTTP = <
|
|
|
106
157
|
if (!routeMeta) {
|
|
107
158
|
throw new Error('Route metadata not found')
|
|
108
159
|
}
|
|
109
|
-
addFunction(routeMeta.pikkuFuncName, httpWiring.func
|
|
160
|
+
addFunction(routeMeta.pikkuFuncName, httpWiring.func)
|
|
110
161
|
const routes = pikkuState('http', 'routes')
|
|
111
162
|
if (!routes.has(httpWiring.method)) {
|
|
112
163
|
routes.set(httpWiring.method, new Map())
|
|
@@ -146,8 +197,6 @@ const getMatchingRoute = (requestType: string, requestPath: string) => {
|
|
|
146
197
|
params: matchedPath.params,
|
|
147
198
|
route,
|
|
148
199
|
permissions: route.permissions,
|
|
149
|
-
httpMiddleware: matchedPath.middleware,
|
|
150
|
-
middleware: route.middleware,
|
|
151
200
|
meta: meta!,
|
|
152
201
|
}
|
|
153
202
|
}
|
|
@@ -183,16 +232,7 @@ export const createHTTPInteraction = (
|
|
|
183
232
|
}
|
|
184
233
|
|
|
185
234
|
/**
|
|
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.
|
|
235
|
+
* Validates the input data and executes the route handler
|
|
196
236
|
*
|
|
197
237
|
* This function performs these steps:
|
|
198
238
|
* 1. Sets URL parameters on the request.
|
|
@@ -205,16 +245,15 @@ export const createHTTPInteraction = (
|
|
|
205
245
|
* 8. Sends the appropriate response.
|
|
206
246
|
*
|
|
207
247
|
* @param {Object} services - A collection of shared services and utilities.
|
|
208
|
-
* @param {Object} matchedRoute - Contains route details, URL parameters,
|
|
248
|
+
* @param {Object} matchedRoute - Contains route details, URL parameters, and optional schema.
|
|
209
249
|
* @param {PikkuHTTP | undefined} http - The HTTP interaction object.
|
|
210
250
|
* @param {Object} options - Options for route execution (e.g., whether to coerce query strings to arrays).
|
|
211
251
|
* @returns {Promise<any>} An object containing the route handler result and session services (if any).
|
|
212
252
|
* @throws Throws errors like MissingSessionError or ForbiddenError on validation failures.
|
|
213
253
|
*/
|
|
214
|
-
const
|
|
254
|
+
const executeRoute = async (
|
|
215
255
|
services: {
|
|
216
256
|
singletonServices: any
|
|
217
|
-
userSession: UserSessionService<CoreUserSession>
|
|
218
257
|
createSessionServices: Function
|
|
219
258
|
skipUserSession: boolean
|
|
220
259
|
requestId: string
|
|
@@ -223,8 +262,6 @@ const executeRouteWithMiddleware = async (
|
|
|
223
262
|
matchedPath: any
|
|
224
263
|
params: any
|
|
225
264
|
route: CoreHTTPFunctionWiring<any, any, any>
|
|
226
|
-
httpMiddleware: CorePikkuMiddleware[] | undefined
|
|
227
|
-
middleware: CorePikkuMiddleware[] | undefined
|
|
228
265
|
meta: HTTPWiringMeta
|
|
229
266
|
},
|
|
230
267
|
http: PikkuHTTP,
|
|
@@ -232,11 +269,10 @@ const executeRouteWithMiddleware = async (
|
|
|
232
269
|
coerceDataFromSchema: boolean
|
|
233
270
|
}
|
|
234
271
|
) => {
|
|
235
|
-
const
|
|
236
|
-
|
|
272
|
+
const userSession = new PikkuUserSessionService<CoreUserSession>()
|
|
273
|
+
const { params, route, meta } = matchedRoute
|
|
237
274
|
const {
|
|
238
275
|
singletonServices,
|
|
239
|
-
userSession,
|
|
240
276
|
createSessionServices,
|
|
241
277
|
skipUserSession,
|
|
242
278
|
requestId,
|
|
@@ -253,116 +289,96 @@ const executeRouteWithMiddleware = async (
|
|
|
253
289
|
`Matched route: ${route.route} | method: ${route.method.toUpperCase()} | auth: ${requiresSession.toString()}`
|
|
254
290
|
)
|
|
255
291
|
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
|
|
292
|
+
// Ensure session is available when required
|
|
293
|
+
if (skipUserSession && requiresSession) {
|
|
294
|
+
throw new Error("Can't skip trying to get user session if auth is required")
|
|
295
|
+
}
|
|
259
296
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
throw new Error(
|
|
263
|
-
"Can't skip trying to get user session if auth is required"
|
|
264
|
-
)
|
|
265
|
-
}
|
|
297
|
+
const data = () => http.request!.data()
|
|
298
|
+
let channel: PikkuChannel<unknown, unknown> | undefined
|
|
266
299
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
300
|
+
if (matchedRoute.route.sse) {
|
|
301
|
+
const response = http?.response
|
|
302
|
+
if (!response) {
|
|
303
|
+
throw new Error('SSE requires a valid HTTP response object')
|
|
304
|
+
}
|
|
305
|
+
if (!response.setMode) {
|
|
306
|
+
throw new Error('Response object does not support SSE mode')
|
|
273
307
|
}
|
|
308
|
+
response.setMode('stream')
|
|
309
|
+
response.header('Content-Type', 'text/event-stream')
|
|
310
|
+
response.header('Cache-Control', 'no-cache')
|
|
311
|
+
response.header('Connection', 'keep-alive')
|
|
312
|
+
response.header('Transfer-Encoding', 'chunked')
|
|
313
|
+
channel = {
|
|
314
|
+
channelId: requestId,
|
|
315
|
+
openingData: await data(),
|
|
316
|
+
send: (data: any) => {
|
|
317
|
+
response.arrayBuffer(isSerializable(data) ? JSON.stringify(data) : data)
|
|
318
|
+
},
|
|
319
|
+
close: () => {
|
|
320
|
+
channel!.state = 'closed'
|
|
321
|
+
response.close?.()
|
|
322
|
+
},
|
|
323
|
+
state: 'open',
|
|
324
|
+
}
|
|
325
|
+
}
|
|
274
326
|
|
|
275
|
-
|
|
327
|
+
const interaction: PikkuInteraction = { http, channel }
|
|
276
328
|
|
|
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
|
-
}
|
|
329
|
+
const getAllServices = async (session?: CoreUserSession) => {
|
|
330
|
+
let channel: PikkuChannel<unknown, unknown> | undefined
|
|
307
331
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
332
|
+
// Create session-specific services for handling the request
|
|
333
|
+
sessionServices = await createSessionServices(
|
|
334
|
+
{ ...singletonServices, userSession, channel },
|
|
335
|
+
{ http },
|
|
336
|
+
session
|
|
337
|
+
)
|
|
314
338
|
|
|
315
|
-
|
|
339
|
+
return rpcService.injectRPCService(
|
|
340
|
+
{
|
|
316
341
|
...singletonServices,
|
|
317
342
|
...sessionServices,
|
|
318
343
|
http,
|
|
319
344
|
userSession,
|
|
320
345
|
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
|
-
}
|
|
346
|
+
},
|
|
347
|
+
interaction,
|
|
348
|
+
route.auth
|
|
336
349
|
)
|
|
350
|
+
}
|
|
337
351
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
352
|
+
result = await runPikkuFunc(
|
|
353
|
+
PikkuWiringTypes.http,
|
|
354
|
+
`${meta.method}:${meta.route}`,
|
|
355
|
+
meta.pikkuFuncName,
|
|
356
|
+
{
|
|
357
|
+
singletonServices,
|
|
358
|
+
getAllServices,
|
|
359
|
+
auth: route.auth !== false,
|
|
360
|
+
userSession,
|
|
361
|
+
data,
|
|
362
|
+
inheritedMiddleware: meta.middleware,
|
|
363
|
+
wireMiddleware: route.middleware,
|
|
364
|
+
inheritedPermissions: meta.permissions,
|
|
365
|
+
wirePermissions: route.permissions,
|
|
366
|
+
coerceDataFromSchema: options.coerceDataFromSchema,
|
|
367
|
+
tags: route.tags,
|
|
368
|
+
interaction,
|
|
343
369
|
}
|
|
370
|
+
)
|
|
344
371
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
372
|
+
// Respond with either a binary or JSON response based on configuration
|
|
373
|
+
if (route.returnsJSON === false) {
|
|
374
|
+
http?.response?.arrayBuffer(result)
|
|
375
|
+
} else {
|
|
376
|
+
http?.response?.json(result)
|
|
348
377
|
}
|
|
349
378
|
|
|
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
|
-
)
|
|
379
|
+
http?.response?.status(200)
|
|
380
|
+
// TODO: Evaluate if the response stream should be explicitly ended.
|
|
381
|
+
// http?.response?.end()
|
|
366
382
|
|
|
367
383
|
return sessionServices ? { result, sessionServices } : { result }
|
|
368
384
|
}
|
|
@@ -442,14 +458,12 @@ export const fetchData = async <In, Out>(
|
|
|
442
458
|
coerceDataFromSchema = true,
|
|
443
459
|
bubbleErrors = false,
|
|
444
460
|
generateRequestId,
|
|
445
|
-
ignoreMiddleware = false,
|
|
446
461
|
}: RunHTTPWiringOptions & RunHTTPWiringParams
|
|
447
462
|
): Promise<Out | void> => {
|
|
448
463
|
const requestId =
|
|
449
464
|
(request as any).getHeader?.('x-request-id') ||
|
|
450
465
|
generateRequestId?.() ||
|
|
451
466
|
createWeakUID()
|
|
452
|
-
const userSession = new PikkuUserSessionService()
|
|
453
467
|
let sessionServices: SessionServices<typeof singletonServices> | undefined
|
|
454
468
|
let result: Out
|
|
455
469
|
|
|
@@ -475,21 +489,14 @@ export const fetchData = async <In, Out>(
|
|
|
475
489
|
}
|
|
476
490
|
|
|
477
491
|
// Execute the matched route along with its middleware and session management
|
|
478
|
-
;({ result, sessionServices } = await
|
|
492
|
+
;({ result, sessionServices } = await executeRoute(
|
|
479
493
|
{
|
|
480
494
|
singletonServices,
|
|
481
|
-
userSession,
|
|
482
495
|
createSessionServices,
|
|
483
496
|
skipUserSession,
|
|
484
497
|
requestId,
|
|
485
498
|
},
|
|
486
|
-
|
|
487
|
-
? {
|
|
488
|
-
...matchedRoute,
|
|
489
|
-
middleware: undefined,
|
|
490
|
-
httpMiddleware: undefined,
|
|
491
|
-
}
|
|
492
|
-
: matchedRoute,
|
|
499
|
+
matchedRoute,
|
|
493
500
|
http!,
|
|
494
501
|
{ coerceDataFromSchema }
|
|
495
502
|
))
|
|
@@ -7,12 +7,15 @@ import type {
|
|
|
7
7
|
CoreUserSession,
|
|
8
8
|
CreateSessionServices,
|
|
9
9
|
CorePikkuMiddleware,
|
|
10
|
+
MiddlewareMetadata,
|
|
11
|
+
PermissionMetadata,
|
|
10
12
|
} from '../../types/core.types.js'
|
|
11
13
|
import type {
|
|
12
14
|
CorePikkuFunction,
|
|
13
15
|
CorePikkuFunctionSessionless,
|
|
14
16
|
CorePikkuPermission,
|
|
15
17
|
CorePermissionGroup,
|
|
18
|
+
CorePikkuFunctionConfig,
|
|
16
19
|
} from '../../function/functions.types.js'
|
|
17
20
|
|
|
18
21
|
type ExtractHTTPWiringParams<S extends string> =
|
|
@@ -39,7 +42,6 @@ export type RunHTTPWiringOptions = Partial<{
|
|
|
39
42
|
coerceDataFromSchema: boolean
|
|
40
43
|
bubbleErrors: boolean
|
|
41
44
|
generateRequestId: () => string
|
|
42
|
-
ignoreMiddleware: boolean
|
|
43
45
|
}>
|
|
44
46
|
|
|
45
47
|
export type RunHTTPWiringParams = {
|
|
@@ -117,15 +119,38 @@ export type CoreHTTPFunctionWiring<
|
|
|
117
119
|
In,
|
|
118
120
|
Out,
|
|
119
121
|
R extends string,
|
|
120
|
-
PikkuFunction
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
PikkuFunction extends CorePikkuFunction<
|
|
123
|
+
In,
|
|
124
|
+
Out,
|
|
125
|
+
any,
|
|
126
|
+
any,
|
|
127
|
+
any
|
|
128
|
+
> = CorePikkuFunction<In, Out>,
|
|
129
|
+
PikkuFunctionSessionless extends CorePikkuFunctionSessionless<
|
|
130
|
+
In,
|
|
131
|
+
Out,
|
|
132
|
+
any,
|
|
133
|
+
any,
|
|
134
|
+
any
|
|
135
|
+
> = CorePikkuFunctionSessionless<In, Out>,
|
|
136
|
+
PikkuPermission extends CorePikkuPermission<
|
|
137
|
+
In,
|
|
138
|
+
any,
|
|
139
|
+
any
|
|
140
|
+
> = CorePikkuPermission<In, any, any>,
|
|
141
|
+
PikkuMiddleware extends CorePikkuMiddleware<
|
|
142
|
+
any,
|
|
143
|
+
any
|
|
144
|
+
> = CorePikkuMiddleware<any>,
|
|
124
145
|
> =
|
|
125
146
|
| (CoreHTTPFunction & {
|
|
126
147
|
route: R
|
|
127
148
|
method: HTTPMethod
|
|
128
|
-
func:
|
|
149
|
+
func: CorePikkuFunctionConfig<
|
|
150
|
+
PikkuFunction,
|
|
151
|
+
PikkuPermission,
|
|
152
|
+
PikkuMiddleware
|
|
153
|
+
>
|
|
129
154
|
permissions?: CorePermissionGroup<PikkuPermission>
|
|
130
155
|
auth?: true
|
|
131
156
|
tags?: string[]
|
|
@@ -135,7 +160,11 @@ export type CoreHTTPFunctionWiring<
|
|
|
135
160
|
| (CoreHTTPFunction & {
|
|
136
161
|
route: R
|
|
137
162
|
method: HTTPMethod
|
|
138
|
-
func:
|
|
163
|
+
func: CorePikkuFunctionConfig<
|
|
164
|
+
PikkuFunctionSessionless,
|
|
165
|
+
PikkuPermission,
|
|
166
|
+
PikkuMiddleware
|
|
167
|
+
>
|
|
139
168
|
permissions?: undefined
|
|
140
169
|
auth?: false
|
|
141
170
|
tags?: string[]
|
|
@@ -145,7 +174,11 @@ export type CoreHTTPFunctionWiring<
|
|
|
145
174
|
| (CoreHTTPFunction & {
|
|
146
175
|
route: R
|
|
147
176
|
method: 'get'
|
|
148
|
-
func:
|
|
177
|
+
func: CorePikkuFunctionConfig<
|
|
178
|
+
PikkuFunction,
|
|
179
|
+
PikkuPermission,
|
|
180
|
+
PikkuMiddleware
|
|
181
|
+
>
|
|
149
182
|
permissions?: CorePermissionGroup<PikkuPermission>
|
|
150
183
|
auth?: true
|
|
151
184
|
sse?: boolean
|
|
@@ -155,7 +188,11 @@ export type CoreHTTPFunctionWiring<
|
|
|
155
188
|
| (CoreHTTPFunction & {
|
|
156
189
|
route: R
|
|
157
190
|
method: 'get'
|
|
158
|
-
func:
|
|
191
|
+
func: CorePikkuFunctionConfig<
|
|
192
|
+
PikkuFunctionSessionless,
|
|
193
|
+
PikkuPermission,
|
|
194
|
+
PikkuMiddleware
|
|
195
|
+
>
|
|
159
196
|
permissions?: undefined
|
|
160
197
|
auth?: false
|
|
161
198
|
sse?: boolean
|
|
@@ -165,7 +202,11 @@ export type CoreHTTPFunctionWiring<
|
|
|
165
202
|
| (CoreHTTPFunction & {
|
|
166
203
|
route: R
|
|
167
204
|
method: 'post'
|
|
168
|
-
func:
|
|
205
|
+
func: CorePikkuFunctionConfig<
|
|
206
|
+
PikkuFunction,
|
|
207
|
+
PikkuPermission,
|
|
208
|
+
PikkuMiddleware
|
|
209
|
+
>
|
|
169
210
|
permissions?: CorePermissionGroup<PikkuPermission>
|
|
170
211
|
auth?: true
|
|
171
212
|
query?: Array<keyof In>
|
|
@@ -176,7 +217,11 @@ export type CoreHTTPFunctionWiring<
|
|
|
176
217
|
| (CoreHTTPFunction & {
|
|
177
218
|
route: R
|
|
178
219
|
method: 'post'
|
|
179
|
-
func:
|
|
220
|
+
func: CorePikkuFunctionConfig<
|
|
221
|
+
PikkuFunctionSessionless,
|
|
222
|
+
PikkuPermission,
|
|
223
|
+
PikkuMiddleware
|
|
224
|
+
>
|
|
180
225
|
permissions?: undefined
|
|
181
226
|
auth?: false
|
|
182
227
|
query?: Array<keyof In>
|
|
@@ -207,6 +252,8 @@ export type HTTPWiringMeta = {
|
|
|
207
252
|
docs?: PikkuDocs
|
|
208
253
|
tags?: string[]
|
|
209
254
|
sse?: true
|
|
255
|
+
middleware?: MiddlewareMetadata[] // Pre-resolved middleware chain (global + route + tag + explicit)
|
|
256
|
+
permissions?: PermissionMetadata[] // Pre-resolved permission chain (global + route + tag + explicit)
|
|
210
257
|
}
|
|
211
258
|
export type HTTPWiringsMeta = Record<HTTPMethod, Record<string, HTTPWiringMeta>>
|
|
212
259
|
|
|
@@ -2,6 +2,12 @@ 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 {
|
|
5
|
+
export {
|
|
6
|
+
fetch,
|
|
7
|
+
fetchData,
|
|
8
|
+
wireHTTP,
|
|
9
|
+
addHTTPMiddleware,
|
|
10
|
+
addHTTPPermission,
|
|
11
|
+
} from './http-runner.js'
|
|
6
12
|
|
|
7
13
|
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
|
|