@pikku/core 0.6.27 → 0.7.1
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 +16 -1
- package/dist/channel/channel-handler.js +16 -38
- package/dist/channel/channel-runner.d.ts +7 -8
- package/dist/channel/channel-runner.js +58 -7
- package/dist/channel/channel.types.d.ts +14 -27
- package/dist/channel/local/local-channel-runner.js +9 -4
- package/dist/channel/log-channels.js +4 -4
- package/dist/channel/serverless/serverless-channel-runner.js +17 -6
- package/dist/function/function-runner.d.ts +12 -0
- package/dist/function/function-runner.js +38 -0
- package/dist/{types → function}/functions.types.d.ts +12 -7
- package/dist/function/index.d.ts +2 -0
- package/dist/function/index.js +2 -0
- package/dist/handle-error.d.ts +1 -1
- package/dist/http/{http-route-runner.d.ts → http-runner.d.ts} +4 -4
- package/dist/http/{http-route-runner.js → http-runner.js} +81 -43
- package/dist/http/{http-routes.types.d.ts → http.types.d.ts} +41 -12
- package/dist/http/index.d.ts +2 -2
- package/dist/http/index.js +1 -1
- package/dist/http/log-http-routes.js +7 -5
- package/dist/http/pikku-fetch-http-request.d.ts +1 -1
- package/dist/http/pikku-fetch-http-response.d.ts +4 -2
- package/dist/http/pikku-fetch-http-response.js +64 -12
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/middleware/auth-apikey.js +5 -4
- package/dist/middleware/auth-bearer.js +5 -4
- package/dist/middleware/auth-cookie.js +10 -9
- package/dist/permissions.d.ts +2 -2
- package/dist/pikku-function.d.ts +1 -0
- package/dist/pikku-function.js +1 -0
- package/dist/pikku-state.d.ts +10 -5
- package/dist/pikku-state.js +7 -3
- package/dist/scheduler/scheduler-runner.d.ts +1 -1
- package/dist/scheduler/scheduler-runner.js +24 -6
- package/dist/scheduler/scheduler.types.d.ts +3 -2
- package/dist/services/content-service.d.ts +33 -37
- package/dist/types/core.types.d.ts +15 -1
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +15 -0
- package/lcov.info +1240 -965
- package/package.json +2 -1
- package/src/channel/channel-handler.ts +16 -78
- package/src/channel/channel-runner.ts +72 -17
- package/src/channel/channel.types.ts +25 -63
- package/src/channel/local/local-channel-runner.test.ts +10 -11
- package/src/channel/local/local-channel-runner.ts +20 -7
- package/src/channel/log-channels.ts +4 -4
- package/src/channel/serverless/serverless-channel-runner.ts +26 -15
- package/src/function/function-runner.ts +91 -0
- package/src/{types → function}/functions.types.ts +25 -11
- package/src/function/index.ts +2 -0
- package/src/handle-error.ts +1 -1
- package/src/http/{http-route-runner.test.ts → http-runner.test.ts} +29 -6
- package/src/http/{http-route-runner.ts → http-runner.ts} +106 -73
- package/src/http/{http-routes.types.ts → http.types.ts} +55 -16
- package/src/http/index.ts +2 -2
- package/src/http/log-http-routes.ts +7 -5
- package/src/http/pikku-fetch-http-request.ts +1 -5
- package/src/http/pikku-fetch-http-response.ts +67 -13
- package/src/index.ts +3 -2
- package/src/middleware/auth-apikey.ts +5 -4
- package/src/middleware/auth-bearer.ts +5 -4
- package/src/middleware/auth-cookie.ts +14 -9
- package/src/permissions.ts +2 -4
- package/src/pikku-function.ts +1 -0
- package/src/pikku-state.ts +25 -9
- package/src/scheduler/scheduler-runner.ts +31 -13
- package/src/scheduler/scheduler.types.ts +12 -8
- package/src/services/content-service.ts +36 -40
- package/src/types/core.types.ts +20 -1
- package/src/utils.ts +19 -0
- package/tsconfig.tsbuildinfo +1 -1
- /package/dist/{types → function}/functions.types.js +0 -0
- /package/dist/http/{http-routes.types.js → http.types.js} +0 -0
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { verifyPermissions } from '../permissions.js';
|
|
2
1
|
import { match } from 'path-to-regexp';
|
|
3
|
-
import {
|
|
4
|
-
import { closeSessionServices } from '../utils.js';
|
|
5
|
-
import { coerceTopLevelDataFromSchema, validateSchema } from '../schema.js';
|
|
2
|
+
import { MissingSessionError, NotFoundError } from '../errors/errors.js';
|
|
3
|
+
import { closeSessionServices, createWeakUID, isSerializable, } from '../utils.js';
|
|
6
4
|
import { PikkuUserSessionService, } from '../services/user-session-service.js';
|
|
7
5
|
import { runMiddleware } from '../middleware-runner.js';
|
|
8
6
|
import { handleError } from '../handle-error.js';
|
|
9
7
|
import { pikkuState } from '../pikku-state.js';
|
|
10
8
|
import { PikkuFetchHTTPResponse } from './pikku-fetch-http-response.js';
|
|
11
9
|
import { PikkuFetchHTTPRequest } from './pikku-fetch-http-request.js';
|
|
10
|
+
import { addFunction, runPikkuFunc } from '../function/function-runner.js';
|
|
12
11
|
/**
|
|
13
12
|
* Registers middleware either globally or for a specific route.
|
|
14
13
|
*
|
|
@@ -45,12 +44,24 @@ export const addMiddleware = (routeOrMiddleware, middleware) => {
|
|
|
45
44
|
* @template Route Route pattern as a string.
|
|
46
45
|
* @template APIFunction Type for the route handler function.
|
|
47
46
|
* @template APIFunctionSessionless Type for a sessionless handler.
|
|
48
|
-
* @template
|
|
47
|
+
* @template APIPermissionGroup Type representing required permissions.
|
|
49
48
|
* @template APIMiddleware Middleware type to be used with the route.
|
|
50
49
|
* @param {CoreHTTPFunctionRoute<In, Out, Route, APIFunction, APIFunctionSessionless, APIPermission, APIMiddleware>} route - The route configuration object.
|
|
51
50
|
*/
|
|
52
|
-
export const
|
|
53
|
-
pikkuState('http', '
|
|
51
|
+
export const addHTTPRoute = (httpRoute) => {
|
|
52
|
+
const httpMeta = pikkuState('http', 'meta');
|
|
53
|
+
const routeMeta = httpMeta.find((meta) => meta.route === httpRoute.route && meta.method === httpRoute.method);
|
|
54
|
+
if (!routeMeta) {
|
|
55
|
+
throw new Error('Route metadata not found');
|
|
56
|
+
}
|
|
57
|
+
addFunction(routeMeta.pikkuFuncName, httpRoute.func);
|
|
58
|
+
const routes = pikkuState('http', 'routes');
|
|
59
|
+
if (!routes.has(httpRoute.method)) {
|
|
60
|
+
routes.set(httpRoute.method, new Map());
|
|
61
|
+
}
|
|
62
|
+
pikkuState('http', 'routes')
|
|
63
|
+
.get(httpRoute.method)
|
|
64
|
+
?.set(httpRoute.route, httpRoute);
|
|
54
65
|
};
|
|
55
66
|
/**
|
|
56
67
|
* Finds a matching route based on the HTTP method and URL path.
|
|
@@ -64,14 +75,13 @@ export const addRoute = (route) => {
|
|
|
64
75
|
* @returns {Object | undefined} An object with matched route details or undefined if no match.
|
|
65
76
|
*/
|
|
66
77
|
const getMatchingRoute = (requestType, requestPath) => {
|
|
67
|
-
const
|
|
78
|
+
const allRoutes = pikkuState('http', 'routes');
|
|
68
79
|
const middleware = pikkuState('http', 'middleware');
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
80
|
+
const routes = allRoutes.get(requestType.toLowerCase());
|
|
81
|
+
if (!routes) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
for (const route of routes.values()) {
|
|
75
85
|
// Generate a matching function from the route pattern
|
|
76
86
|
const matchFunc = match(`/${route.route}`.replace(/^\/\//, '/'), {
|
|
77
87
|
decode: decodeURIComponent,
|
|
@@ -84,14 +94,14 @@ const getMatchingRoute = (requestType, requestPath) => {
|
|
|
84
94
|
.filter((m) => m.route === '*' || new RegExp(m.route).test(route.route))
|
|
85
95
|
.map((m) => m.middleware)
|
|
86
96
|
.flat();
|
|
87
|
-
|
|
88
|
-
const schemaName = routesMeta.find((routeMeta) => routeMeta.method === route.method && routeMeta.route === route.route)?.input;
|
|
97
|
+
const meta = pikkuState('http', 'meta').find((meta) => meta.route === route.route && meta.method === route.method);
|
|
89
98
|
return {
|
|
90
99
|
matchedPath,
|
|
91
100
|
params: matchedPath.params,
|
|
92
101
|
route,
|
|
102
|
+
permissions: route.permissions,
|
|
93
103
|
middleware: [...globalMiddleware, ...(route.middleware || [])],
|
|
94
|
-
|
|
104
|
+
meta: meta,
|
|
95
105
|
};
|
|
96
106
|
}
|
|
97
107
|
}
|
|
@@ -141,8 +151,8 @@ export const createHTTPInteraction = (request, response) => {
|
|
|
141
151
|
* @throws Throws errors like MissingSessionError or ForbiddenError on validation failures.
|
|
142
152
|
*/
|
|
143
153
|
const executeRouteWithMiddleware = async (services, matchedRoute, http, options) => {
|
|
144
|
-
const { matchedPath, params, route, middleware,
|
|
145
|
-
const { singletonServices, userSession, createSessionServices, skipUserSession, } = services;
|
|
154
|
+
const { matchedPath, params, route, middleware, meta } = matchedRoute;
|
|
155
|
+
const { singletonServices, userSession, createSessionServices, skipUserSession, requestId, } = services;
|
|
146
156
|
const requiresSession = route.auth !== false;
|
|
147
157
|
let sessionServices;
|
|
148
158
|
let result;
|
|
@@ -163,28 +173,53 @@ const executeRouteWithMiddleware = async (services, matchedRoute, http, options)
|
|
|
163
173
|
});
|
|
164
174
|
throw new MissingSessionError();
|
|
165
175
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
176
|
+
const data = 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 {
|
|
208
|
+
...singletonServices,
|
|
209
|
+
...sessionServices,
|
|
210
|
+
http,
|
|
211
|
+
userSession,
|
|
212
|
+
channel,
|
|
213
|
+
};
|
|
173
214
|
};
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const permissioned = await verifyPermissions(route.permissions, allServices, data, session);
|
|
183
|
-
if (permissioned === false) {
|
|
184
|
-
throw new ForbiddenError('Permission denied');
|
|
185
|
-
}
|
|
186
|
-
// Invoke the actual route handler function
|
|
187
|
-
result = await route.func(allServices, data, session);
|
|
215
|
+
const result = await runPikkuFunc(meta.pikkuFuncName, {
|
|
216
|
+
singletonServices,
|
|
217
|
+
getAllServices,
|
|
218
|
+
session,
|
|
219
|
+
data,
|
|
220
|
+
permissions: route.permissions,
|
|
221
|
+
coerceDataFromSchema: options.coerceDataFromSchema,
|
|
222
|
+
});
|
|
188
223
|
// Respond with either a binary or JSON response based on configuration
|
|
189
224
|
if (route.returnsJSON === false) {
|
|
190
225
|
http?.response?.arrayBuffer(result);
|
|
@@ -256,7 +291,10 @@ export const pikkuFetch = async (request, params) => {
|
|
|
256
291
|
* @param {RunRouteOptions & RunRouteParams} options - Options such as singleton services, session handling, and error configuration.
|
|
257
292
|
* @returns {Promise<Out | void>} The output from the route handler or void if an error occurred.
|
|
258
293
|
*/
|
|
259
|
-
export const fetchData = async (request, response, { singletonServices, createSessionServices, skipUserSession = false, respondWith404 = true, logWarningsForStatusCodes = [], coerceDataFromSchema = true, bubbleErrors = false, }) => {
|
|
294
|
+
export const fetchData = async (request, response, { singletonServices, createSessionServices, skipUserSession = false, respondWith404 = true, logWarningsForStatusCodes = [], coerceDataFromSchema = true, bubbleErrors = false, generateRequestId, }) => {
|
|
295
|
+
const requestId = request.getHeader?.('x-request-id') ||
|
|
296
|
+
generateRequestId?.() ||
|
|
297
|
+
createWeakUID();
|
|
260
298
|
const userSession = new PikkuUserSessionService();
|
|
261
299
|
let sessionServices;
|
|
262
300
|
let result;
|
|
@@ -283,13 +321,13 @@ export const fetchData = async (request, response, { singletonServices, createSe
|
|
|
283
321
|
userSession,
|
|
284
322
|
createSessionServices,
|
|
285
323
|
skipUserSession,
|
|
324
|
+
requestId,
|
|
286
325
|
}, matchedRoute, http, { coerceDataFromSchema }));
|
|
287
326
|
return result;
|
|
288
327
|
}
|
|
289
328
|
catch (e) {
|
|
290
329
|
// Handle errors and, depending on configuration, bubble them up or respond with an error
|
|
291
|
-
handleError(e, http,
|
|
292
|
-
singletonServices.logger, logWarningsForStatusCodes, respondWith404, bubbleErrors);
|
|
330
|
+
handleError(e, http, requestId, singletonServices.logger, logWarningsForStatusCodes, respondWith404, bubbleErrors);
|
|
293
331
|
}
|
|
294
332
|
finally {
|
|
295
333
|
// Clean up any session-specific services created during processing
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { SerializeOptions } from 'cookie';
|
|
2
|
-
import { PikkuError } from '../errors/error-handler.js';
|
|
3
|
-
import { APIDocs, CoreServices, CoreSingletonServices, CoreUserSession, CreateSessionServices, PikkuMiddleware } from '../types/core.types.js';
|
|
4
|
-
import { CoreAPIFunction, CoreAPIFunctionSessionless, CoreAPIPermission } from '../
|
|
1
|
+
import type { SerializeOptions } from 'cookie';
|
|
2
|
+
import type { PikkuError } from '../errors/error-handler.js';
|
|
3
|
+
import type { APIDocs, CoreServices, CoreSingletonServices, CoreUserSession, CreateSessionServices, PikkuMiddleware } from '../types/core.types.js';
|
|
4
|
+
import type { CoreAPIFunction, CoreAPIFunctionSessionless, CoreAPIPermission, CorePermissionGroup } from '../function/functions.types.js';
|
|
5
5
|
type ExtractRouteParams<S extends string> = S extends `${string}:${infer Param}/${infer Rest}` ? Param | ExtractRouteParams<`/${Rest}`> : S extends `${string}:${infer Param}` ? Param : never;
|
|
6
6
|
export type AssertRouteParams<In, Route extends string> = ExtractRouteParams<Route> extends keyof In ? unknown : ['Error: Route parameters', ExtractRouteParams<Route>, 'not in', keyof In];
|
|
7
7
|
export type RunRouteOptions = Partial<{
|
|
@@ -10,6 +10,7 @@ export type RunRouteOptions = Partial<{
|
|
|
10
10
|
logWarningsForStatusCodes: number[];
|
|
11
11
|
coerceDataFromSchema: boolean;
|
|
12
12
|
bubbleErrors: boolean;
|
|
13
|
+
generateRequestId: () => string;
|
|
13
14
|
}>;
|
|
14
15
|
export type RunRouteParams = {
|
|
15
16
|
singletonServices: CoreSingletonServices;
|
|
@@ -64,10 +65,11 @@ export type CoreHTTPFunctionRoute<In, Out, R extends string, APIFunction = CoreA
|
|
|
64
65
|
route: R;
|
|
65
66
|
method: HTTPMethod;
|
|
66
67
|
func: APIFunction;
|
|
67
|
-
permissions?:
|
|
68
|
+
permissions?: CorePermissionGroup<APIPermission>;
|
|
68
69
|
auth?: true;
|
|
69
70
|
tags?: string[];
|
|
70
71
|
middleware?: APIMiddleware[];
|
|
72
|
+
sse?: undefined;
|
|
71
73
|
}) | (CoreHTTPFunction & {
|
|
72
74
|
route: R;
|
|
73
75
|
method: HTTPMethod;
|
|
@@ -76,15 +78,35 @@ export type CoreHTTPFunctionRoute<In, Out, R extends string, APIFunction = CoreA
|
|
|
76
78
|
auth?: false;
|
|
77
79
|
tags?: string[];
|
|
78
80
|
middleware?: APIMiddleware[];
|
|
81
|
+
sse?: undefined;
|
|
82
|
+
}) | (CoreHTTPFunction & {
|
|
83
|
+
route: R;
|
|
84
|
+
method: 'get';
|
|
85
|
+
func: APIFunction;
|
|
86
|
+
permissions?: CorePermissionGroup<APIPermission>;
|
|
87
|
+
auth?: true;
|
|
88
|
+
sse?: boolean;
|
|
89
|
+
tags?: string[];
|
|
90
|
+
middleware?: APIMiddleware[];
|
|
91
|
+
}) | (CoreHTTPFunction & {
|
|
92
|
+
route: R;
|
|
93
|
+
method: 'get';
|
|
94
|
+
func: APIFunctionSessionless;
|
|
95
|
+
permissions?: undefined;
|
|
96
|
+
auth?: false;
|
|
97
|
+
sse?: boolean;
|
|
98
|
+
tags?: string[];
|
|
99
|
+
middleware?: APIMiddleware[];
|
|
79
100
|
}) | (CoreHTTPFunction & {
|
|
80
101
|
route: R;
|
|
81
102
|
method: 'post';
|
|
82
103
|
func: APIFunction;
|
|
83
|
-
permissions?:
|
|
104
|
+
permissions?: CorePermissionGroup<APIPermission>;
|
|
84
105
|
auth?: true;
|
|
85
106
|
query?: Array<keyof In>;
|
|
86
107
|
tags?: string[];
|
|
87
108
|
middleware?: APIMiddleware[];
|
|
109
|
+
sse?: undefined;
|
|
88
110
|
}) | (CoreHTTPFunction & {
|
|
89
111
|
route: R;
|
|
90
112
|
method: 'post';
|
|
@@ -94,11 +116,8 @@ export type CoreHTTPFunctionRoute<In, Out, R extends string, APIFunction = CoreA
|
|
|
94
116
|
query?: Array<keyof In>;
|
|
95
117
|
tags?: string[];
|
|
96
118
|
middleware?: APIMiddleware[];
|
|
119
|
+
sse?: undefined;
|
|
97
120
|
});
|
|
98
|
-
/**
|
|
99
|
-
* Represents an array of core API routes.
|
|
100
|
-
*/
|
|
101
|
-
export type CoreHTTPFunctionRoutes = Array<CoreHTTPFunctionRoute<any, any, string>>;
|
|
102
121
|
/**
|
|
103
122
|
* Represents the input types for route metadata, including parameters, query, and body types.
|
|
104
123
|
*/
|
|
@@ -110,7 +129,8 @@ export type HTTPFunctionMetaInputTypes = {
|
|
|
110
129
|
/**
|
|
111
130
|
* Represents metadata for a set of routes, including route details, methods, input/output types, and documentation.
|
|
112
131
|
*/
|
|
113
|
-
export type
|
|
132
|
+
export type HTTPRouteMeta = {
|
|
133
|
+
pikkuFuncName: string;
|
|
114
134
|
route: string;
|
|
115
135
|
method: HTTPMethod;
|
|
116
136
|
params?: string[];
|
|
@@ -120,6 +140,13 @@ export type HTTPRoutesMeta = Array<{
|
|
|
120
140
|
inputTypes?: HTTPFunctionMetaInputTypes;
|
|
121
141
|
docs?: APIDocs;
|
|
122
142
|
tags?: string[];
|
|
143
|
+
sse?: true;
|
|
144
|
+
};
|
|
145
|
+
export type HTTPRoutesMeta = Array<HTTPRouteMeta>;
|
|
146
|
+
export type HTTPFunctionsMeta = Array<{
|
|
147
|
+
name: string;
|
|
148
|
+
inputs: string[] | null;
|
|
149
|
+
outputs: string[] | null;
|
|
123
150
|
}>;
|
|
124
151
|
export type HTTPRouteMiddleware = {
|
|
125
152
|
route: string;
|
|
@@ -141,8 +168,10 @@ export interface PikkuHTTPResponse {
|
|
|
141
168
|
status(code: number): this;
|
|
142
169
|
cookie(name: string, value: string | null, options: SerializeOptions): this;
|
|
143
170
|
header(name: string, value: string | string[]): this;
|
|
144
|
-
arrayBuffer(data:
|
|
171
|
+
arrayBuffer(data: ArrayBuffer | ArrayBufferView | Blob | string | FormData | URLSearchParams | ReadableStream): this;
|
|
145
172
|
json(data: unknown): this;
|
|
146
173
|
redirect(location: string, status?: number): this;
|
|
174
|
+
close?: () => void;
|
|
175
|
+
setMode?: (mode: 'stream') => void;
|
|
147
176
|
}
|
|
148
177
|
export {};
|
package/dist/http/index.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ export * from './pikku-fetch-http-request.js';
|
|
|
2
2
|
export * from './pikku-fetch-http-response.js';
|
|
3
3
|
export * from './incomingmessage-to-request-convertor.js';
|
|
4
4
|
export * from './log-http-routes.js';
|
|
5
|
-
export { fetch, fetchData,
|
|
6
|
-
export type * from './http
|
|
5
|
+
export { fetch, fetchData, addHTTPRoute } from './http-runner.js';
|
|
6
|
+
export type * from './http.types.js';
|
package/dist/http/index.js
CHANGED
|
@@ -2,4 +2,4 @@ export * from './pikku-fetch-http-request.js';
|
|
|
2
2
|
export * from './pikku-fetch-http-response.js';
|
|
3
3
|
export * from './incomingmessage-to-request-convertor.js';
|
|
4
4
|
export * from './log-http-routes.js';
|
|
5
|
-
export { fetch, fetchData,
|
|
5
|
+
export { fetch, fetchData, addHTTPRoute } from './http-runner.js';
|
|
@@ -4,14 +4,16 @@ import { pikkuState } from '../pikku-state.js';
|
|
|
4
4
|
* @param logger - A logger for logging information.
|
|
5
5
|
*/
|
|
6
6
|
export const logRoutes = (logger) => {
|
|
7
|
-
const
|
|
8
|
-
if (
|
|
7
|
+
const routesByType = pikkuState('http', 'routes');
|
|
8
|
+
if (routesByType.size === 0) {
|
|
9
9
|
logger.info('No routes added');
|
|
10
10
|
return;
|
|
11
11
|
}
|
|
12
12
|
let routesMessage = 'Routes loaded:';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
routesByType.forEach((routes) => {
|
|
14
|
+
routes.forEach((route) => {
|
|
15
|
+
routesMessage += `\n\t- ${route.method.toUpperCase()} -> ${route.route}`;
|
|
16
|
+
});
|
|
17
|
+
});
|
|
16
18
|
logger.info(routesMessage);
|
|
17
19
|
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { PikkuHTTPResponse } from './http
|
|
1
|
+
import { PikkuHTTPResponse } from './http.types.js';
|
|
2
2
|
import { SerializeOptions as CookieSerializeOptions } from 'cookie';
|
|
3
3
|
export declare class PikkuFetchHTTPResponse implements PikkuHTTPResponse {
|
|
4
4
|
#private;
|
|
5
|
+
setMode(mode: 'stream'): void;
|
|
5
6
|
status(code: number): this;
|
|
6
7
|
cookie(name: string, value: string, flags: CookieSerializeOptions): this;
|
|
7
8
|
header(name: string, value: string | string[]): this;
|
|
@@ -9,7 +10,8 @@ export declare class PikkuFetchHTTPResponse implements PikkuHTTPResponse {
|
|
|
9
10
|
json(data: unknown): this;
|
|
10
11
|
text(content: string): this;
|
|
11
12
|
html(content: string): this;
|
|
12
|
-
body(body: BodyInit): this;
|
|
13
13
|
redirect(location: string, status?: number): this;
|
|
14
|
+
close(): this;
|
|
14
15
|
toResponse(args?: Record<string, any>): Response;
|
|
16
|
+
private createStream;
|
|
15
17
|
}
|
|
@@ -4,6 +4,15 @@ export class PikkuFetchHTTPResponse {
|
|
|
4
4
|
#headers = new Headers();
|
|
5
5
|
#cookies = new Map();
|
|
6
6
|
#body = null;
|
|
7
|
+
#responseMode = null;
|
|
8
|
+
#send = null;
|
|
9
|
+
#close = null;
|
|
10
|
+
setMode(mode) {
|
|
11
|
+
this.#responseMode = 'stream';
|
|
12
|
+
if (mode === 'stream') {
|
|
13
|
+
this.#body = this.createStream();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
7
16
|
status(code) {
|
|
8
17
|
this.#statusCode = code;
|
|
9
18
|
return this;
|
|
@@ -23,34 +32,60 @@ export class PikkuFetchHTTPResponse {
|
|
|
23
32
|
return this;
|
|
24
33
|
}
|
|
25
34
|
arrayBuffer(data) {
|
|
26
|
-
this.#
|
|
27
|
-
|
|
35
|
+
if (this.#responseMode === 'stream') {
|
|
36
|
+
this.#send(data);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
this.#body = data;
|
|
40
|
+
this.header('Content-Type', 'application/octet-stream');
|
|
41
|
+
}
|
|
28
42
|
return this;
|
|
29
43
|
}
|
|
30
44
|
json(data) {
|
|
31
|
-
this.#
|
|
32
|
-
|
|
45
|
+
if (this.#responseMode === 'stream') {
|
|
46
|
+
this.#send(JSON.stringify(data));
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
this.#body = JSON.stringify(data);
|
|
50
|
+
this.header('Content-Type', 'application/json');
|
|
51
|
+
}
|
|
33
52
|
return this;
|
|
34
53
|
}
|
|
35
54
|
text(content) {
|
|
36
|
-
this.#
|
|
37
|
-
|
|
55
|
+
if (this.#responseMode === 'stream') {
|
|
56
|
+
this.#send(content);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
this.#body = content;
|
|
60
|
+
this.header('Content-Type', 'text/plain');
|
|
61
|
+
}
|
|
38
62
|
return this;
|
|
39
63
|
}
|
|
40
64
|
html(content) {
|
|
41
|
-
this.#
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
65
|
+
if (this.#responseMode === 'stream') {
|
|
66
|
+
this.#send(content);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
this.#body = content;
|
|
70
|
+
this.header('Content-Type', 'text/html');
|
|
71
|
+
}
|
|
47
72
|
return this;
|
|
48
73
|
}
|
|
74
|
+
// public body(body: BodyInit): this {
|
|
75
|
+
// this.#body = body
|
|
76
|
+
// return this
|
|
77
|
+
// }
|
|
49
78
|
redirect(location, status = 302) {
|
|
50
79
|
this.#statusCode = status;
|
|
51
80
|
this.header('Location', location);
|
|
52
81
|
return this;
|
|
53
82
|
}
|
|
83
|
+
close() {
|
|
84
|
+
if (this.#close) {
|
|
85
|
+
this.#close();
|
|
86
|
+
}
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
54
89
|
toResponse(args) {
|
|
55
90
|
const cookieHeader = Array.from(this.#cookies.entries()).map(([name, { value, flags }]) => serializeCookie(name, value, flags));
|
|
56
91
|
this.#headers.set('Set-Cookie', cookieHeader.join(', '));
|
|
@@ -60,4 +95,21 @@ export class PikkuFetchHTTPResponse {
|
|
|
60
95
|
headers: this.#headers,
|
|
61
96
|
});
|
|
62
97
|
}
|
|
98
|
+
createStream() {
|
|
99
|
+
const encoder = new TextEncoder();
|
|
100
|
+
return new ReadableStream({
|
|
101
|
+
start: (controller) => {
|
|
102
|
+
const send = (data) => {
|
|
103
|
+
controller.enqueue(encoder.encode(`data: ${data}\n\n`));
|
|
104
|
+
};
|
|
105
|
+
const close = () => {
|
|
106
|
+
controller.close();
|
|
107
|
+
};
|
|
108
|
+
this.#send = send;
|
|
109
|
+
this.#close = close;
|
|
110
|
+
// Force initial flush
|
|
111
|
+
controller.enqueue(encoder.encode(':\n\n'));
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
}
|
|
63
115
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @module @pikku/core
|
|
3
3
|
*/
|
|
4
4
|
export * from './types/core.types.js';
|
|
5
|
-
export * from './
|
|
5
|
+
export * from './function/index.js';
|
|
6
6
|
export * from './pikku-request.js';
|
|
7
7
|
export * from './pikku-response.js';
|
|
8
8
|
export * from './services/index.js';
|
|
@@ -12,8 +12,9 @@ export * from './scheduler/index.js';
|
|
|
12
12
|
export * from './errors/index.js';
|
|
13
13
|
export * from './middleware/index.js';
|
|
14
14
|
export * from './time-utils.js';
|
|
15
|
+
export * from './utils.js';
|
|
15
16
|
export { pikkuState } from './pikku-state.js';
|
|
16
17
|
export { runMiddleware } from './middleware-runner.js';
|
|
17
|
-
export {
|
|
18
|
+
export { addHTTPRoute, addMiddleware } from './http/http-runner.js';
|
|
18
19
|
export { addChannel } from './channel/channel-runner.js';
|
|
19
20
|
export { addScheduledTask } from './scheduler/scheduler-runner.js';
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @module @pikku/core
|
|
3
3
|
*/
|
|
4
4
|
export * from './types/core.types.js';
|
|
5
|
-
export * from './
|
|
5
|
+
export * from './function/index.js';
|
|
6
6
|
export * from './pikku-request.js';
|
|
7
7
|
export * from './pikku-response.js';
|
|
8
8
|
export * from './services/index.js';
|
|
@@ -12,8 +12,9 @@ export * from './scheduler/index.js';
|
|
|
12
12
|
export * from './errors/index.js';
|
|
13
13
|
export * from './middleware/index.js';
|
|
14
14
|
export * from './time-utils.js';
|
|
15
|
+
export * from './utils.js';
|
|
15
16
|
export { pikkuState } from './pikku-state.js';
|
|
16
17
|
export { runMiddleware } from './middleware-runner.js';
|
|
17
|
-
export {
|
|
18
|
+
export { addHTTPRoute, addMiddleware } from './http/http-runner.js';
|
|
18
19
|
export { addChannel } from './channel/channel-runner.js';
|
|
19
20
|
export { addScheduledTask } from './scheduler/scheduler-runner.js';
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export const authAPIKey = ({ source, getSessionForAPIKey, jwt, }) => {
|
|
7
7
|
const middleware = async (services, { http }, next) => {
|
|
8
|
-
|
|
8
|
+
const { userSession: userSessionService, jwt: jwtService } = services;
|
|
9
|
+
if (!http?.request || userSessionService.get()) {
|
|
9
10
|
return next();
|
|
10
11
|
}
|
|
11
12
|
let apiKey = null;
|
|
@@ -18,16 +19,16 @@ export const authAPIKey = ({ source, getSessionForAPIKey, jwt, }) => {
|
|
|
18
19
|
if (apiKey) {
|
|
19
20
|
let userSession = null;
|
|
20
21
|
if (jwt) {
|
|
21
|
-
if (!
|
|
22
|
+
if (!jwtService) {
|
|
22
23
|
throw new Error('JWT service is required for JWT decoding.');
|
|
23
24
|
}
|
|
24
|
-
userSession = await
|
|
25
|
+
userSession = await jwtService.decode(apiKey);
|
|
25
26
|
}
|
|
26
27
|
else {
|
|
27
28
|
userSession = await getSessionForAPIKey(services, apiKey);
|
|
28
29
|
}
|
|
29
30
|
if (userSession) {
|
|
30
|
-
|
|
31
|
+
userSessionService.setInitial(userSession);
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
return next();
|
|
@@ -4,8 +4,9 @@ import { InvalidSessionError } from '../errors/errors.js';
|
|
|
4
4
|
*/
|
|
5
5
|
export const authBearer = ({ token, jwt, getSession, } = {}) => {
|
|
6
6
|
const middleware = async (services, { http }, next) => {
|
|
7
|
+
const { userSession: userSessionService, jwt: jwtService } = services;
|
|
7
8
|
// Skip if session already exists.
|
|
8
|
-
if (!http?.request ||
|
|
9
|
+
if (!http?.request || userSessionService.get()) {
|
|
9
10
|
return next();
|
|
10
11
|
}
|
|
11
12
|
const authHeader = http.request.header('authorization') ||
|
|
@@ -17,10 +18,10 @@ export const authBearer = ({ token, jwt, getSession, } = {}) => {
|
|
|
17
18
|
}
|
|
18
19
|
let userSession = null;
|
|
19
20
|
if (jwt) {
|
|
20
|
-
if (!
|
|
21
|
+
if (!jwtService) {
|
|
21
22
|
throw new Error('JWT service is required for JWT decoding.');
|
|
22
23
|
}
|
|
23
|
-
userSession = await
|
|
24
|
+
userSession = await jwtService.decode(bearerToken);
|
|
24
25
|
}
|
|
25
26
|
else if (token) {
|
|
26
27
|
if (bearerToken === token.value) {
|
|
@@ -31,7 +32,7 @@ export const authBearer = ({ token, jwt, getSession, } = {}) => {
|
|
|
31
32
|
userSession = await getSession(services, token);
|
|
32
33
|
}
|
|
33
34
|
if (userSession) {
|
|
34
|
-
|
|
35
|
+
userSessionService.setInitial(userSession);
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
return next();
|
|
@@ -7,43 +7,44 @@ import { getRelativeTimeOffsetFromNow, } from '../time-utils.js';
|
|
|
7
7
|
*/
|
|
8
8
|
export const authCookie = ({ name, getSessionForCookieValue, jwt, options, expiresIn, }) => {
|
|
9
9
|
const middleware = async (services, { http }, next) => {
|
|
10
|
-
|
|
10
|
+
const { userSession: userSessionService, jwt: jwtService, logger, } = services;
|
|
11
|
+
if (!http?.request || userSessionService.get()) {
|
|
11
12
|
return next();
|
|
12
13
|
}
|
|
13
14
|
let userSession = null;
|
|
14
15
|
const cookieValue = http.request.cookie(name);
|
|
15
16
|
if (cookieValue) {
|
|
16
17
|
if (jwt) {
|
|
17
|
-
if (!
|
|
18
|
+
if (!jwtService) {
|
|
18
19
|
throw new Error('JWT service is required for JWT decoding.');
|
|
19
20
|
}
|
|
20
|
-
userSession = await
|
|
21
|
+
userSession = await jwtService.decode(cookieValue);
|
|
21
22
|
}
|
|
22
23
|
else if (getSessionForCookieValue) {
|
|
23
24
|
userSession = await getSessionForCookieValue(services, cookieValue, name);
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
if (userSession) {
|
|
27
|
-
|
|
28
|
+
userSessionService.setInitial(userSession);
|
|
28
29
|
}
|
|
29
30
|
await next();
|
|
30
31
|
// Set the cookie in the response if the session has changed
|
|
31
32
|
if (!http?.response) {
|
|
32
33
|
return;
|
|
33
34
|
}
|
|
34
|
-
if (
|
|
35
|
-
const session =
|
|
35
|
+
if (userSessionService.sessionChanged) {
|
|
36
|
+
const session = userSessionService.get();
|
|
36
37
|
if (jwt) {
|
|
37
|
-
if (!
|
|
38
|
+
if (!jwtService) {
|
|
38
39
|
throw new Error('JWT service is required for JWT encoding.');
|
|
39
40
|
}
|
|
40
|
-
http.response.cookie(name, await
|
|
41
|
+
http.response.cookie(name, await jwtService.encode(expiresIn, session), {
|
|
41
42
|
...options,
|
|
42
43
|
expires: getRelativeTimeOffsetFromNow(expiresIn),
|
|
43
44
|
});
|
|
44
45
|
}
|
|
45
46
|
else {
|
|
46
|
-
|
|
47
|
+
logger.warn('No JWT service available, unable to set cookie');
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
};
|