@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.
Files changed (75) hide show
  1. package/CHANGELOG.md +16 -1
  2. package/dist/channel/channel-handler.js +16 -38
  3. package/dist/channel/channel-runner.d.ts +7 -8
  4. package/dist/channel/channel-runner.js +58 -7
  5. package/dist/channel/channel.types.d.ts +14 -27
  6. package/dist/channel/local/local-channel-runner.js +9 -4
  7. package/dist/channel/log-channels.js +4 -4
  8. package/dist/channel/serverless/serverless-channel-runner.js +17 -6
  9. package/dist/function/function-runner.d.ts +12 -0
  10. package/dist/function/function-runner.js +38 -0
  11. package/dist/{types → function}/functions.types.d.ts +12 -7
  12. package/dist/function/index.d.ts +2 -0
  13. package/dist/function/index.js +2 -0
  14. package/dist/handle-error.d.ts +1 -1
  15. package/dist/http/{http-route-runner.d.ts → http-runner.d.ts} +4 -4
  16. package/dist/http/{http-route-runner.js → http-runner.js} +81 -43
  17. package/dist/http/{http-routes.types.d.ts → http.types.d.ts} +41 -12
  18. package/dist/http/index.d.ts +2 -2
  19. package/dist/http/index.js +1 -1
  20. package/dist/http/log-http-routes.js +7 -5
  21. package/dist/http/pikku-fetch-http-request.d.ts +1 -1
  22. package/dist/http/pikku-fetch-http-response.d.ts +4 -2
  23. package/dist/http/pikku-fetch-http-response.js +64 -12
  24. package/dist/index.d.ts +3 -2
  25. package/dist/index.js +3 -2
  26. package/dist/middleware/auth-apikey.js +5 -4
  27. package/dist/middleware/auth-bearer.js +5 -4
  28. package/dist/middleware/auth-cookie.js +10 -9
  29. package/dist/permissions.d.ts +2 -2
  30. package/dist/pikku-function.d.ts +1 -0
  31. package/dist/pikku-function.js +1 -0
  32. package/dist/pikku-state.d.ts +10 -5
  33. package/dist/pikku-state.js +7 -3
  34. package/dist/scheduler/scheduler-runner.d.ts +1 -1
  35. package/dist/scheduler/scheduler-runner.js +24 -6
  36. package/dist/scheduler/scheduler.types.d.ts +3 -2
  37. package/dist/services/content-service.d.ts +33 -37
  38. package/dist/types/core.types.d.ts +15 -1
  39. package/dist/utils.d.ts +2 -0
  40. package/dist/utils.js +15 -0
  41. package/lcov.info +1240 -965
  42. package/package.json +2 -1
  43. package/src/channel/channel-handler.ts +16 -78
  44. package/src/channel/channel-runner.ts +72 -17
  45. package/src/channel/channel.types.ts +25 -63
  46. package/src/channel/local/local-channel-runner.test.ts +10 -11
  47. package/src/channel/local/local-channel-runner.ts +20 -7
  48. package/src/channel/log-channels.ts +4 -4
  49. package/src/channel/serverless/serverless-channel-runner.ts +26 -15
  50. package/src/function/function-runner.ts +91 -0
  51. package/src/{types → function}/functions.types.ts +25 -11
  52. package/src/function/index.ts +2 -0
  53. package/src/handle-error.ts +1 -1
  54. package/src/http/{http-route-runner.test.ts → http-runner.test.ts} +29 -6
  55. package/src/http/{http-route-runner.ts → http-runner.ts} +106 -73
  56. package/src/http/{http-routes.types.ts → http.types.ts} +55 -16
  57. package/src/http/index.ts +2 -2
  58. package/src/http/log-http-routes.ts +7 -5
  59. package/src/http/pikku-fetch-http-request.ts +1 -5
  60. package/src/http/pikku-fetch-http-response.ts +67 -13
  61. package/src/index.ts +3 -2
  62. package/src/middleware/auth-apikey.ts +5 -4
  63. package/src/middleware/auth-bearer.ts +5 -4
  64. package/src/middleware/auth-cookie.ts +14 -9
  65. package/src/permissions.ts +2 -4
  66. package/src/pikku-function.ts +1 -0
  67. package/src/pikku-state.ts +25 -9
  68. package/src/scheduler/scheduler-runner.ts +31 -13
  69. package/src/scheduler/scheduler.types.ts +12 -8
  70. package/src/services/content-service.ts +36 -40
  71. package/src/types/core.types.ts +20 -1
  72. package/src/utils.ts +19 -0
  73. package/tsconfig.tsbuildinfo +1 -1
  74. /package/dist/{types → function}/functions.types.js +0 -0
  75. /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 { ForbiddenError, MissingSessionError, NotFoundError, } from '../errors/errors.js';
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 APIPermission Type representing required permissions.
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 addRoute = (route) => {
53
- pikkuState('http', 'routes').push(route);
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 routes = pikkuState('http', 'routes');
78
+ const allRoutes = pikkuState('http', 'routes');
68
79
  const middleware = pikkuState('http', 'middleware');
69
- const routesMeta = pikkuState('http', 'meta');
70
- for (const route of routes) {
71
- // Skip routes that don't match the HTTP method
72
- if (route.method !== requestType.toLowerCase()) {
73
- continue;
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
- // Extract associated schema information if available
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
- schemaName,
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, schemaName } = matchedRoute;
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
- // Create session-specific services for handling the request
167
- sessionServices = await createSessionServices({ ...singletonServices, userSession }, { http }, session);
168
- const allServices = {
169
- ...singletonServices,
170
- ...sessionServices,
171
- userSession,
172
- http,
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 data = await http?.request?.data();
175
- // Validate request data against the defined schema, if any
176
- await validateSchema(singletonServices.logger, singletonServices.schema, schemaName, data);
177
- // Coerce (top level) query string parameters or date objects if specified by the schema
178
- if (options.coerceDataFromSchema && schemaName) {
179
- coerceTopLevelDataFromSchema(schemaName, data);
180
- }
181
- // Execute permission checks
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, '111', // TODO: context.get('trackingId'),
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 '../types/functions.types.js';
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?: Record<string, APIPermission[] | APIPermission>;
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?: Record<string, APIPermission[] | APIPermission>;
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 HTTPRoutesMeta = Array<{
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: XMLHttpRequestBodyInit): this;
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 {};
@@ -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, addRoute } from './http-route-runner.js';
6
- export type * from './http-routes.types.js';
5
+ export { fetch, fetchData, addHTTPRoute } from './http-runner.js';
6
+ export type * from './http.types.js';
@@ -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, addRoute } from './http-route-runner.js';
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 routes = pikkuState('http', 'routes');
8
- if (routes.length === 0) {
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
- for (const { method, route } of routes) {
14
- routesMessage += `\n\t- ${method.toUpperCase()} -> ${route}`;
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,4 +1,4 @@
1
- import { HTTPMethod, PikkuHTTPRequest, PikkuQuery } from './http-routes.types.js';
1
+ import { HTTPMethod, PikkuHTTPRequest, PikkuQuery } from './http.types.js';
2
2
  /**
3
3
  * Abstract class representing a pikku request.
4
4
  * @template In - The type of the request body.
@@ -1,7 +1,8 @@
1
- import { PikkuHTTPResponse } from './http-routes.types.js';
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.#body = data;
27
- this.header('Content-Type', 'application/octet-stream');
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.#body = JSON.stringify(data);
32
- this.header('Content-Type', 'application/json');
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.#body = content;
37
- this.header('Content-Type', 'text/plain');
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.#body = content;
42
- this.header('Content-Type', 'text/html');
43
- return this;
44
- }
45
- body(body) {
46
- this.#body = body;
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 './types/functions.types.js';
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 { addRoute, addMiddleware } from './http/http-route-runner.js';
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 './types/functions.types.js';
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 { addRoute, addMiddleware } from './http/http-route-runner.js';
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
- if (!http?.request || services.userSession.get()) {
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 (!services.jwt) {
22
+ if (!jwtService) {
22
23
  throw new Error('JWT service is required for JWT decoding.');
23
24
  }
24
- userSession = await services.jwt.decode(apiKey);
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
- services.userSession.setInitial(userSession);
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 || services.userSession.get()) {
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 (!services.jwt) {
21
+ if (!jwtService) {
21
22
  throw new Error('JWT service is required for JWT decoding.');
22
23
  }
23
- userSession = await services.jwt.decode(bearerToken);
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
- services.userSession.setInitial(userSession);
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
- if (!http?.request || services.userSession.get()) {
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 (!services.jwt) {
18
+ if (!jwtService) {
18
19
  throw new Error('JWT service is required for JWT decoding.');
19
20
  }
20
- userSession = await services.jwt.decode(cookieValue);
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
- services.userSession.setInitial(userSession);
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 (services.userSession.sessionChanged) {
35
- const session = services.userSession.get();
35
+ if (userSessionService.sessionChanged) {
36
+ const session = userSessionService.get();
36
37
  if (jwt) {
37
- if (!services.jwt) {
38
+ if (!jwtService) {
38
39
  throw new Error('JWT service is required for JWT encoding.');
39
40
  }
40
- http.response.cookie(name, await services.jwt.encode(expiresIn, session), {
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
- services.logger.warn('No JWT service available, unable to set cookie');
47
+ logger.warn('No JWT service available, unable to set cookie');
47
48
  }
48
49
  }
49
50
  };