@pikku/core 0.9.11 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/errors/errors.d.ts +2 -0
  3. package/dist/errors/errors.js +6 -0
  4. package/dist/function/function-runner.d.ts +15 -9
  5. package/dist/function/function-runner.js +55 -37
  6. package/dist/function/functions.types.d.ts +67 -1
  7. package/dist/function/functions.types.js +43 -1
  8. package/dist/index.d.ts +2 -6
  9. package/dist/index.js +2 -6
  10. package/dist/middleware/auth-apikey.d.ts +19 -11
  11. package/dist/middleware/auth-apikey.js +33 -33
  12. package/dist/middleware/auth-bearer.d.ts +34 -7
  13. package/dist/middleware/auth-bearer.js +53 -36
  14. package/dist/middleware/auth-cookie.d.ts +30 -12
  15. package/dist/middleware/auth-cookie.js +51 -43
  16. package/dist/middleware/timeout.d.ts +6 -2
  17. package/dist/middleware/timeout.js +7 -9
  18. package/dist/middleware-runner.d.ts +46 -43
  19. package/dist/middleware-runner.js +100 -69
  20. package/dist/permissions.d.ts +82 -20
  21. package/dist/permissions.js +176 -62
  22. package/dist/pikku-state.d.ts +46 -1
  23. package/dist/pikku-state.js +16 -1
  24. package/dist/services/content-service.d.ts +2 -3
  25. package/dist/services/index.d.ts +0 -1
  26. package/dist/services/index.js +0 -1
  27. package/dist/services/local-content.d.ts +2 -4
  28. package/dist/types/core.types.d.ts +110 -4
  29. package/dist/types/core.types.js +44 -1
  30. package/dist/utils.d.ts +1 -1
  31. package/dist/wirings/channel/channel-handler.d.ts +2 -2
  32. package/dist/wirings/channel/channel-handler.js +16 -12
  33. package/dist/wirings/channel/channel-runner.d.ts +2 -2
  34. package/dist/wirings/channel/channel-runner.js +6 -24
  35. package/dist/wirings/channel/channel.types.d.ts +15 -14
  36. package/dist/wirings/channel/local/local-channel-runner.js +12 -12
  37. package/dist/wirings/channel/serverless/serverless-channel-runner.js +7 -8
  38. package/dist/wirings/cli/channel/cli-channel-runner.d.ts +12 -0
  39. package/dist/wirings/cli/channel/cli-channel-runner.js +83 -0
  40. package/dist/wirings/cli/channel/index.d.ts +1 -0
  41. package/dist/wirings/cli/channel/index.js +1 -0
  42. package/dist/wirings/cli/cli-runner.d.ts +42 -0
  43. package/dist/wirings/cli/cli-runner.js +352 -0
  44. package/dist/wirings/cli/cli.types.d.ts +204 -0
  45. package/dist/wirings/cli/cli.types.js +1 -0
  46. package/dist/wirings/cli/command-parser.d.ts +19 -0
  47. package/dist/wirings/cli/command-parser.js +403 -0
  48. package/dist/wirings/cli/index.d.ts +5 -0
  49. package/dist/wirings/cli/index.js +5 -0
  50. package/dist/wirings/http/http-runner.d.ts +59 -10
  51. package/dist/wirings/http/http-runner.js +138 -131
  52. package/dist/wirings/http/http.types.d.ts +11 -10
  53. package/dist/wirings/http/index.d.ts +1 -1
  54. package/dist/wirings/http/index.js +1 -1
  55. package/dist/wirings/http/pikku-fetch-http-response.js +3 -1
  56. package/dist/wirings/http/routers/http-router.d.ts +0 -2
  57. package/dist/wirings/http/routers/path-to-regex.d.ts +1 -1
  58. package/dist/wirings/http/routers/path-to-regex.js +5 -34
  59. package/dist/wirings/mcp/mcp-endpoint-registry.d.ts +1 -1
  60. package/dist/wirings/mcp/mcp-runner.d.ts +4 -5
  61. package/dist/wirings/mcp/mcp-runner.js +32 -34
  62. package/dist/wirings/mcp/mcp.types.d.ts +38 -14
  63. package/dist/wirings/queue/queue-runner.d.ts +2 -2
  64. package/dist/wirings/queue/queue-runner.js +25 -27
  65. package/dist/wirings/queue/queue.types.d.ts +6 -5
  66. package/dist/wirings/rpc/index.d.ts +1 -1
  67. package/dist/wirings/rpc/index.js +1 -1
  68. package/dist/wirings/rpc/rpc-runner.d.ts +6 -18
  69. package/dist/wirings/rpc/rpc-runner.js +13 -8
  70. package/dist/wirings/scheduler/scheduler-runner.d.ts +2 -2
  71. package/dist/wirings/scheduler/scheduler-runner.js +38 -35
  72. package/dist/wirings/scheduler/scheduler.types.d.ts +5 -4
  73. package/package.json +9 -7
  74. package/src/errors/errors.ts +6 -0
  75. package/src/factory-functions.test.ts +60 -1
  76. package/src/function/function-runner.test.ts +81 -26
  77. package/src/function/function-runner.ts +89 -57
  78. package/src/function/functions.types.ts +85 -2
  79. package/src/index.ts +6 -19
  80. package/src/middleware/auth-apikey.ts +42 -57
  81. package/src/middleware/auth-bearer.ts +66 -49
  82. package/src/middleware/auth-cookie.ts +63 -82
  83. package/src/middleware/timeout.ts +10 -14
  84. package/src/middleware-runner.test.ts +24 -16
  85. package/src/middleware-runner.ts +115 -87
  86. package/src/permissions.test.ts +26 -27
  87. package/src/permissions.ts +216 -104
  88. package/src/pikku-state.ts +79 -2
  89. package/src/services/content-service.ts +5 -4
  90. package/src/services/index.ts +0 -1
  91. package/src/services/local-content.ts +9 -4
  92. package/src/types/core.types.ts +143 -5
  93. package/src/utils.ts +1 -1
  94. package/src/wirings/channel/channel-handler.ts +18 -21
  95. package/src/wirings/channel/channel-runner.ts +19 -32
  96. package/src/wirings/channel/channel.types.ts +29 -16
  97. package/src/wirings/channel/local/local-channel-runner.ts +25 -15
  98. package/src/wirings/channel/serverless/serverless-channel-runner.ts +15 -17
  99. package/src/wirings/cli/channel/cli-channel-runner.ts +123 -0
  100. package/src/wirings/cli/channel/index.ts +1 -0
  101. package/src/wirings/cli/cli-runner.test.ts +403 -0
  102. package/src/wirings/cli/cli-runner.ts +539 -0
  103. package/src/wirings/cli/cli.types.ts +350 -0
  104. package/src/wirings/cli/command-parser.test.ts +445 -0
  105. package/src/wirings/cli/command-parser.ts +504 -0
  106. package/src/wirings/cli/index.ts +13 -0
  107. package/src/wirings/http/http-runner.test.ts +8 -7
  108. package/src/wirings/http/http-runner.ts +165 -158
  109. package/src/wirings/http/http.types.ts +58 -11
  110. package/src/wirings/http/index.ts +7 -1
  111. package/src/wirings/http/pikku-fetch-http-response.ts +3 -1
  112. package/src/wirings/http/routers/http-router.ts +0 -2
  113. package/src/wirings/http/routers/path-to-regex.test.ts +4 -5
  114. package/src/wirings/http/routers/path-to-regex.ts +6 -43
  115. package/src/wirings/mcp/mcp-endpoint-registry.ts +1 -1
  116. package/src/wirings/mcp/mcp-runner.ts +58 -55
  117. package/src/wirings/mcp/mcp.types.ts +65 -14
  118. package/src/wirings/queue/queue-runner.ts +44 -47
  119. package/src/wirings/queue/queue.types.ts +10 -6
  120. package/src/wirings/rpc/index.ts +1 -1
  121. package/src/wirings/rpc/rpc-runner.ts +27 -9
  122. package/src/wirings/scheduler/scheduler-runner.ts +58 -56
  123. package/src/wirings/scheduler/scheduler.types.ts +10 -3
  124. package/tsconfig.tsbuildinfo +1 -1
@@ -1,20 +1,38 @@
1
1
  import { SerializeOptions } from 'cookie';
2
- import { CoreConfig, CoreSingletonServices, CoreUserSession, CorePikkuMiddleware } from '../types/core.types.js';
3
2
  import { RelativeTimeInput } from '../time-utils.js';
4
3
  /**
5
- * Cookie middleware that extracts a session from cookies.
4
+ * Cookie-based session middleware that uses JWT for encoding/decoding session data.
6
5
  *
7
- * @param options.name - List of cookie names to check.
8
- * @param options.getSessionForCookieValue - Function to retrieve a session using a cookie value.
6
+ * Reads session from a cookie on incoming requests and automatically updates the cookie
7
+ * when the session changes (e.g., after login).
8
+ *
9
+ * @param options.name - Cookie name
10
+ * @param options.expiresIn - Cookie expiration time (e.g., { value: 30, unit: 'day' })
11
+ * @param options.options - Cookie serialization options (httpOnly, secure, sameSite, etc.)
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import { authCookie } from '@pikku/core/middleware'
16
+ *
17
+ * addHTTPMiddleware([
18
+ * authCookie({
19
+ * name: 'session',
20
+ * expiresIn: { value: 30, unit: 'day' },
21
+ * options: {
22
+ * httpOnly: true,
23
+ * secure: true,
24
+ * sameSite: 'strict',
25
+ * path: '/'
26
+ * }
27
+ * })
28
+ * ])
29
+ * ```
9
30
  */
10
- export declare const authCookie: <SingletonServices extends CoreSingletonServices<CoreConfig>, UserSession extends CoreUserSession>({ name, getSessionForCookieValue, jwt, options, expiresIn, }: {
31
+ export declare const authCookie: import("../types/core.types.js").CorePikkuMiddlewareFactory<{
11
32
  name: string;
12
33
  options: SerializeOptions;
13
34
  expiresIn: RelativeTimeInput;
14
- } & ({
15
- getSessionForCookieValue: (services: SingletonServices, cookieValue: string, cookieName: string) => Promise<UserSession>;
16
- jwt?: false;
17
- } | {
18
- getSessionForCookieValue?: undefined;
19
- jwt: true;
20
- })) => CorePikkuMiddleware;
35
+ }, import("../types/core.types.js").CoreSingletonServices<{
36
+ logLevel?: import("../index.js").LogLevel;
37
+ secrets?: {};
38
+ }>, import("../types/core.types.js").CoreUserSession>;
@@ -1,52 +1,60 @@
1
+ import { pikkuMiddleware, pikkuMiddlewareFactory } from '../types/core.types.js';
1
2
  import { getRelativeTimeOffsetFromNow, } from '../time-utils.js';
2
3
  /**
3
- * Cookie middleware that extracts a session from cookies.
4
+ * Cookie-based session middleware that uses JWT for encoding/decoding session data.
4
5
  *
5
- * @param options.name - List of cookie names to check.
6
- * @param options.getSessionForCookieValue - Function to retrieve a session using a cookie value.
6
+ * Reads session from a cookie on incoming requests and automatically updates the cookie
7
+ * when the session changes (e.g., after login).
8
+ *
9
+ * @param options.name - Cookie name
10
+ * @param options.expiresIn - Cookie expiration time (e.g., { value: 30, unit: 'day' })
11
+ * @param options.options - Cookie serialization options (httpOnly, secure, sameSite, etc.)
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import { authCookie } from '@pikku/core/middleware'
16
+ *
17
+ * addHTTPMiddleware([
18
+ * authCookie({
19
+ * name: 'session',
20
+ * expiresIn: { value: 30, unit: 'day' },
21
+ * options: {
22
+ * httpOnly: true,
23
+ * secure: true,
24
+ * sameSite: 'strict',
25
+ * path: '/'
26
+ * }
27
+ * })
28
+ * ])
29
+ * ```
7
30
  */
8
- export const authCookie = ({ name, getSessionForCookieValue, jwt, options, expiresIn, }) => {
9
- const middleware = async (services, { http }, next) => {
10
- const { userSession: userSessionService, jwt: jwtService, logger, } = services;
11
- if (!http?.request || userSessionService.get()) {
12
- return next();
13
- }
14
- let userSession = null;
15
- const cookieValue = http.request.cookie(name);
16
- if (cookieValue) {
17
- if (jwt) {
18
- if (!jwtService) {
19
- throw new Error('JWT service is required for JWT decoding.');
20
- }
21
- userSession = await jwtService.decode(cookieValue);
22
- }
23
- else if (getSessionForCookieValue) {
24
- userSession = await getSessionForCookieValue(services, cookieValue, name);
25
- }
26
- }
31
+ export const authCookie = pikkuMiddlewareFactory(({ name, options, expiresIn }) => pikkuMiddleware(async ({ userSession: userSessionService, jwt: jwtService, logger }, { http }, next) => {
32
+ if (!http?.request || userSessionService.get()) {
33
+ return next();
34
+ }
35
+ // Try to decode session from cookie
36
+ const cookieValue = http.request.cookie(name);
37
+ if (cookieValue && jwtService) {
38
+ const userSession = await jwtService.decode(cookieValue);
27
39
  if (userSession) {
28
40
  userSessionService.setInitial(userSession);
29
41
  }
30
- await next();
31
- // Set the cookie in the response if the session has changed
32
- if (!http?.response) {
33
- return;
42
+ }
43
+ await next();
44
+ // Set the cookie in the response if the session has changed
45
+ if (!http?.response) {
46
+ return;
47
+ }
48
+ if (userSessionService.sessionChanged) {
49
+ const session = userSessionService.get();
50
+ if (jwtService) {
51
+ http.response.cookie(name, await jwtService.encode(expiresIn, session), {
52
+ ...options,
53
+ expires: getRelativeTimeOffsetFromNow(expiresIn),
54
+ });
34
55
  }
35
- if (userSessionService.sessionChanged) {
36
- const session = userSessionService.get();
37
- if (jwt) {
38
- if (!jwtService) {
39
- throw new Error('JWT service is required for JWT encoding.');
40
- }
41
- http.response.cookie(name, await jwtService.encode(expiresIn, session), {
42
- ...options,
43
- expires: getRelativeTimeOffsetFromNow(expiresIn),
44
- });
45
- }
46
- else {
47
- logger.warn('No JWT service available, unable to set cookie');
48
- }
56
+ else {
57
+ logger.warn('No JWT service available, unable to set cookie');
49
58
  }
50
- };
51
- return middleware;
52
- };
59
+ }
60
+ }));
@@ -1,2 +1,6 @@
1
- import { CorePikkuMiddleware } from '../types/core.types.js';
2
- export declare const timeout: (timeout: number) => CorePikkuMiddleware;
1
+ export declare const timeout: () => import("../types/core.types.js").CorePikkuMiddlewareFactory<{
2
+ timeout: number;
3
+ }, import("../types/core.types.js").CoreSingletonServices<{
4
+ logLevel?: import("../index.js").LogLevel;
5
+ secrets?: {};
6
+ }>, import("../types/core.types.js").CoreUserSession>;
@@ -1,10 +1,8 @@
1
1
  import { RequestTimeoutError } from '../errors/errors.js';
2
- export const timeout = (timeout) => {
3
- const middleware = async (_services, _interaction, next) => {
4
- setTimeout(() => {
5
- throw new RequestTimeoutError();
6
- }, timeout);
7
- await next();
8
- };
9
- return middleware;
10
- };
2
+ import { pikkuMiddleware, pikkuMiddlewareFactory } from '../types/core.types.js';
3
+ export const timeout = () => pikkuMiddlewareFactory(({ timeout }) => pikkuMiddleware(async (_services, _interaction, next) => {
4
+ setTimeout(() => {
5
+ throw new RequestTimeoutError();
6
+ }, timeout);
7
+ await next();
8
+ }));
@@ -1,5 +1,5 @@
1
1
  import { UserSessionService } from './services/user-session-service.js';
2
- import { CoreSingletonServices, PikkuInteraction, CorePikkuMiddleware, PikkuWiringTypes } from './types/core.types.js';
2
+ import { CoreSingletonServices, PikkuInteraction, CorePikkuMiddleware, CorePikkuMiddlewareGroup, PikkuWiringTypes, MiddlewareMetadata } from './types/core.types.js';
3
3
  /**
4
4
  * Runs a chain of middleware functions in sequence before executing the main function.
5
5
  *
@@ -21,73 +21,76 @@ export declare const runMiddleware: <Middleware extends CorePikkuMiddleware>(ser
21
21
  userSession?: UserSessionService<any>;
22
22
  }, interaction: PikkuInteraction, middlewares: readonly Middleware[], main?: () => Promise<unknown>) => Promise<unknown>;
23
23
  /**
24
- * Adds global middleware for a specific tag.
24
+ * Registers global middleware for a specific tag.
25
25
  *
26
- * This function allows you to register middleware that will be applied to
27
- * any wiring (HTTP, Channel, Queue, Scheduler, MCP) that includes the matching tag.
26
+ * This function registers middleware at runtime that will be applied to
27
+ * any wiring (HTTP, Channel, Queue, Scheduler, MCP, CLI) that includes the matching tag.
28
+ *
29
+ * For tree-shaking benefits, wrap in a factory function:
30
+ * `export const x = () => addMiddleware('tag', [...])`
31
+ *
32
+ * Accepts an array that can contain:
33
+ * - Direct middleware functions (CorePikkuMiddleware)
34
+ * - Factory middleware functions (CorePikkuMiddlewareFactory)
28
35
  *
29
36
  * @template PikkuMiddleware The middleware type.
30
37
  * @param {string} tag - The tag that the middleware should apply to.
31
- * @param {PikkuMiddleware[]} middleware - The middleware array to apply for the specified tag.
38
+ * @param {CorePikkuMiddlewareGroup} middleware - Array of middleware for this tag.
32
39
  *
33
- * @throws {Error} If middleware for the tag already exists.
40
+ * @returns {CorePikkuMiddlewareGroup} The middleware array (for chaining/wrapping).
34
41
  *
35
42
  * @example
36
43
  * ```typescript
37
- * // Add admin middleware for admin endpoints
38
- * addMiddleware('admin', [adminMiddleware])
39
- *
40
- * // Add authentication middleware for auth endpoints
41
- * addMiddleware('auth', [authMiddleware])
44
+ * // Recommended: tree-shakeable
45
+ * export const adminMiddleware = () => addMiddleware('admin', [
46
+ * authMiddleware,
47
+ * loggingMiddleware({ level: 'info' })
48
+ * ])
42
49
  *
43
- * // Add logging middleware for all API endpoints
44
- * addMiddleware('api', [loggingMiddleware])
50
+ * // Also works: no tree-shaking
51
+ * export const apiMiddleware = addMiddleware('api', [
52
+ * rateLimitMiddleware
53
+ * ])
45
54
  * ```
46
55
  */
47
- export declare const addMiddleware: <PikkuMiddleware extends CorePikkuMiddleware>(tag: string, middleware: PikkuMiddleware[]) => void;
56
+ export declare const addMiddleware: <PikkuMiddleware extends CorePikkuMiddleware>(tag: string, middleware: CorePikkuMiddlewareGroup) => CorePikkuMiddlewareGroup;
48
57
  /**
49
- * Retrieves middleware for a given set of tags.
58
+ * Retrieves a registered middleware function by its name.
50
59
  *
51
- * This function looks up all middleware registered for any of the provided tags
52
- * and returns them as a flattened array.
60
+ * This function looks up middleware that was registered with registerMiddleware.
61
+ * It's used internally by the framework to resolve middleware references in metadata.
53
62
  *
54
- * @param {string[]} tags - Array of tags to look up middleware for.
55
- * @returns {CorePikkuMiddleware[]} Array of middleware functions that apply to the given tags.
63
+ * @param {string} name - The unique name (pikkuFuncName) of the middleware function.
64
+ * @returns {CorePikkuMiddleware | undefined} The middleware function, or undefined if not found.
56
65
  *
57
- * @example
58
- * ```typescript
59
- * // Get all middleware for tags 'api' and 'auth'
60
- * const middleware = getMiddlewareForTags(['api', 'auth'])
61
- * ```
66
+ * @internal
62
67
  */
63
- export declare const getMiddlewareForTags: (tags?: string[]) => CorePikkuMiddleware[];
68
+ export declare const getMiddlewareByName: (name: string) => CorePikkuMiddleware | undefined;
64
69
  /**
65
- * Combines tag-based middleware with wiring-specific middleware and function-level middleware.
70
+ * Combines wiring-specific middleware with function-level middleware.
66
71
  *
67
- * This helper function gets middleware for tags and combines it with any
68
- * wiring-specific middleware and function-level middleware, avoiding the need for manual spreading.
72
+ * This function resolves middleware metadata into actual middleware functions and combines them.
73
+ * It filters out wire middleware without tags from inheritedMiddleware to avoid duplication
74
+ * (those are passed separately as wireMiddleware).
69
75
  *
70
76
  * @param {object} options - Configuration object for combining middleware.
71
- * @param {CorePikkuMiddleware[] | undefined} options.wiringMiddleware - Wiring-specific middleware.
72
- * @param {string[] | undefined} options.wiringTags - Array of wiring-level tags to look up middleware for.
73
- * @param {CorePikkuMiddleware[] | undefined} options.funcMiddleware - Function-level middleware.
74
- * @param {string[] | undefined} options.funcTags - Array of function-level tags to look up middleware for.
75
- * @returns {CorePikkuMiddleware[]} Combined array of tag-based and wiring-specific middleware.
77
+ * @param {MiddlewareMetadata[] | undefined} options.inheritedMiddleware - Metadata from wiring (HTTP + tags + wire with tags).
78
+ * @param {CorePikkuMiddleware[] | undefined} options.wireMiddleware - Inline wire middleware.
79
+ * @param {MiddlewareMetadata[] | undefined} options.funcMiddleware - Function middleware metadata.
80
+ * @returns {CorePikkuMiddleware[]} Combined array of resolved middleware.
76
81
  *
77
82
  * @example
78
83
  * ```typescript
79
- * const combined = combineMiddleware({
80
- * wiringMiddleware: httpRoute.middleware,
81
- * wiringTags: httpRoute.tags,
82
- * funcMiddleware: funcConfig.middleware,
83
- * funcTags: funcConfig.tags
84
+ * const combined = combineMiddleware(wireType, wireId, {
85
+ * inheritedMiddleware: meta.middleware,
86
+ * wireMiddleware: [inlineMiddleware],
87
+ * funcMiddleware: funcMeta.middleware
84
88
  * })
85
89
  * ```
86
90
  */
87
- export declare const combineMiddleware: (wireType: PikkuWiringTypes, uid: string, { httpMiddleware, wiringMiddleware, wiringTags, funcMiddleware, funcTags, }?: {
88
- httpMiddleware?: CorePikkuMiddleware[];
89
- wiringMiddleware?: CorePikkuMiddleware[];
90
- wiringTags?: string[];
91
+ export declare const combineMiddleware: (wireType: PikkuWiringTypes, uid: string, { wireInheritedMiddleware, wireMiddleware, funcInheritedMiddleware, funcMiddleware, }?: {
92
+ wireInheritedMiddleware?: MiddlewareMetadata[];
93
+ wireMiddleware?: CorePikkuMiddleware[];
94
+ funcInheritedMiddleware?: MiddlewareMetadata[];
91
95
  funcMiddleware?: CorePikkuMiddleware[];
92
- funcTags?: string[];
93
96
  }) => readonly CorePikkuMiddleware[];
@@ -33,66 +33,58 @@ export const runMiddleware = async (services, interaction, middlewares, main) =>
33
33
  return result;
34
34
  };
35
35
  /**
36
- * Adds global middleware for a specific tag.
36
+ * Registers global middleware for a specific tag.
37
37
  *
38
- * This function allows you to register middleware that will be applied to
39
- * any wiring (HTTP, Channel, Queue, Scheduler, MCP) that includes the matching tag.
38
+ * This function registers middleware at runtime that will be applied to
39
+ * any wiring (HTTP, Channel, Queue, Scheduler, MCP, CLI) that includes the matching tag.
40
+ *
41
+ * For tree-shaking benefits, wrap in a factory function:
42
+ * `export const x = () => addMiddleware('tag', [...])`
43
+ *
44
+ * Accepts an array that can contain:
45
+ * - Direct middleware functions (CorePikkuMiddleware)
46
+ * - Factory middleware functions (CorePikkuMiddlewareFactory)
40
47
  *
41
48
  * @template PikkuMiddleware The middleware type.
42
49
  * @param {string} tag - The tag that the middleware should apply to.
43
- * @param {PikkuMiddleware[]} middleware - The middleware array to apply for the specified tag.
50
+ * @param {CorePikkuMiddlewareGroup} middleware - Array of middleware for this tag.
44
51
  *
45
- * @throws {Error} If middleware for the tag already exists.
52
+ * @returns {CorePikkuMiddlewareGroup} The middleware array (for chaining/wrapping).
46
53
  *
47
54
  * @example
48
55
  * ```typescript
49
- * // Add admin middleware for admin endpoints
50
- * addMiddleware('admin', [adminMiddleware])
51
- *
52
- * // Add authentication middleware for auth endpoints
53
- * addMiddleware('auth', [authMiddleware])
56
+ * // Recommended: tree-shakeable
57
+ * export const adminMiddleware = () => addMiddleware('admin', [
58
+ * authMiddleware,
59
+ * loggingMiddleware({ level: 'info' })
60
+ * ])
54
61
  *
55
- * // Add logging middleware for all API endpoints
56
- * addMiddleware('api', [loggingMiddleware])
62
+ * // Also works: no tree-shaking
63
+ * export const apiMiddleware = addMiddleware('api', [
64
+ * rateLimitMiddleware
65
+ * ])
57
66
  * ```
58
67
  */
59
68
  export const addMiddleware = (tag, middleware) => {
60
- const middlewareStore = pikkuState('misc', 'middleware');
61
- // Check if tag already exists
62
- if (middlewareStore[tag]) {
63
- throw new Error(`Middleware for tag '${tag}' already exists. Use a different tag or remove the existing middleware first.`);
64
- }
65
- middlewareStore[tag] = middleware;
69
+ const tagGroups = pikkuState('middleware', 'tagGroup');
70
+ tagGroups[tag] = middleware;
71
+ return middleware;
66
72
  };
67
73
  /**
68
- * Retrieves middleware for a given set of tags.
74
+ * Retrieves a registered middleware function by its name.
69
75
  *
70
- * This function looks up all middleware registered for any of the provided tags
71
- * and returns them as a flattened array.
76
+ * This function looks up middleware that was registered with registerMiddleware.
77
+ * It's used internally by the framework to resolve middleware references in metadata.
72
78
  *
73
- * @param {string[]} tags - Array of tags to look up middleware for.
74
- * @returns {CorePikkuMiddleware[]} Array of middleware functions that apply to the given tags.
79
+ * @param {string} name - The unique name (pikkuFuncName) of the middleware function.
80
+ * @returns {CorePikkuMiddleware | undefined} The middleware function, or undefined if not found.
75
81
  *
76
- * @example
77
- * ```typescript
78
- * // Get all middleware for tags 'api' and 'auth'
79
- * const middleware = getMiddlewareForTags(['api', 'auth'])
80
- * ```
82
+ * @internal
81
83
  */
82
- export const getMiddlewareForTags = (tags) => {
83
- if (!tags || tags.length === 0) {
84
- return [];
85
- }
84
+ export const getMiddlewareByName = (name) => {
86
85
  const middlewareStore = pikkuState('misc', 'middleware');
87
- const applicableMiddleware = [];
88
- // Collect middleware for all matching tags
89
- for (const tag of tags) {
90
- const tagMiddleware = middlewareStore[tag];
91
- if (tagMiddleware) {
92
- applicableMiddleware.push(...tagMiddleware);
93
- }
94
- }
95
- return applicableMiddleware;
86
+ const middleware = middlewareStore[name];
87
+ return middleware?.[0];
96
88
  };
97
89
  const middlewareCache = {
98
90
  [PikkuWiringTypes.http]: {},
@@ -101,47 +93,86 @@ const middlewareCache = {
101
93
  [PikkuWiringTypes.queue]: {},
102
94
  [PikkuWiringTypes.scheduler]: {},
103
95
  [PikkuWiringTypes.mcp]: {},
96
+ [PikkuWiringTypes.cli]: {},
104
97
  };
105
98
  /**
106
- * Combines tag-based middleware with wiring-specific middleware and function-level middleware.
99
+ * Combines wiring-specific middleware with function-level middleware.
107
100
  *
108
- * This helper function gets middleware for tags and combines it with any
109
- * wiring-specific middleware and function-level middleware, avoiding the need for manual spreading.
101
+ * This function resolves middleware metadata into actual middleware functions and combines them.
102
+ * It filters out wire middleware without tags from inheritedMiddleware to avoid duplication
103
+ * (those are passed separately as wireMiddleware).
110
104
  *
111
105
  * @param {object} options - Configuration object for combining middleware.
112
- * @param {CorePikkuMiddleware[] | undefined} options.wiringMiddleware - Wiring-specific middleware.
113
- * @param {string[] | undefined} options.wiringTags - Array of wiring-level tags to look up middleware for.
114
- * @param {CorePikkuMiddleware[] | undefined} options.funcMiddleware - Function-level middleware.
115
- * @param {string[] | undefined} options.funcTags - Array of function-level tags to look up middleware for.
116
- * @returns {CorePikkuMiddleware[]} Combined array of tag-based and wiring-specific middleware.
106
+ * @param {MiddlewareMetadata[] | undefined} options.inheritedMiddleware - Metadata from wiring (HTTP + tags + wire with tags).
107
+ * @param {CorePikkuMiddleware[] | undefined} options.wireMiddleware - Inline wire middleware.
108
+ * @param {MiddlewareMetadata[] | undefined} options.funcMiddleware - Function middleware metadata.
109
+ * @returns {CorePikkuMiddleware[]} Combined array of resolved middleware.
117
110
  *
118
111
  * @example
119
112
  * ```typescript
120
- * const combined = combineMiddleware({
121
- * wiringMiddleware: httpRoute.middleware,
122
- * wiringTags: httpRoute.tags,
123
- * funcMiddleware: funcConfig.middleware,
124
- * funcTags: funcConfig.tags
113
+ * const combined = combineMiddleware(wireType, wireId, {
114
+ * inheritedMiddleware: meta.middleware,
115
+ * wireMiddleware: [inlineMiddleware],
116
+ * funcMiddleware: funcMeta.middleware
125
117
  * })
126
118
  * ```
127
119
  */
128
- export const combineMiddleware = (wireType, uid, { httpMiddleware, wiringMiddleware, wiringTags, funcMiddleware, funcTags, } = {}) => {
120
+ export const combineMiddleware = (wireType, uid, { wireInheritedMiddleware, wireMiddleware, funcInheritedMiddleware, funcMiddleware, } = {}) => {
129
121
  if (middlewareCache[wireType][uid]) {
130
122
  return middlewareCache[wireType][uid];
131
123
  }
132
- // Run middleware in specific order:
133
- // 1) wiringTags middleware
134
- // 2) wiringMiddleware
135
- // 3) funcMiddleware
136
- // 4) funcTags middleware
137
- const wiringTaggedMiddleware = getMiddlewareForTags(wiringTags);
138
- const funcTaggedMiddleware = getMiddlewareForTags(funcTags);
139
- middlewareCache[wireType][uid] = freezeDedupe([
140
- ...(httpMiddleware || []),
141
- ...wiringTaggedMiddleware,
142
- ...(wiringMiddleware || []),
143
- ...(funcMiddleware || []),
144
- ...funcTaggedMiddleware,
145
- ]);
124
+ const resolved = [];
125
+ // 1. Resolve wire inherited middleware (HTTP + tag groups + individual wire middleware)
126
+ if (wireInheritedMiddleware) {
127
+ for (const meta of wireInheritedMiddleware) {
128
+ if (meta.type === 'http') {
129
+ // Look up HTTP middleware group from pikkuState
130
+ const group = pikkuState('middleware', 'httpGroup')[meta.route];
131
+ if (group) {
132
+ // At runtime, all factories should be resolved to middleware
133
+ resolved.push(...group);
134
+ }
135
+ }
136
+ else if (meta.type === 'tag') {
137
+ // Look up tag middleware group from pikkuState
138
+ const group = pikkuState('middleware', 'tagGroup')[meta.tag];
139
+ if (group) {
140
+ // At runtime, all factories should be resolved to middleware
141
+ resolved.push(...group);
142
+ }
143
+ }
144
+ else if (meta.type === 'wire') {
145
+ // Individual wire middleware (exported, not inline)
146
+ const middleware = getMiddlewareByName(meta.name);
147
+ if (middleware) {
148
+ resolved.push(middleware);
149
+ }
150
+ }
151
+ }
152
+ }
153
+ // 2. Add inline wire middleware
154
+ if (wireMiddleware) {
155
+ resolved.push(...wireMiddleware);
156
+ }
157
+ // 3. Resolve function inherited middleware (only tags, wire middleware already handled)
158
+ if (funcInheritedMiddleware) {
159
+ for (const meta of funcInheritedMiddleware) {
160
+ if (meta.type === 'tag') {
161
+ // Look up tag middleware group from pikkuState
162
+ const group = pikkuState('middleware', 'tagGroup')[meta.tag];
163
+ if (group) {
164
+ // At runtime, all factories should be resolved to middleware
165
+ resolved.push(...group);
166
+ }
167
+ }
168
+ // Note: wire middleware is already handled in wireInheritedMiddleware
169
+ }
170
+ }
171
+ // 4. Add inline function middleware
172
+ if (funcMiddleware) {
173
+ resolved.push(...funcMiddleware);
174
+ }
175
+ // Deduplicate and freeze
176
+ middlewareCache[wireType][uid] = freezeDedupe(resolved);
146
177
  return middlewareCache[wireType][uid];
147
178
  };