@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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
+ # @pikku/core
2
+
1
3
  ## 0.9.0
2
4
 
5
+ ## 0.10.0
6
+
7
+ This release includes significant improvements across the framework including tree-shaking support, middleware/permission factories, enhanced CLI functionality, improved TypeScript type safety, and comprehensive test strategies.
8
+
9
+ For complete details, see https://pikku.dev/changelogs/0_10_0.md
10
+
11
+ ## 0.9.12-next.0
12
+
13
+ ### Patch Changes
14
+
15
+ - feat: running @pikku/cli using pikku
16
+
3
17
  ## 0.9.11
4
18
 
5
19
  ### Patch Changes
@@ -2,6 +2,8 @@
2
2
  * The server cannot or will not process the request due to client error (e.g., malformed request syntax).
3
3
  */
4
4
  import { PikkuError } from './error-handler.js';
5
+ export declare class InvalidMiddlewareInteractionError extends PikkuError {
6
+ }
5
7
  /**
6
8
  * The server cannot or will not process the request due to client error (e.g., malformed request syntax).
7
9
  * @group Error
@@ -2,6 +2,12 @@
2
2
  * The server cannot or will not process the request due to client error (e.g., malformed request syntax).
3
3
  */
4
4
  import { addError, PikkuError } from './error-handler.js';
5
+ export class InvalidMiddlewareInteractionError extends PikkuError {
6
+ }
7
+ addError(InvalidMiddlewareInteractionError, {
8
+ status: 500,
9
+ message: 'The middleware interaction is invalid for the current wiring type.',
10
+ });
5
11
  /**
6
12
  * The server cannot or will not process the request due to client error (e.g., malformed request syntax).
7
13
  * @group Error
@@ -1,13 +1,19 @@
1
- import { CoreServices, CoreUserSession, CorePikkuMiddleware, PikkuWiringTypes } from '../types/core.types.js';
2
- import { CorePermissionGroup, CorePikkuFunction, CorePikkuFunctionConfig, CorePikkuFunctionSessionless } from './functions.types.js';
3
- export declare const addFunction: (funcName: string, funcConfig: CorePikkuFunctionConfig<any, any> | CorePikkuFunctionSessionless<any, any> | CorePikkuFunction<any, any>) => void;
1
+ import { CoreServices, CoreUserSession, CorePikkuMiddleware, PikkuWiringTypes, CoreSingletonServices, PikkuInteraction, MiddlewareMetadata, PermissionMetadata } from '../types/core.types.js';
2
+ import { CorePermissionGroup, CorePikkuFunctionConfig, CorePikkuPermission } from './functions.types.js';
3
+ import { UserSessionService } from '../services/user-session-service.js';
4
+ export declare const addFunction: (funcName: string, funcConfig: CorePikkuFunctionConfig<any, any>) => void;
4
5
  export declare const runPikkuFuncDirectly: <In, Out>(funcName: string, allServices: CoreServices, data: In, session?: CoreUserSession) => Promise<Out>;
5
- export declare const runPikkuFunc: <In = any, Out = any>(wireType: PikkuWiringTypes, wireId: string, funcName: string, { getAllServices, data, session, permissions: wiringPermissions, middleware: wiringMiddleware, coerceDataFromSchema, tags, }: {
6
- getAllServices: () => Promise<CoreServices> | CoreServices;
7
- data: In;
8
- session?: CoreUserSession;
9
- permissions?: CorePermissionGroup;
10
- middleware?: CorePikkuMiddleware[];
6
+ export declare const runPikkuFunc: <In = any, Out = any>(wireType: PikkuWiringTypes, wireId: string, funcName: string, { getAllServices, singletonServices, data, userSession, auth: wiringAuth, inheritedMiddleware, wireMiddleware, inheritedPermissions, wirePermissions, coerceDataFromSchema, tags, interaction, }: {
7
+ singletonServices: CoreSingletonServices;
8
+ getAllServices: (session?: CoreUserSession) => Promise<CoreServices> | CoreServices;
9
+ userSession?: UserSessionService<CoreUserSession>;
10
+ data: () => Promise<In> | In;
11
+ auth?: boolean;
12
+ inheritedMiddleware?: MiddlewareMetadata[];
13
+ wireMiddleware?: CorePikkuMiddleware[];
14
+ inheritedPermissions?: PermissionMetadata[];
15
+ wirePermissions?: CorePermissionGroup | CorePikkuPermission[];
11
16
  coerceDataFromSchema?: boolean;
12
17
  tags?: string[];
18
+ interaction: PikkuInteraction;
13
19
  }) => Promise<Out>;
@@ -1,12 +1,9 @@
1
- import { ForbiddenError } from '../errors/errors.js';
2
1
  import { runMiddleware, combineMiddleware } from '../middleware-runner.js';
3
2
  import { runPermissions } from '../permissions.js';
4
3
  import { pikkuState } from '../pikku-state.js';
5
4
  import { coerceTopLevelDataFromSchema, validateSchema } from '../schema.js';
5
+ import { ForbiddenError } from '../errors/errors.js';
6
6
  export const addFunction = (funcName, funcConfig) => {
7
- if (funcConfig instanceof Function) {
8
- funcConfig = { func: funcConfig };
9
- }
10
7
  pikkuState('function', 'functions').set(funcName, funcConfig);
11
8
  };
12
9
  export const runPikkuFuncDirectly = async (funcName, allServices, data, session) => {
@@ -16,7 +13,7 @@ export const runPikkuFuncDirectly = async (funcName, allServices, data, session)
16
13
  }
17
14
  return (await funcConfig.func(allServices, data, session));
18
15
  };
19
- export const runPikkuFunc = async (wireType, wireId, funcName, { getAllServices, data, session, permissions: wiringPermissions, middleware: wiringMiddleware, coerceDataFromSchema, tags = [], }) => {
16
+ export const runPikkuFunc = async (wireType, wireId, funcName, { getAllServices, singletonServices, data, userSession, auth: wiringAuth, inheritedMiddleware, wireMiddleware, inheritedPermissions, wirePermissions, coerceDataFromSchema, tags = [], interaction, }) => {
20
17
  const funcConfig = pikkuState('function', 'functions').get(funcName);
21
18
  if (!funcConfig) {
22
19
  throw new Error(`Function not found: ${funcName}`);
@@ -25,42 +22,63 @@ export const runPikkuFunc = async (wireType, wireId, funcName, { getAllServices,
25
22
  if (!funcMeta) {
26
23
  throw new Error(`Function meta not found: ${funcName}`);
27
24
  }
28
- if (funcConfig.auth && !session) {
29
- throw new ForbiddenError(`Function ${funcName} requires authentication even though transport does not`);
30
- }
31
- const allServices = await getAllServices();
32
- const inputSchemaName = funcMeta.inputSchemaName;
33
- if (inputSchemaName) {
34
- // Validate request data against the defined schema, if any
35
- await validateSchema(allServices.logger, allServices.schema, inputSchemaName, data);
36
- // Coerce (top level) query string parameters or date objects if specified by the schema
37
- if (coerceDataFromSchema) {
38
- coerceTopLevelDataFromSchema(inputSchemaName, data);
25
+ // Convert tags to PermissionMetadata and merge with inheritedPermissions
26
+ const mergedInheritedPermissions = [
27
+ ...(inheritedPermissions || []),
28
+ ...(tags?.map((tag) => ({ type: 'tag', tag })) || []),
29
+ ];
30
+ // Helper function to run permissions and execute the function
31
+ const executeFunction = async () => {
32
+ const session = userSession?.get();
33
+ if (wiringAuth === true || funcConfig.auth === true) {
34
+ // This means it was explicitly enabled in either wiring or function and has to be respected
35
+ if (!session) {
36
+ throw new ForbiddenError('Authentication required');
37
+ }
39
38
  }
40
- }
41
- // Run permission checks in the specified order
42
- await runPermissions(wireType, wireId, {
43
- wiringTags: tags,
44
- wiringPermissions,
45
- funcTags: funcConfig.tags,
46
- funcPermissions: funcConfig.permissions,
47
- allServices,
48
- data,
49
- session,
50
- });
51
- // Combine all middleware: wiring tags wiring middleware → func middleware → func tags
39
+ if (wiringAuth === undefined && funcConfig.auth === undefined) {
40
+ // We always default to requiring auth unless explicitly disabled
41
+ if (!session) {
42
+ throw new ForbiddenError('Authentication required');
43
+ }
44
+ }
45
+ // Evaluate the data from the lazy function
46
+ const actualData = await data();
47
+ // Validate and coerce data if schema is defined
48
+ const inputSchemaName = funcMeta.inputSchemaName;
49
+ if (inputSchemaName) {
50
+ // Validate request data against the defined schema, if any
51
+ await validateSchema(singletonServices.logger, singletonServices.schema, inputSchemaName, actualData);
52
+ // Coerce (top level) query string parameters or date objects if specified by the schema
53
+ if (coerceDataFromSchema) {
54
+ coerceTopLevelDataFromSchema(inputSchemaName, actualData);
55
+ }
56
+ }
57
+ const allServices = await getAllServices(session);
58
+ // Run permissions check with combined permissions: inheritedPermissions (including tags) → wirePermissions → funcPermissions
59
+ await runPermissions(wireType, wireId, {
60
+ wireInheritedPermissions: mergedInheritedPermissions,
61
+ wirePermissions: wirePermissions,
62
+ funcInheritedPermissions: funcMeta.permissions,
63
+ funcPermissions: funcConfig.permissions,
64
+ allServices,
65
+ data: actualData,
66
+ session,
67
+ });
68
+ return await funcConfig.func(allServices, actualData, session);
69
+ };
70
+ // Combine all middleware: inheritedMiddleware → wireMiddleware → funcMiddleware
52
71
  const allMiddleware = combineMiddleware(wireType, wireId, {
53
- wiringTags: tags,
54
- wiringMiddleware,
72
+ wireInheritedMiddleware: inheritedMiddleware,
73
+ wireMiddleware,
74
+ funcInheritedMiddleware: funcMeta.middleware,
55
75
  funcMiddleware: funcConfig.middleware,
56
- funcTags: funcConfig.tags,
57
76
  });
58
77
  if (allMiddleware.length > 0) {
59
- return (await runMiddleware(allServices, {
60
- http: allServices.http,
61
- mcp: allServices.mcp,
62
- rpc: allServices.rpc,
63
- }, allMiddleware, async () => await funcConfig.func(allServices, data, session)));
78
+ return (await runMiddleware({
79
+ ...singletonServices,
80
+ userSession,
81
+ }, interaction, allMiddleware, executeFunction));
64
82
  }
65
- return (await funcConfig.func(allServices, data, session));
83
+ return (await executeFunction());
66
84
  };
@@ -5,6 +5,7 @@ import type { CoreServices, CoreSingletonServices, CoreUserSession, PikkuDocs, C
5
5
  *
6
6
  * @template In - The input type.
7
7
  * @template Out - The output type.
8
+ * @template ChannelData - The channel data type.
8
9
  * @template Services - The services type, defaults to `CoreServices`.
9
10
  * @template Session - The session type, defaults to `CoreUserSession`.
10
11
  */
@@ -18,6 +19,7 @@ export type CorePikkuFunction<In, Out, ChannelData extends unknown | null = null
18
19
  *
19
20
  * @template In - The input type.
20
21
  * @template Out - The output type.
22
+ * @template ChannelData - The channel data type.
21
23
  * @template Services - The services type, defaults to `CoreServices`.
22
24
  * @template Session - The session type, defaults to `CoreUserSession`.
23
25
  */
@@ -34,10 +36,74 @@ export type CorePikkuFunctionSessionless<In, Out, ChannelData extends unknown |
34
36
  * @template Session - The session type, defaults to `CoreUserSession`.
35
37
  */
36
38
  export type CorePikkuPermission<In = any, Services extends CoreSingletonServices = CoreServices, Session extends CoreUserSession = CoreUserSession> = (services: Services, data: In, session?: Session) => Promise<boolean>;
39
+ /**
40
+ * Configuration object for creating a permission with metadata
41
+ *
42
+ * @template In - The input type.
43
+ * @template Services - The services type, defaults to `CoreServices`.
44
+ * @template Session - The session type, defaults to `CoreUserSession`.
45
+ */
46
+ export type CorePikkuPermissionConfig<In = any, Services extends CoreSingletonServices = CoreServices, Session extends CoreUserSession = CoreUserSession> = {
47
+ /** The permission function */
48
+ func: CorePikkuPermission<In, Services, Session>;
49
+ /** Optional human-readable name for the permission */
50
+ name?: string;
51
+ /** Optional description of what the permission checks */
52
+ description?: string;
53
+ };
37
54
  /**
38
55
  * Factory function for creating permissions with tree-shaking support
56
+ * Supports both direct function and configuration object syntax
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * // Direct function syntax
61
+ * export const adminPermission = pikkuPermission(
62
+ * async ({ logger }, data, session) => {
63
+ * return session?.role === 'admin'
64
+ * }
65
+ * )
66
+ *
67
+ * // Configuration object syntax with metadata
68
+ * export const adminPermission = pikkuPermission({
69
+ * name: 'Admin Permission',
70
+ * description: 'Checks if user has admin role',
71
+ * func: async ({ logger }, data, session) => {
72
+ * return session?.role === 'admin'
73
+ * }
74
+ * })
75
+ * ```
76
+ */
77
+ export declare const pikkuPermission: <In = any, Services extends CoreSingletonServices = CoreServices, Session extends CoreUserSession = CoreUserSession>(permission: CorePikkuPermission<In, Services, Session> | CorePikkuPermissionConfig<In, Services, Session>) => CorePikkuPermission<In, Services, Session>;
78
+ /**
79
+ * A factory function that takes input and returns a permission
80
+ * Used when permissions need configuration/input parameters
81
+ *
82
+ * @template In - The input type for the factory.
83
+ * @template Services - The services type, defaults to `CoreServices`.
84
+ * @template Session - The session type, defaults to `CoreUserSession`.
85
+ */
86
+ export type CorePikkuPermissionFactory<In = any, Services extends CoreSingletonServices = CoreServices, Session extends CoreUserSession = CoreUserSession> = (input: In) => CorePikkuPermission<any, Services, Session>;
87
+ /**
88
+ * Factory function for creating permission factories
89
+ * Use this when your permission needs configuration/input parameters
90
+ *
91
+ * @example
92
+ * ```typescript
93
+ * export const requireRole = pikkuPermissionFactory<{ role: string }>(({
94
+ * role
95
+ * }) => {
96
+ * return pikkuPermission(async ({ logger }, data, session) => {
97
+ * if (!session || session.role !== role) {
98
+ * logger.warn(`Permission denied: required role '${role}'`)
99
+ * return false
100
+ * }
101
+ * return true
102
+ * })
103
+ * })
104
+ * ```
39
105
  */
40
- export declare const pikkuPermission: <In = any, Services extends CoreSingletonServices = CoreServices, Session extends CoreUserSession = CoreUserSession>(permission: CorePikkuPermission<In, Services, Session>) => CorePikkuPermission<In, Services, Session>;
106
+ export declare const pikkuPermissionFactory: <In = any>(factory: CorePikkuPermissionFactory<In>) => CorePikkuPermissionFactory<In>;
41
107
  export type CorePermissionGroup<PikkuPermission = CorePikkuPermission<any>> = Record<string, PikkuPermission | PikkuPermission[]> | undefined;
42
108
  export type CorePikkuFunctionConfig<PikkuFunction extends CorePikkuFunction<any, any, any, any, any> | CorePikkuFunctionSessionless<any, any, any, any, any>, PikkuPermission extends CorePikkuPermission<any, any, any> = CorePikkuPermission<any>, PikkuMiddleware extends CorePikkuMiddleware<any> = CorePikkuMiddleware<any>> = {
43
109
  name?: string;
@@ -1,6 +1,48 @@
1
1
  /**
2
2
  * Factory function for creating permissions with tree-shaking support
3
+ * Supports both direct function and configuration object syntax
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * // Direct function syntax
8
+ * export const adminPermission = pikkuPermission(
9
+ * async ({ logger }, data, session) => {
10
+ * return session?.role === 'admin'
11
+ * }
12
+ * )
13
+ *
14
+ * // Configuration object syntax with metadata
15
+ * export const adminPermission = pikkuPermission({
16
+ * name: 'Admin Permission',
17
+ * description: 'Checks if user has admin role',
18
+ * func: async ({ logger }, data, session) => {
19
+ * return session?.role === 'admin'
20
+ * }
21
+ * })
22
+ * ```
3
23
  */
4
24
  export const pikkuPermission = (permission) => {
5
- return permission;
25
+ return typeof permission === 'function' ? permission : permission.func;
26
+ };
27
+ /**
28
+ * Factory function for creating permission factories
29
+ * Use this when your permission needs configuration/input parameters
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * export const requireRole = pikkuPermissionFactory<{ role: string }>(({
34
+ * role
35
+ * }) => {
36
+ * return pikkuPermission(async ({ logger }, data, session) => {
37
+ * if (!session || session.role !== role) {
38
+ * logger.warn(`Permission denied: required role '${role}'`)
39
+ * return false
40
+ * }
41
+ * return true
42
+ * })
43
+ * })
44
+ * ```
45
+ */
46
+ export const pikkuPermissionFactory = (factory) => {
47
+ return factory;
6
48
  };
package/dist/index.d.ts CHANGED
@@ -16,15 +16,11 @@ export * from './wirings/scheduler/index.js';
16
16
  export * from './wirings/rpc/index.js';
17
17
  export * from './wirings/queue/index.js';
18
18
  export * from './wirings/mcp/index.js';
19
+ export * from './wirings/cli/index.js';
19
20
  export * from './errors/index.js';
20
21
  export * from './middleware/index.js';
21
22
  export * from './utils.js';
22
23
  export * from './time-utils.js';
23
24
  export { pikkuState } from './pikku-state.js';
24
- export { addMiddleware, runMiddleware } from './middleware-runner.js';
25
+ export { runMiddleware, addMiddleware, getMiddlewareByName, } from './middleware-runner.js';
25
26
  export { addPermission } from './permissions.js';
26
- export { wireHTTP, addHTTPMiddleware } from './wirings/http/http-runner.js';
27
- export { wireChannel } from './wirings/channel/channel-runner.js';
28
- export { wireScheduler } from './wirings/scheduler/scheduler-runner.js';
29
- export { wireQueueWorker } from './wirings/queue/queue-runner.js';
30
- export { wireMCPResource, wireMCPTool, wireMCPPrompt, runMCPResource, runMCPTool, runMCPPrompt, getMCPTools, getMCPResources, getMCPPrompts, getMCPResourcesMeta, getMCPToolsMeta, getMCPPromptsMeta, } from './wirings/mcp/mcp-runner.js';
package/dist/index.js CHANGED
@@ -16,15 +16,11 @@ export * from './wirings/scheduler/index.js';
16
16
  export * from './wirings/rpc/index.js';
17
17
  export * from './wirings/queue/index.js';
18
18
  export * from './wirings/mcp/index.js';
19
+ export * from './wirings/cli/index.js';
19
20
  export * from './errors/index.js';
20
21
  export * from './middleware/index.js';
21
22
  export * from './utils.js';
22
23
  export * from './time-utils.js';
23
24
  export { pikkuState } from './pikku-state.js';
24
- export { addMiddleware, runMiddleware } from './middleware-runner.js';
25
+ export { runMiddleware, addMiddleware, getMiddlewareByName, } from './middleware-runner.js';
25
26
  export { addPermission } from './permissions.js';
26
- export { wireHTTP, addHTTPMiddleware } from './wirings/http/http-runner.js';
27
- export { wireChannel } from './wirings/channel/channel-runner.js';
28
- export { wireScheduler } from './wirings/scheduler/scheduler-runner.js';
29
- export { wireQueueWorker } from './wirings/queue/queue-runner.js';
30
- export { wireMCPResource, wireMCPTool, wireMCPPrompt, runMCPResource, runMCPTool, runMCPPrompt, getMCPTools, getMCPResources, getMCPPrompts, getMCPResourcesMeta, getMCPToolsMeta, getMCPPromptsMeta, } from './wirings/mcp/mcp-runner.js';
@@ -1,15 +1,23 @@
1
- import { CoreConfig, CoreSingletonServices, CoreUserSession, CorePikkuMiddleware } from '../types/core.types.js';
2
1
  /**
3
- * API key middleware that retrieves a session from the 'x-api-key' header using a provided callback.
2
+ * API key middleware that retrieves a session from the 'x-api-key' header using JWT decoding.
4
3
  *
5
- * @param options.getSessionForAPIKey - A function that returns a session when provided an API key.
4
+ * Extracts API key from either the 'x-api-key' header or 'apiKey' query parameter
5
+ * and decodes it using the JWT service.
6
+ *
7
+ * @param options.source - Where to look for the API key: 'header', 'query', or 'all'
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { authAPIKey } from '@pikku/core/middleware'
12
+ *
13
+ * addHTTPMiddleware([
14
+ * authAPIKey({ source: 'header' })
15
+ * ])
16
+ * ```
6
17
  */
7
- export declare const authAPIKey: <SingletonServices extends CoreSingletonServices<CoreConfig>, UserSession extends CoreUserSession>({ source, getSessionForAPIKey, jwt, }: {
18
+ export declare const authAPIKey: import("../types/core.types.js").CorePikkuMiddlewareFactory<{
8
19
  source: "header" | "query" | "all";
9
- } & ({
10
- getSessionForAPIKey?: undefined;
11
- jwt?: true;
12
- } | {
13
- getSessionForAPIKey: (services: SingletonServices, apiKey: string) => Promise<any>;
14
- jwt?: false;
15
- })) => CorePikkuMiddleware;
20
+ }, import("../types/core.types.js").CoreSingletonServices<{
21
+ logLevel?: import("../index.js").LogLevel;
22
+ secrets?: {};
23
+ }>, import("../types/core.types.js").CoreUserSession>;
@@ -1,37 +1,37 @@
1
+ import { pikkuMiddleware, pikkuMiddlewareFactory } from '../types/core.types.js';
1
2
  /**
2
- * API key middleware that retrieves a session from the 'x-api-key' header using a provided callback.
3
+ * API key middleware that retrieves a session from the 'x-api-key' header using JWT decoding.
3
4
  *
4
- * @param options.getSessionForAPIKey - A function that returns a session when provided an API key.
5
+ * Extracts API key from either the 'x-api-key' header or 'apiKey' query parameter
6
+ * and decodes it using the JWT service.
7
+ *
8
+ * @param options.source - Where to look for the API key: 'header', 'query', or 'all'
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { authAPIKey } from '@pikku/core/middleware'
13
+ *
14
+ * addHTTPMiddleware([
15
+ * authAPIKey({ source: 'header' })
16
+ * ])
17
+ * ```
5
18
  */
6
- export const authAPIKey = ({ source, getSessionForAPIKey, jwt, }) => {
7
- const middleware = async (services, { http }, next) => {
8
- const { userSession: userSessionService, jwt: jwtService } = services;
9
- if (!http?.request || userSessionService.get()) {
10
- return next();
11
- }
12
- let apiKey = null;
13
- if (source === 'header' || source === 'all') {
14
- apiKey = http.request.header('x-api-key');
15
- }
16
- if (!apiKey && (source === 'query' || source === 'all')) {
17
- apiKey = http.request.query().apiKey;
18
- }
19
- if (apiKey) {
20
- let userSession = null;
21
- if (jwt) {
22
- if (!jwtService) {
23
- throw new Error('JWT service is required for JWT decoding.');
24
- }
25
- userSession = await jwtService.decode(apiKey);
26
- }
27
- else {
28
- userSession = await getSessionForAPIKey(services, apiKey);
29
- }
30
- if (userSession) {
31
- userSessionService.setInitial(userSession);
32
- }
33
- }
19
+ export const authAPIKey = pikkuMiddlewareFactory(({ source }) => pikkuMiddleware(async ({ userSession: userSessionService, jwt: jwtService }, { http }, next) => {
20
+ if (!http?.request || userSessionService.get()) {
34
21
  return next();
35
- };
36
- return middleware;
37
- };
22
+ }
23
+ let apiKey = null;
24
+ if (source === 'header' || source === 'all') {
25
+ apiKey = http.request.header('x-api-key');
26
+ }
27
+ if (!apiKey && (source === 'query' || source === 'all')) {
28
+ apiKey = http.request.query().apiKey;
29
+ }
30
+ if (apiKey && jwtService) {
31
+ const userSession = await jwtService.decode(apiKey);
32
+ if (userSession) {
33
+ userSessionService.setInitial(userSession);
34
+ }
35
+ }
36
+ return next();
37
+ }));
@@ -1,12 +1,39 @@
1
- import { CoreConfig, CoreSingletonServices, CoreUserSession, CorePikkuMiddleware } from '../types/core.types.js';
1
+ import { CoreUserSession } from '../types/core.types.js';
2
2
  /**
3
- * Extracts the Bearer token from the Authorization header
3
+ * Bearer token middleware that extracts and validates tokens from the Authorization header.
4
+ *
5
+ * Supports two modes:
6
+ * - JWT decoding (default): Decodes bearer tokens using the JWT service
7
+ * - Static token: Provide a `token` object with a value and userSession for simple token validation
8
+ *
9
+ * @param options.token - Optional static token configuration { value: string, userSession: CoreUserSession }
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { authBearer } from '@pikku/core/middleware'
14
+ *
15
+ * // JWT mode (default)
16
+ * addHTTPMiddleware('*', [
17
+ * authBearer()
18
+ * ])
19
+ *
20
+ * // Static token mode
21
+ * addHTTPMiddleware('*', [
22
+ * authBearer({
23
+ * token: {
24
+ * value: process.env.API_TOKEN,
25
+ * userSession: { userId: 'system', role: 'admin' }
26
+ * }
27
+ * })
28
+ * ])
29
+ * ```
4
30
  */
5
- export declare const authBearer: <SingletonServices extends CoreSingletonServices<CoreConfig>, UserSession extends CoreUserSession>({ token, jwt, getSession, }?: {
31
+ export declare const authBearer: import("../types/core.types.js").CorePikkuMiddlewareFactory<{
6
32
  token?: {
7
33
  value: string;
8
- userSession: UserSession;
34
+ userSession: CoreUserSession;
9
35
  };
10
- jwt?: boolean;
11
- getSession?: (services: SingletonServices, token: string) => Promise<UserSession> | UserSession;
12
- }) => CorePikkuMiddleware;
36
+ }, import("../types/core.types.js").CoreSingletonServices<{
37
+ logLevel?: import("../index.js").LogLevel;
38
+ secrets?: {};
39
+ }>, CoreUserSession>;
@@ -1,41 +1,58 @@
1
1
  import { InvalidSessionError } from '../errors/errors.js';
2
+ import { pikkuMiddleware, pikkuMiddlewareFactory, } from '../types/core.types.js';
2
3
  /**
3
- * Extracts the Bearer token from the Authorization header
4
+ * Bearer token middleware that extracts and validates tokens from the Authorization header.
5
+ *
6
+ * Supports two modes:
7
+ * - JWT decoding (default): Decodes bearer tokens using the JWT service
8
+ * - Static token: Provide a `token` object with a value and userSession for simple token validation
9
+ *
10
+ * @param options.token - Optional static token configuration { value: string, userSession: CoreUserSession }
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import { authBearer } from '@pikku/core/middleware'
15
+ *
16
+ * // JWT mode (default)
17
+ * addHTTPMiddleware('*', [
18
+ * authBearer()
19
+ * ])
20
+ *
21
+ * // Static token mode
22
+ * addHTTPMiddleware('*', [
23
+ * authBearer({
24
+ * token: {
25
+ * value: process.env.API_TOKEN,
26
+ * userSession: { userId: 'system', role: 'admin' }
27
+ * }
28
+ * })
29
+ * ])
30
+ * ```
4
31
  */
5
- export const authBearer = ({ token, jwt, getSession, } = {}) => {
6
- const middleware = async (services, { http }, next) => {
7
- const { userSession: userSessionService, jwt: jwtService } = services;
8
- // Skip if session already exists.
9
- if (!http?.request || userSessionService.get()) {
10
- return next();
32
+ export const authBearer = pikkuMiddlewareFactory(({ token } = {}) => pikkuMiddleware(async ({ userSession: userSessionService, jwt: jwtService }, { http }, next) => {
33
+ // Skip if session already exists.
34
+ if (!http?.request || userSessionService.get()) {
35
+ return next();
36
+ }
37
+ const authHeader = http.request.header('authorization') ||
38
+ http.request.header('Authorization');
39
+ if (authHeader) {
40
+ const [scheme, bearerToken] = authHeader.split(' ');
41
+ if (scheme !== 'Bearer' || !bearerToken) {
42
+ throw new InvalidSessionError();
11
43
  }
12
- const authHeader = http.request.header('authorization') ||
13
- http.request.header('Authorization');
14
- if (authHeader) {
15
- const [scheme, bearerToken] = authHeader.split(' ');
16
- if (scheme !== 'Bearer' || !token || !bearerToken) {
17
- throw new InvalidSessionError();
18
- }
19
- let userSession = null;
20
- if (jwt) {
21
- if (!jwtService) {
22
- throw new Error('JWT service is required for JWT decoding.');
23
- }
24
- userSession = await jwtService.decode(bearerToken);
25
- }
26
- else if (token) {
27
- if (bearerToken === token.value) {
28
- userSession = token.userSession;
29
- }
30
- }
31
- else if (getSession) {
32
- userSession = await getSession(services, token);
33
- }
34
- if (userSession) {
35
- userSessionService.setInitial(userSession);
36
- }
44
+ let userSession = null;
45
+ // If static token provided, validate against it
46
+ if (token && bearerToken === token.value) {
47
+ userSession = token.userSession;
37
48
  }
38
- return next();
39
- };
40
- return middleware;
41
- };
49
+ // Otherwise, default to JWT decoding
50
+ else if (jwtService && !token) {
51
+ userSession = await jwtService.decode(bearerToken);
52
+ }
53
+ if (userSession) {
54
+ userSessionService.setInitial(userSession);
55
+ }
56
+ }
57
+ return next();
58
+ }));