@pikku/core 0.9.12-next.0 → 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 (83) hide show
  1. package/CHANGELOG.md +8 -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 +5 -4
  5. package/dist/function/function-runner.js +10 -4
  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 +1 -7
  9. package/dist/index.js +1 -7
  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 +0 -18
  19. package/dist/middleware-runner.js +0 -21
  20. package/dist/permissions.d.ts +82 -20
  21. package/dist/permissions.js +175 -62
  22. package/dist/pikku-state.d.ts +22 -2
  23. package/dist/pikku-state.js +7 -1
  24. package/dist/types/core.types.d.ts +58 -1
  25. package/dist/types/core.types.js +23 -1
  26. package/dist/utils.d.ts +1 -1
  27. package/dist/wirings/channel/channel-handler.js +6 -5
  28. package/dist/wirings/channel/channel-runner.d.ts +3 -1
  29. package/dist/wirings/channel/channel-runner.js +4 -21
  30. package/dist/wirings/channel/channel.types.d.ts +12 -13
  31. package/dist/wirings/cli/channel/cli-channel-runner.js +5 -2
  32. package/dist/wirings/cli/cli-runner.d.ts +10 -0
  33. package/dist/wirings/cli/cli-runner.js +44 -20
  34. package/dist/wirings/cli/cli.types.d.ts +54 -27
  35. package/dist/wirings/cli/command-parser.js +34 -4
  36. package/dist/wirings/cli/index.d.ts +1 -1
  37. package/dist/wirings/cli/index.js +1 -1
  38. package/dist/wirings/http/http-runner.d.ts +31 -1
  39. package/dist/wirings/http/http-runner.js +36 -1
  40. package/dist/wirings/http/http.types.d.ts +2 -1
  41. package/dist/wirings/http/index.d.ts +1 -1
  42. package/dist/wirings/http/index.js +1 -1
  43. package/dist/wirings/mcp/mcp-endpoint-registry.d.ts +1 -1
  44. package/dist/wirings/mcp/mcp-runner.js +2 -0
  45. package/dist/wirings/mcp/mcp.types.d.ts +35 -14
  46. package/dist/wirings/scheduler/scheduler-runner.js +1 -0
  47. package/dist/wirings/scheduler/scheduler.types.d.ts +2 -2
  48. package/package.json +6 -5
  49. package/src/errors/errors.ts +6 -0
  50. package/src/factory-functions.test.ts +60 -1
  51. package/src/function/function-runner.test.ts +8 -3
  52. package/src/function/function-runner.ts +18 -5
  53. package/src/function/functions.types.ts +85 -2
  54. package/src/index.ts +1 -21
  55. package/src/middleware/auth-apikey.ts +42 -57
  56. package/src/middleware/auth-bearer.ts +66 -49
  57. package/src/middleware/auth-cookie.ts +63 -82
  58. package/src/middleware/timeout.ts +10 -14
  59. package/src/middleware-runner.ts +0 -25
  60. package/src/permissions.test.ts +26 -27
  61. package/src/permissions.ts +215 -104
  62. package/src/pikku-state.ts +35 -3
  63. package/src/types/core.types.ts +70 -3
  64. package/src/utils.ts +1 -1
  65. package/src/wirings/channel/channel-handler.ts +11 -10
  66. package/src/wirings/channel/channel-runner.ts +18 -27
  67. package/src/wirings/channel/channel.types.ts +24 -15
  68. package/src/wirings/cli/channel/cli-channel-runner.ts +8 -3
  69. package/src/wirings/cli/cli-runner.test.ts +80 -59
  70. package/src/wirings/cli/cli-runner.ts +57 -21
  71. package/src/wirings/cli/cli.types.ts +74 -44
  72. package/src/wirings/cli/command-parser.test.ts +91 -86
  73. package/src/wirings/cli/command-parser.ts +38 -4
  74. package/src/wirings/cli/index.ts +1 -0
  75. package/src/wirings/http/http-runner.ts +41 -1
  76. package/src/wirings/http/http.types.ts +3 -1
  77. package/src/wirings/http/index.ts +7 -1
  78. package/src/wirings/mcp/mcp-endpoint-registry.ts +1 -1
  79. package/src/wirings/mcp/mcp-runner.ts +2 -0
  80. package/src/wirings/mcp/mcp.types.ts +48 -12
  81. package/src/wirings/scheduler/scheduler-runner.ts +1 -0
  82. package/src/wirings/scheduler/scheduler.types.ts +2 -2
  83. package/tsconfig.tsbuildinfo +1 -1
@@ -1,4 +1,4 @@
1
- import { CorePikkuMiddleware, CoreSingletonServices, CoreUserSession, PikkuDocs, CoreServices, MiddlewareMetadata } from '../../types/core.types.js';
1
+ import { CorePikkuMiddleware, CoreSingletonServices, CoreUserSession, PikkuDocs, CoreServices, MiddlewareMetadata, PermissionMetadata } from '../../types/core.types.js';
2
2
  import { CorePikkuFunctionConfig, CorePikkuPermission, CorePikkuFunction, CorePikkuFunctionSessionless } from '../../function/functions.types.js';
3
3
  import { PikkuChannel } from '../channel/channel.types.js';
4
4
  /**
@@ -60,6 +60,8 @@ export interface CLICommandMeta {
60
60
  tags?: string[];
61
61
  subcommands?: Record<string, CLICommandMeta>;
62
62
  middleware?: MiddlewareMetadata[];
63
+ permissions?: PermissionMetadata[];
64
+ isDefault?: boolean;
63
65
  }
64
66
  /**
65
67
  * CLI metadata collection (per program)
@@ -70,10 +72,26 @@ export type CLIProgramMeta = {
70
72
  options: Record<string, CLIOption>;
71
73
  defaultRenderName?: string;
72
74
  };
75
+ /**
76
+ * Renderer metadata for CLI commands
77
+ */
78
+ export interface RendererMeta {
79
+ name: string;
80
+ exportedName?: string;
81
+ services: {
82
+ optimized: boolean;
83
+ services: string[];
84
+ };
85
+ filePath: string;
86
+ }
87
+ export type RenderersMeta = Record<string, RendererMeta>;
73
88
  /**
74
89
  * All CLI programs metadata
75
90
  */
76
- export type CLIMeta = Record<string, CLIProgramMeta>;
91
+ export interface CLIMeta {
92
+ programs: Record<string, CLIProgramMeta>;
93
+ renderers: RenderersMeta;
94
+ }
77
95
  /**
78
96
  * CLI-specific renderer that outputs to console
79
97
  */
@@ -81,7 +99,7 @@ export type CorePikkuCLIRender<Data, Services extends CoreSingletonServices = Co
81
99
  /**
82
100
  * Extract input parameters from a Pikku function config type
83
101
  */
84
- export type ExtractFunctionInput<Func> = Func extends CorePikkuFunctionConfig<infer FuncType> ? FuncType extends CorePikkuFunction<infer Input, any, any, any, any> | CorePikkuFunctionSessionless<infer Input, any, any, any, any> ? Input : never : never;
102
+ export type ExtractFunctionInput<Func> = Func extends CorePikkuFunctionConfig<infer FuncType, any, any> ? FuncType extends CorePikkuFunction<infer Input, any, any, any, any> | CorePikkuFunctionSessionless<infer Input, any, any, any, any> ? Input : never : never;
85
103
  /**
86
104
  * Strip < > [ ] characters from a string
87
105
  */
@@ -91,49 +109,57 @@ type StripBrackets<S extends string> = S extends `<${infer Inner}>` ? Inner : S
91
109
  */
92
110
  type SplitBySpace<S extends string> = S extends `${infer First} ${infer Rest}` ? [First, ...SplitBySpace<Rest>] : S extends '' ? [] : [S];
93
111
  /**
94
- * Extract parameter names from CLI parameter string
112
+ * Recursively build a tuple by stripping brackets from each element
113
+ */
114
+ type BuildParamsTuple<Parts extends readonly string[]> = Parts extends readonly [
115
+ infer First extends string,
116
+ ...infer Rest extends string[]
117
+ ] ? [StripBrackets<First>, ...BuildParamsTuple<Rest>] : [];
118
+ /**
119
+ * Extract parameter names from CLI parameter string into a tuple
95
120
  * Example: "<env> [region]" => ["env", "region"]
96
121
  */
97
- type ExtractParameterNames<S extends string> = {
98
- [K in keyof SplitBySpace<S>]: SplitBySpace<S>[K] extends string ? StripBrackets<SplitBySpace<S>[K]> : never;
99
- };
122
+ type ExtractParameterNames<S extends string> = BuildParamsTuple<SplitBySpace<S>>;
123
+ /**
124
+ * Check if all elements of a tuple are valid keys of Input
125
+ */
126
+ type AllParamsValid<Params extends readonly any[], Input> = Params extends readonly [infer First, ...infer Rest] ? First extends keyof Input ? AllParamsValid<Rest, Input> : false : true;
100
127
  /**
101
128
  * Validate that all parameter names are valid keys of the function input
102
129
  */
103
- export type ValidateParameters<Params extends string, Input> = ExtractParameterNames<Params> extends (infer P)[] ? P extends keyof Input ? Params : never : never;
130
+ export type ValidateParameters<Params extends string, Input> = AllParamsValid<ExtractParameterNames<Params>, Input> extends true ? Params : never;
104
131
  /**
105
132
  * Extract output type from a Pikku function config type
106
133
  */
107
- export type ExtractFunctionOutput<Func> = Func extends CorePikkuFunctionConfig<infer FuncType> ? FuncType extends CorePikkuFunction<any, infer Output, any, any, any> | CorePikkuFunctionSessionless<any, infer Output, any, any, any> ? Output : never : never;
134
+ export type ExtractFunctionOutput<Func> = Func extends CorePikkuFunctionConfig<infer FuncType, any, any> ? FuncType extends CorePikkuFunction<any, infer Output, any, any, any> | CorePikkuFunctionSessionless<any, infer Output, any, any, any> ? Output : never : never;
108
135
  /**
109
136
  * CLI command configuration that infers options from function input type.
110
137
  * This is a helper type for creating type-safe CLI commands.
111
138
  */
112
- export interface CoreCLICommandConfig<Func, PikkuMiddleware extends CorePikkuMiddleware<any> = CorePikkuMiddleware<any>, PikkuCLIRender extends CorePikkuCLIRender<any, any, any> = CorePikkuCLIRender<any, any>> {
113
- parameters?: ValidateParameters<string, ExtractFunctionInput<Func>>;
114
- func?: Func;
139
+ export type CoreCLICommandConfig<FuncConfig, PikkuMiddleware extends CorePikkuMiddleware<any> = CorePikkuMiddleware<any>, PikkuCLIRender extends CorePikkuCLIRender<any, any, any> = CorePikkuCLIRender<any, any>, Params extends string = string> = {
140
+ parameters?: ValidateParameters<Params, ExtractFunctionInput<FuncConfig>>;
141
+ func?: FuncConfig;
115
142
  description?: string;
116
143
  render?: PikkuCLIRender;
117
- options?: Partial<Record<keyof ExtractFunctionInput<Func>, {
118
- description?: string;
119
- short?: string;
120
- default?: ExtractFunctionInput<Func>[keyof ExtractFunctionInput<Func>];
121
- }>> & Record<string, {
122
- description?: string;
123
- short?: string;
124
- default?: any;
125
- }>;
144
+ options?: {
145
+ [K in keyof ExtractFunctionInput<FuncConfig>]?: {
146
+ description?: string;
147
+ short?: string;
148
+ default?: ExtractFunctionInput<FuncConfig>[K];
149
+ };
150
+ };
126
151
  middleware?: PikkuMiddleware[];
127
- subcommands?: Record<string, CoreCLICommandConfig<any, PikkuMiddleware, PikkuCLIRender>>;
152
+ subcommands?: Record<string, CoreCLICommandConfig<any, PikkuMiddleware, PikkuCLIRender, any>>;
128
153
  auth?: boolean;
129
154
  permissions?: any[];
130
- }
155
+ isDefault?: boolean;
156
+ };
131
157
  /**
132
158
  * CLI command definition
133
159
  */
134
- export interface CoreCLICommand<In, Out, PikkuFunction extends CorePikkuFunctionConfig<CorePikkuFunction<In, Out, any, any, any> | CorePikkuFunctionSessionless<In, Out, any, any, any>>, PikkuPermission extends CorePikkuPermission<any, any, any>, PikkuMiddleware extends CorePikkuMiddleware, Options = any, Subcommands extends Record<string, CoreCLICommandConfig<any, any, any>> = Record<string, CoreCLICommandConfig<any, any, any>>> {
160
+ export interface CoreCLICommand<In, Out, PikkuFunctionConfig extends CorePikkuFunctionConfig<CorePikkuFunction<In, Out, any, any, any> | CorePikkuFunctionSessionless<In, Out, any, any, any>>, PikkuPermission extends CorePikkuPermission<any, any, any>, PikkuMiddleware extends CorePikkuMiddleware, Options = any, Subcommands extends Record<string, CoreCLICommandConfig<any, any, any>> = Record<string, CoreCLICommandConfig<any, any, any>>> {
135
161
  parameters?: string;
136
- func: PikkuFunction;
162
+ func: PikkuFunctionConfig;
137
163
  render?: CorePikkuCLIRender<Out>;
138
164
  description?: string;
139
165
  options?: CLIOptions<Options>;
@@ -142,15 +168,16 @@ export interface CoreCLICommand<In, Out, PikkuFunction extends CorePikkuFunction
142
168
  auth?: boolean;
143
169
  docs?: PikkuDocs;
144
170
  subcommands?: Subcommands;
171
+ isDefault?: boolean;
145
172
  }
146
173
  /**
147
174
  * Shorthand command definition (just a function)
148
175
  */
149
- export type CLICommandShorthand<In, Out, PikkuFunction extends CorePikkuFunctionConfig<CorePikkuFunction<In, Out, any, any, any> | CorePikkuFunctionSessionless<In, Out, any, any, any>>> = PikkuFunction;
176
+ export type CLICommandShorthand<In, Out, PikkuFunctionConfig extends CorePikkuFunctionConfig<CorePikkuFunction<In, Out, any, any, any> | CorePikkuFunctionSessionless<In, Out, any, any, any>>> = PikkuFunctionConfig;
150
177
  /**
151
178
  * Command definition (either full or shorthand)
152
179
  */
153
- export type CLICommandDefinition<In, Out, PikkuFunction extends CorePikkuFunctionConfig<CorePikkuFunction<In, Out, any, any, any> | CorePikkuFunctionSessionless<In, Out, any, any, any>>, PikkuPermission extends CorePikkuPermission<any, any, any>, PikkuMiddleware extends CorePikkuMiddleware, Options = any, Subcommands extends Record<string, CoreCLICommandConfig<any, any, any>> = Record<string, CoreCLICommandConfig<any, any, any>>> = CoreCLICommand<In, Out, PikkuFunction, PikkuPermission, PikkuMiddleware, Options, Subcommands> | CLICommandShorthand<In, Out, PikkuFunction>;
180
+ export type CLICommandDefinition<In, Out, PikkuFunctionConfig extends CorePikkuFunctionConfig<CorePikkuFunction<In, Out, any, any, any> | CorePikkuFunctionSessionless<In, Out, any, any, any>>, PikkuPermission extends CorePikkuPermission<any, any, any>, PikkuMiddleware extends CorePikkuMiddleware, Options = any, Subcommands extends Record<string, CoreCLICommandConfig<any, any, any>> = Record<string, CoreCLICommandConfig<any, any, any>>> = CoreCLICommand<In, Out, PikkuFunctionConfig, PikkuPermission, PikkuMiddleware, Options, Subcommands> | CLICommandShorthand<In, Out, PikkuFunctionConfig>;
154
181
  /**
155
182
  * CLI wiring configuration
156
183
  */
@@ -9,7 +9,7 @@ export function parseCLIArguments(args, programName, allMeta) {
9
9
  options: {},
10
10
  errors: [],
11
11
  };
12
- const meta = allMeta[programName];
12
+ const meta = allMeta.programs[programName];
13
13
  if (!meta) {
14
14
  result.errors.push(`Program not found: ${programName}`);
15
15
  return result;
@@ -38,10 +38,39 @@ export function parseCLIArguments(args, programName, allMeta) {
38
38
  break;
39
39
  }
40
40
  }
41
+ // If no command was parsed, check for a default command
42
+ // Only use default if user didn't provide any non-flag arguments
43
+ const hasNonFlagArgs = args.some((arg, idx) => idx >= currentIndex && !arg.startsWith('-'));
44
+ if (result.commandPath.length === 0 && meta.commands && !hasNonFlagArgs) {
45
+ for (const [name, cmd] of Object.entries(meta.commands)) {
46
+ if (cmd.isDefault) {
47
+ result.commandPath.push(name);
48
+ currentMeta = {
49
+ program: currentMeta.program,
50
+ commands: cmd.subcommands || {},
51
+ options: {
52
+ ...currentMeta.options,
53
+ ...cmd.options,
54
+ },
55
+ defaultRenderName: cmd.renderName || currentMeta.defaultRenderName,
56
+ };
57
+ break;
58
+ }
59
+ }
60
+ }
41
61
  // Get the final command metadata
42
62
  const commandMeta = getCommandMeta(meta, result.commandPath);
43
63
  if (!commandMeta) {
44
- result.errors.push(`Unknown command: ${result.commandPath.join(' ')}`);
64
+ // If we have no command path but there are non-flag args, the first arg is likely an unknown command
65
+ if (result.commandPath.length === 0 &&
66
+ hasNonFlagArgs &&
67
+ args.length > 0 &&
68
+ !args[0].startsWith('-')) {
69
+ result.errors.push(`Unknown command: ${args[0]}`);
70
+ }
71
+ else {
72
+ result.errors.push(`Unknown command: ${result.commandPath.join(' ')}`);
73
+ }
45
74
  return result;
46
75
  }
47
76
  // Collect all available options (global + inherited)
@@ -279,7 +308,7 @@ function applyOptionDefaults(optionDefs, options, result) {
279
308
  */
280
309
  export function generateCommandHelp(programName, allMeta, commandPath = []) {
281
310
  const lines = [];
282
- const meta = allMeta[programName];
311
+ const meta = allMeta.programs[programName];
283
312
  if (!meta) {
284
313
  return `Program not found: ${programName}`;
285
314
  }
@@ -290,7 +319,8 @@ export function generateCommandHelp(programName, allMeta, commandPath = []) {
290
319
  lines.push('Commands:');
291
320
  for (const [name, cmd] of Object.entries(meta.commands)) {
292
321
  const desc = cmd.description || '';
293
- lines.push(` ${name.padEnd(20)} ${desc}`);
322
+ const defaultMarker = cmd.isDefault ? ' (default)' : '';
323
+ lines.push(` ${name.padEnd(20)} ${desc}${defaultMarker}`);
294
324
  }
295
325
  if (Object.keys(meta.options).length > 0) {
296
326
  lines.push('');
@@ -1,5 +1,5 @@
1
1
  export * from './cli.types.js';
2
2
  export * from './cli-runner.js';
3
3
  export * from './command-parser.js';
4
- export { wireCLI, runCLICommand, pikkuCLIRender, executeCLI, } from './cli-runner.js';
4
+ export { wireCLI, runCLICommand, pikkuCLIRender, executeCLI, CLIError, } from './cli-runner.js';
5
5
  export { parseCLIArguments, generateCommandHelp } from './command-parser.js';
@@ -1,5 +1,5 @@
1
1
  export * from './cli.types.js';
2
2
  export * from './cli-runner.js';
3
3
  export * from './command-parser.js';
4
- export { wireCLI, runCLICommand, pikkuCLIRender, executeCLI, } from './cli-runner.js';
4
+ export { wireCLI, runCLICommand, pikkuCLIRender, executeCLI, CLIError, } from './cli-runner.js';
5
5
  export { parseCLIArguments, generateCommandHelp } from './command-parser.js';
@@ -1,5 +1,5 @@
1
1
  import { CoreHTTPFunctionWiring, RunHTTPWiringOptions, RunHTTPWiringParams, PikkuHTTP, PikkuHTTPRequest, PikkuHTTPResponse } from './http.types.js';
2
- import { CorePikkuFunction, CorePikkuFunctionSessionless, CorePikkuPermission } from '../../function/functions.types.js';
2
+ import { CorePikkuFunction, CorePikkuFunctionSessionless, CorePikkuPermission, CorePermissionGroup } from '../../function/functions.types.js';
3
3
  import { CorePikkuMiddleware, CorePikkuMiddlewareGroup } from '../../types/core.types.js';
4
4
  import { PikkuFetchHTTPResponse } from './pikku-fetch-http-response.js';
5
5
  /**
@@ -32,6 +32,36 @@ import { PikkuFetchHTTPResponse } from './pikku-fetch-http-response.js';
32
32
  * ```
33
33
  */
34
34
  export declare const addHTTPMiddleware: <PikkuMiddleware extends CorePikkuMiddleware>(pattern: string, middleware: CorePikkuMiddlewareGroup) => CorePikkuMiddlewareGroup;
35
+ /**
36
+ * Registers HTTP permissions for a specific route pattern.
37
+ *
38
+ * This function registers permissions at runtime that will be applied to
39
+ * HTTP routes matching the specified pattern.
40
+ *
41
+ * For tree-shaking benefits, wrap in a factory function:
42
+ * `export const x = () => addHTTPPermission('pattern', [...])`
43
+ *
44
+ * @template PikkuPermission The permission type.
45
+ * @param {string} pattern - Route pattern (e.g., '*' for all routes, '/api/*' for specific routes).
46
+ * @param {CorePermissionGroup | CorePikkuPermission[]} permissions - Permissions for this route pattern.
47
+ *
48
+ * @returns {CorePermissionGroup | CorePikkuPermission[]} The permissions (for chaining/wrapping).
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * // Recommended: tree-shakeable
53
+ * export const httpGlobalPermissions = () => addHTTPPermission('*', [
54
+ * authenticatedPermission,
55
+ * rateLimitPermission
56
+ * ])
57
+ *
58
+ * // Also works: no tree-shaking
59
+ * export const apiPermissions = addHTTPPermission('/api/*', [
60
+ * adminPermission
61
+ * ])
62
+ * ```
63
+ */
64
+ export declare const addHTTPPermission: <PikkuPermission extends CorePikkuPermission>(pattern: string, permissions: CorePermissionGroup | CorePikkuPermission[]) => CorePermissionGroup | CorePikkuPermission[];
35
65
  /**
36
66
  * Adds a new route to the global HTTP route registry.
37
67
  *
@@ -43,6 +43,40 @@ export const addHTTPMiddleware = (pattern, middleware) => {
43
43
  httpGroups[pattern] = middleware;
44
44
  return middleware;
45
45
  };
46
+ /**
47
+ * Registers HTTP permissions for a specific route pattern.
48
+ *
49
+ * This function registers permissions at runtime that will be applied to
50
+ * HTTP routes matching the specified pattern.
51
+ *
52
+ * For tree-shaking benefits, wrap in a factory function:
53
+ * `export const x = () => addHTTPPermission('pattern', [...])`
54
+ *
55
+ * @template PikkuPermission The permission type.
56
+ * @param {string} pattern - Route pattern (e.g., '*' for all routes, '/api/*' for specific routes).
57
+ * @param {CorePermissionGroup | CorePikkuPermission[]} permissions - Permissions for this route pattern.
58
+ *
59
+ * @returns {CorePermissionGroup | CorePikkuPermission[]} The permissions (for chaining/wrapping).
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * // Recommended: tree-shakeable
64
+ * export const httpGlobalPermissions = () => addHTTPPermission('*', [
65
+ * authenticatedPermission,
66
+ * rateLimitPermission
67
+ * ])
68
+ *
69
+ * // Also works: no tree-shaking
70
+ * export const apiPermissions = addHTTPPermission('/api/*', [
71
+ * adminPermission
72
+ * ])
73
+ * ```
74
+ */
75
+ export const addHTTPPermission = (pattern, permissions) => {
76
+ const httpGroups = pikkuState('permissions', 'httpGroup');
77
+ httpGroups[pattern] = permissions;
78
+ return permissions;
79
+ };
46
80
  /**
47
81
  * Adds a new route to the global HTTP route registry.
48
82
  *
@@ -204,9 +238,10 @@ const executeRoute = async (services, matchedRoute, http, options) => {
204
238
  auth: route.auth !== false,
205
239
  userSession,
206
240
  data,
207
- permissions: route.permissions,
208
241
  inheritedMiddleware: meta.middleware,
209
242
  wireMiddleware: route.middleware,
243
+ inheritedPermissions: meta.permissions,
244
+ wirePermissions: route.permissions,
210
245
  coerceDataFromSchema: options.coerceDataFromSchema,
211
246
  tags: route.tags,
212
247
  interaction,
@@ -1,6 +1,6 @@
1
1
  import type { SerializeOptions } from 'cookie';
2
2
  import type { PikkuError } from '../../errors/error-handler.js';
3
- import type { PikkuDocs, CoreServices, CoreSingletonServices, CoreUserSession, CreateSessionServices, CorePikkuMiddleware, MiddlewareMetadata } from '../../types/core.types.js';
3
+ import type { PikkuDocs, CoreServices, CoreSingletonServices, CoreUserSession, CreateSessionServices, CorePikkuMiddleware, MiddlewareMetadata, PermissionMetadata } from '../../types/core.types.js';
4
4
  import type { CorePikkuFunction, CorePikkuFunctionSessionless, CorePikkuPermission, CorePermissionGroup, CorePikkuFunctionConfig } from '../../function/functions.types.js';
5
5
  type ExtractHTTPWiringParams<S extends string> = S extends `${string}:${infer Param}/${infer Rest}` ? Param | ExtractHTTPWiringParams<`/${Rest}`> : S extends `${string}:${infer Param}` ? Param : never;
6
6
  export type AssertHTTPWiringParams<In, HTTPWiring extends string> = ExtractHTTPWiringParams<HTTPWiring> extends keyof In ? unknown : [
@@ -146,6 +146,7 @@ export type HTTPWiringMeta = {
146
146
  tags?: string[];
147
147
  sse?: true;
148
148
  middleware?: MiddlewareMetadata[];
149
+ permissions?: PermissionMetadata[];
149
150
  };
150
151
  export type HTTPWiringsMeta = Record<HTTPMethod, Record<string, HTTPWiringMeta>>;
151
152
  export type HTTPFunctionsMeta = Array<{
@@ -1,5 +1,5 @@
1
1
  export * from './pikku-fetch-http-request.js';
2
2
  export * from './pikku-fetch-http-response.js';
3
3
  export * from './log-http-routes.js';
4
- export { fetch, fetchData, wireHTTP, addHTTPMiddleware } from './http-runner.js';
4
+ export { fetch, fetchData, wireHTTP, addHTTPMiddleware, addHTTPPermission, } from './http-runner.js';
5
5
  export type * from './http.types.js';
@@ -1,4 +1,4 @@
1
1
  export * from './pikku-fetch-http-request.js';
2
2
  export * from './pikku-fetch-http-response.js';
3
3
  export * from './log-http-routes.js';
4
- export { fetch, fetchData, wireHTTP, addHTTPMiddleware } from './http-runner.js';
4
+ export { fetch, fetchData, wireHTTP, addHTTPMiddleware, addHTTPPermission, } from './http-runner.js';
@@ -1,4 +1,4 @@
1
- import { MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from '@pikku/core';
1
+ import { MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from '@pikku/core/mcp';
2
2
  export interface MCPToolEndpoint {
3
3
  name: string;
4
4
  title: string;
@@ -140,6 +140,8 @@ async function runMCPPikkuFunc(request, type, name, mcp, pikkuFuncName, { single
140
140
  data: () => request.params,
141
141
  inheritedMiddleware: meta?.middleware,
142
142
  wireMiddleware: mcp.middleware,
143
+ inheritedPermissions: meta?.permissions,
144
+ wirePermissions: mcp.permissions,
143
145
  tags: mcp.tags,
144
146
  interaction,
145
147
  });
@@ -1,5 +1,20 @@
1
- import { CorePikkuFunctionConfig, CorePikkuFunctionSessionless } from '../../function/functions.types.js';
2
- import { CorePikkuMiddleware, MiddlewareMetadata } from '../../types/core.types.js';
1
+ import { CorePermissionGroup, CorePikkuFunctionConfig, CorePikkuFunctionSessionless, CorePikkuPermission } from '../../function/functions.types.js';
2
+ import { CorePikkuMiddleware, MiddlewareMetadata, PermissionMetadata } from '../../types/core.types.js';
3
+ /**
4
+ * Extract URI parameters from MCP resource URI template.
5
+ * E.g., "user/{userId}/post/{postId}" => "userId" | "postId"
6
+ */
7
+ type ExtractMCPURIParams<S extends string> = S extends `${string}{${infer Param}}/${infer Rest}` ? Param | ExtractMCPURIParams<Rest> : S extends `${string}{${infer Param}}` ? Param : never;
8
+ /**
9
+ * Type-level assertion that MCP resource URI parameters are present in the input type.
10
+ * This ensures compile-time safety for URI parameter validation.
11
+ */
12
+ export type AssertMCPResourceURIParams<In, URI extends string> = ExtractMCPURIParams<URI> extends keyof In ? unknown : [
13
+ 'Error: MCP Resource URI parameters',
14
+ ExtractMCPURIParams<URI>,
15
+ 'not in input type',
16
+ keyof In
17
+ ];
3
18
  export type PikkuMCP<Tools extends string = any> = {
4
19
  uri?: string;
5
20
  sendResourceUpdated: (uri: string) => void;
@@ -10,25 +25,27 @@ export type PikkuMCP<Tools extends string = any> = {
10
25
  /**
11
26
  * Represents metadata for MCP resources, including name, description, and documentation.
12
27
  */
13
- export type MCPResourceMeta = Record<string, Omit<CoreMCPResource, 'func' | 'middleware'> & {
28
+ export type MCPResourceMeta = Record<string, Omit<CoreMCPResource, 'func' | 'middleware' | 'permissions'> & {
14
29
  pikkuFuncName: string;
15
30
  inputSchema: string | null;
16
31
  outputSchema: string | null;
17
32
  middleware?: MiddlewareMetadata[];
33
+ permissions?: PermissionMetadata[];
18
34
  }>;
19
35
  /**
20
36
  * Represents metadata for MCP tools, including name, description, and documentation.
21
37
  */
22
- export type MCPToolMeta = Record<string, Omit<CoreMCPTool, 'func' | 'middleware'> & {
38
+ export type MCPToolMeta = Record<string, Omit<CoreMCPTool, 'func' | 'middleware' | 'permissions'> & {
23
39
  pikkuFuncName: string;
24
40
  inputSchema: string | null;
25
41
  outputSchema: string | null;
26
42
  middleware?: MiddlewareMetadata[];
43
+ permissions?: PermissionMetadata[];
27
44
  }>;
28
45
  /**
29
46
  * Represents metadata for MCP prompts, including name, description, and arguments.
30
47
  */
31
- export type MCPPromptMeta = Record<string, Omit<CoreMCPPrompt, 'func' | 'middleware'> & {
48
+ export type MCPPromptMeta = Record<string, Omit<CoreMCPPrompt, 'func' | 'middleware' | 'permissions'> & {
32
49
  pikkuFuncName: string;
33
50
  inputSchema: string | null;
34
51
  outputSchema: string | null;
@@ -38,43 +55,46 @@ export type MCPPromptMeta = Record<string, Omit<CoreMCPPrompt, 'func' | 'middlew
38
55
  required: boolean;
39
56
  }>;
40
57
  middleware?: MiddlewareMetadata[];
58
+ permissions?: PermissionMetadata[];
41
59
  }>;
42
60
  /**
43
61
  * Represents an MCP resource with specific properties.
44
62
  */
45
- export type CoreMCPResource<PikkuFunction = CorePikkuFunctionConfig<CorePikkuFunctionSessionless<any, any>>, PikkuMiddleware = CorePikkuMiddleware<any>> = {
63
+ export type CoreMCPResource<PikkuFunctionConfig = CorePikkuFunctionConfig<CorePikkuFunctionSessionless<any, any>>, PikkuPermission = CorePikkuPermission<any, any>, PikkuMiddleware = CorePikkuMiddleware<any>> = {
46
64
  uri: string;
47
65
  title: string;
48
- description?: string;
66
+ description: string;
49
67
  mimeType?: string;
50
68
  size?: number;
51
69
  streaming?: boolean;
52
- func: PikkuFunction;
70
+ func: PikkuFunctionConfig;
53
71
  tags?: string[];
54
72
  middleware?: PikkuMiddleware[];
73
+ permissions?: CorePermissionGroup<PikkuPermission>;
55
74
  };
56
75
  /**
57
76
  * Represents an MCP tool with specific properties.
58
77
  */
59
- export type CoreMCPTool<PikkuFunction = CorePikkuFunctionConfig<CorePikkuFunctionSessionless<any, any>>, PikkuMiddleware = CorePikkuMiddleware<any>> = {
78
+ export type CoreMCPTool<PikkuFunctionConfig = CorePikkuFunctionConfig<CorePikkuFunctionSessionless<any, any>>, PikkuPermission = CorePikkuPermission<any, any>, PikkuMiddleware = CorePikkuMiddleware<any>> = {
60
79
  name: string;
61
80
  title?: string;
62
81
  description: string;
63
- annotations?: Record<string, any>;
64
- func: PikkuFunction;
82
+ func: PikkuFunctionConfig;
65
83
  tags?: string[];
66
84
  streaming?: boolean;
67
85
  middleware?: PikkuMiddleware[];
86
+ permissions?: CorePermissionGroup<PikkuPermission>;
68
87
  };
69
88
  /**
70
89
  * Represents an MCP prompt with specific properties.
71
90
  */
72
- export type CoreMCPPrompt<PikkuFunction = CorePikkuFunctionConfig<CorePikkuFunctionSessionless<any, MCPPromptResponse>>, PikkuMiddleware = CorePikkuMiddleware<any>> = {
91
+ export type CoreMCPPrompt<PikkuFunctionConfig = CorePikkuFunctionConfig<CorePikkuFunctionSessionless<any, MCPPromptResponse>>, PikkuPermission = CorePikkuPermission<any, any>, PikkuMiddleware = CorePikkuMiddleware<any>> = {
73
92
  name: string;
74
- description?: string;
75
- func: PikkuFunction;
93
+ description: string;
94
+ func: PikkuFunctionConfig;
76
95
  tags?: string[];
77
96
  middleware?: PikkuMiddleware[];
97
+ permissions?: CorePermissionGroup<PikkuPermission>;
78
98
  };
79
99
  export type JsonRpcRequest = {
80
100
  jsonrpc: string;
@@ -122,3 +142,4 @@ export type MCPToolMessage = {
122
142
  data: string;
123
143
  };
124
144
  export type MCPToolResponse = MCPToolMessage[];
145
+ export {};
@@ -74,6 +74,7 @@ export async function runScheduledTask({ name, session, singletonServices, creat
74
74
  singletonServices,
75
75
  getAllServices,
76
76
  userSession,
77
+ auth: false,
77
78
  data: () => undefined,
78
79
  inheritedMiddleware: meta.middleware,
79
80
  wireMiddleware: task.middleware,
@@ -15,10 +15,10 @@ export type ScheduledTasksMeta<UserSession extends CoreUserSession = any> = Reco
15
15
  /**
16
16
  * Represents a core scheduled task.
17
17
  */
18
- export type CoreScheduledTask<PikkuFunction = CorePikkuFunctionConfig<CorePikkuFunctionSessionless<void, void>>, PikkuMiddleware = CorePikkuMiddleware<any>> = {
18
+ export type CoreScheduledTask<PikkuFunctionConfig = CorePikkuFunctionConfig<CorePikkuFunctionSessionless<void, void>>, PikkuMiddleware = CorePikkuMiddleware<any>> = {
19
19
  name: string;
20
20
  schedule: string;
21
- func: PikkuFunction;
21
+ func: PikkuFunctionConfig;
22
22
  docs?: PikkuDocs;
23
23
  tags?: string[];
24
24
  middleware?: PikkuMiddleware[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/core",
3
- "version": "0.9.12-next.0",
3
+ "version": "0.10.0",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "module": "dist/index.js",
@@ -27,6 +27,7 @@
27
27
  "./scheduler": "./dist/wirings/scheduler/index.js",
28
28
  "./rpc": "./dist/wirings/rpc/index.js",
29
29
  "./mcp": "./dist/wirings/mcp/index.js",
30
+ "./cli": "./dist/wirings/cli/index.js",
30
31
  "./cli/channel": "./dist/wirings/cli/channel/index.js",
31
32
  "./errors": "./dist/errors/index.js",
32
33
  "./services": "./dist/services/index.js",
@@ -36,14 +37,14 @@
36
37
  },
37
38
  "dependencies": {
38
39
  "@types/cookie": "^1.0.0",
39
- "@types/uuid": "^10.0.0",
40
+ "@types/uuid": "^11.0.0",
40
41
  "cookie": "^1.0.2",
41
- "path-to-regexp": "^8.2.0",
42
+ "path-to-regexp": "^8.3.0",
42
43
  "picoquery": "^2.5.0"
43
44
  },
44
45
  "devDependencies": {
45
- "@types/node": "^24.2.1",
46
- "tsx": "^4.20.4",
46
+ "@types/node": "^24.9.1",
47
+ "tsx": "^4.20.6",
47
48
  "typescript": "^5.9"
48
49
  },
49
50
  "engines": {
@@ -3,6 +3,12 @@
3
3
  */
4
4
  import { addError, PikkuError } from './error-handler.js'
5
5
 
6
+ export class InvalidMiddlewareInteractionError extends PikkuError {}
7
+ addError(InvalidMiddlewareInteractionError, {
8
+ status: 500,
9
+ message: 'The middleware interaction is invalid for the current wiring type.',
10
+ })
11
+
6
12
  /**
7
13
  * The server cannot or will not process the request due to client error (e.g., malformed request syntax).
8
14
  * @group Error
@@ -1,6 +1,11 @@
1
1
  import { test } from 'node:test'
2
2
  import assert from 'node:assert'
3
- import { pikkuPermission, pikkuMiddleware } from './index.js'
3
+ import {
4
+ pikkuPermission,
5
+ pikkuMiddleware,
6
+ pikkuPermissionFactory,
7
+ pikkuMiddlewareFactory,
8
+ } from './index.js'
4
9
 
5
10
  test('pikkuPermission factory function', async () => {
6
11
  const permission = pikkuPermission(async ({ logger }, data, session) => {
@@ -35,3 +40,57 @@ test('pikkuMiddleware returns the same function', async () => {
35
40
 
36
41
  assert.strictEqual(wrappedFn, originalFn)
37
42
  })
43
+
44
+ test('pikkuPermissionFactory creates a factory function', async () => {
45
+ const factory = pikkuPermissionFactory<{ role: string }>(({ role }) => {
46
+ return pikkuPermission(async ({ logger }, data, session) => {
47
+ return session?.role === role
48
+ })
49
+ })
50
+
51
+ assert.strictEqual(typeof factory, 'function')
52
+ assert.strictEqual(factory.length, 1) // input parameter
53
+
54
+ // Call the factory to get a permission
55
+ const permission = factory({ role: 'admin' })
56
+ assert.strictEqual(typeof permission, 'function')
57
+ assert.strictEqual(permission.length, 3) // services, data, session
58
+ })
59
+
60
+ test('pikkuMiddlewareFactory creates a factory function', async () => {
61
+ const factory = pikkuMiddlewareFactory<{ message: string }>(({ message }) => {
62
+ return pikkuMiddleware(async ({ logger }, interactions, next) => {
63
+ logger.info(message)
64
+ await next()
65
+ })
66
+ })
67
+
68
+ assert.strictEqual(typeof factory, 'function')
69
+ assert.strictEqual(factory.length, 1) // input parameter
70
+
71
+ // Call the factory to get middleware
72
+ const middleware = factory({ message: 'test' })
73
+ assert.strictEqual(typeof middleware, 'function')
74
+ assert.strictEqual(middleware.length, 3) // services, interactions, next
75
+ })
76
+
77
+ test('pikkuPermissionFactory returns the same factory', async () => {
78
+ const originalFactory = ({ role }) =>
79
+ pikkuPermission(async ({ logger }, data, session) => {
80
+ return session?.role === role
81
+ })
82
+ const wrappedFactory = pikkuPermissionFactory(originalFactory)
83
+
84
+ assert.strictEqual(wrappedFactory, originalFactory)
85
+ })
86
+
87
+ test('pikkuMiddlewareFactory returns the same factory', async () => {
88
+ const originalFactory = ({ message }) =>
89
+ pikkuMiddleware(async ({ logger }, interactions, next) => {
90
+ logger.info(message)
91
+ await next()
92
+ })
93
+ const wrappedFactory = pikkuMiddlewareFactory(originalFactory)
94
+
95
+ assert.strictEqual(wrappedFactory, originalFactory)
96
+ })