@pikku/core 0.9.11 → 0.9.12-next.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 (99) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/function/function-runner.d.ts +13 -8
  3. package/dist/function/function-runner.js +49 -37
  4. package/dist/index.d.ts +3 -1
  5. package/dist/index.js +3 -1
  6. package/dist/middleware-runner.d.ts +61 -40
  7. package/dist/middleware-runner.js +118 -66
  8. package/dist/permissions.js +1 -0
  9. package/dist/pikku-state.d.ts +26 -1
  10. package/dist/pikku-state.js +10 -1
  11. package/dist/services/content-service.d.ts +2 -3
  12. package/dist/services/index.d.ts +0 -1
  13. package/dist/services/index.js +0 -1
  14. package/dist/services/local-content.d.ts +2 -4
  15. package/dist/types/core.types.d.ts +51 -2
  16. package/dist/types/core.types.js +21 -0
  17. package/dist/wirings/channel/channel-handler.d.ts +2 -2
  18. package/dist/wirings/channel/channel-handler.js +12 -9
  19. package/dist/wirings/channel/channel-runner.d.ts +0 -2
  20. package/dist/wirings/channel/channel-runner.js +2 -3
  21. package/dist/wirings/channel/channel.types.d.ts +5 -3
  22. package/dist/wirings/channel/local/local-channel-runner.js +12 -12
  23. package/dist/wirings/channel/serverless/serverless-channel-runner.js +7 -8
  24. package/dist/wirings/cli/channel/cli-channel-runner.d.ts +12 -0
  25. package/dist/wirings/cli/channel/cli-channel-runner.js +80 -0
  26. package/dist/wirings/cli/channel/index.d.ts +1 -0
  27. package/dist/wirings/cli/channel/index.js +1 -0
  28. package/dist/wirings/cli/cli-runner.d.ts +32 -0
  29. package/dist/wirings/cli/cli-runner.js +328 -0
  30. package/dist/wirings/cli/cli.types.d.ts +177 -0
  31. package/dist/wirings/cli/cli.types.js +1 -0
  32. package/dist/wirings/cli/command-parser.d.ts +19 -0
  33. package/dist/wirings/cli/command-parser.js +373 -0
  34. package/dist/wirings/cli/index.d.ts +5 -0
  35. package/dist/wirings/cli/index.js +5 -0
  36. package/dist/wirings/http/http-runner.d.ts +29 -10
  37. package/dist/wirings/http/http-runner.js +103 -131
  38. package/dist/wirings/http/http.types.d.ts +10 -10
  39. package/dist/wirings/http/index.d.ts +1 -1
  40. package/dist/wirings/http/index.js +1 -1
  41. package/dist/wirings/http/pikku-fetch-http-response.js +3 -1
  42. package/dist/wirings/http/routers/http-router.d.ts +0 -2
  43. package/dist/wirings/http/routers/path-to-regex.d.ts +1 -1
  44. package/dist/wirings/http/routers/path-to-regex.js +5 -34
  45. package/dist/wirings/mcp/mcp-runner.d.ts +4 -5
  46. package/dist/wirings/mcp/mcp-runner.js +30 -34
  47. package/dist/wirings/mcp/mcp.types.d.ts +11 -8
  48. package/dist/wirings/queue/queue-runner.d.ts +2 -2
  49. package/dist/wirings/queue/queue-runner.js +25 -27
  50. package/dist/wirings/queue/queue.types.d.ts +6 -5
  51. package/dist/wirings/rpc/index.d.ts +1 -1
  52. package/dist/wirings/rpc/index.js +1 -1
  53. package/dist/wirings/rpc/rpc-runner.d.ts +6 -18
  54. package/dist/wirings/rpc/rpc-runner.js +13 -8
  55. package/dist/wirings/scheduler/scheduler-runner.d.ts +2 -2
  56. package/dist/wirings/scheduler/scheduler-runner.js +37 -35
  57. package/dist/wirings/scheduler/scheduler.types.d.ts +4 -3
  58. package/package.json +4 -3
  59. package/src/function/function-runner.test.ts +73 -23
  60. package/src/function/function-runner.ts +74 -55
  61. package/src/index.ts +8 -1
  62. package/src/middleware-runner.test.ts +24 -16
  63. package/src/middleware-runner.ts +136 -83
  64. package/src/permissions.ts +1 -0
  65. package/src/pikku-state.ts +47 -2
  66. package/src/services/content-service.ts +5 -4
  67. package/src/services/index.ts +0 -1
  68. package/src/services/local-content.ts +9 -4
  69. package/src/types/core.types.ts +74 -3
  70. package/src/wirings/channel/channel-handler.ts +9 -13
  71. package/src/wirings/channel/channel-runner.ts +2 -6
  72. package/src/wirings/channel/channel.types.ts +5 -1
  73. package/src/wirings/channel/local/local-channel-runner.ts +25 -15
  74. package/src/wirings/channel/serverless/serverless-channel-runner.ts +15 -17
  75. package/src/wirings/cli/channel/cli-channel-runner.ts +118 -0
  76. package/src/wirings/cli/channel/index.ts +1 -0
  77. package/src/wirings/cli/cli-runner.test.ts +382 -0
  78. package/src/wirings/cli/cli-runner.ts +503 -0
  79. package/src/wirings/cli/cli.types.ts +320 -0
  80. package/src/wirings/cli/command-parser.test.ts +440 -0
  81. package/src/wirings/cli/command-parser.ts +470 -0
  82. package/src/wirings/cli/index.ts +12 -0
  83. package/src/wirings/http/http-runner.test.ts +8 -7
  84. package/src/wirings/http/http-runner.ts +126 -159
  85. package/src/wirings/http/http.types.ts +56 -11
  86. package/src/wirings/http/index.ts +1 -1
  87. package/src/wirings/http/pikku-fetch-http-response.ts +3 -1
  88. package/src/wirings/http/routers/http-router.ts +0 -2
  89. package/src/wirings/http/routers/path-to-regex.test.ts +4 -5
  90. package/src/wirings/http/routers/path-to-regex.ts +6 -43
  91. package/src/wirings/mcp/mcp-runner.ts +56 -55
  92. package/src/wirings/mcp/mcp.types.ts +23 -8
  93. package/src/wirings/queue/queue-runner.ts +44 -47
  94. package/src/wirings/queue/queue.types.ts +10 -6
  95. package/src/wirings/rpc/index.ts +1 -1
  96. package/src/wirings/rpc/rpc-runner.ts +27 -9
  97. package/src/wirings/scheduler/scheduler-runner.ts +57 -56
  98. package/src/wirings/scheduler/scheduler.types.ts +9 -2
  99. package/tsconfig.tsbuildinfo +1 -1
@@ -3,7 +3,9 @@ import {
3
3
  CoreSingletonServices,
4
4
  PikkuInteraction,
5
5
  CorePikkuMiddleware,
6
+ CorePikkuMiddlewareGroup,
6
7
  PikkuWiringTypes,
8
+ MiddlewareMetadata,
7
9
  } from './types/core.types.js'
8
10
  import { pikkuState } from './pikku-state.js'
9
11
  import { freezeDedupe } from './utils.js'
@@ -49,79 +51,89 @@ export const runMiddleware = async <Middleware extends CorePikkuMiddleware>(
49
51
  }
50
52
 
51
53
  /**
52
- * Adds global middleware for a specific tag.
54
+ * Registers global middleware for a specific tag.
53
55
  *
54
- * This function allows you to register middleware that will be applied to
55
- * any wiring (HTTP, Channel, Queue, Scheduler, MCP) that includes the matching tag.
56
+ * This function registers middleware at runtime that will be applied to
57
+ * any wiring (HTTP, Channel, Queue, Scheduler, MCP, CLI) that includes the matching tag.
58
+ *
59
+ * For tree-shaking benefits, wrap in a factory function:
60
+ * `export const x = () => addMiddleware('tag', [...])`
61
+ *
62
+ * Accepts an array that can contain:
63
+ * - Direct middleware functions (CorePikkuMiddleware)
64
+ * - Factory middleware functions (CorePikkuMiddlewareFactory)
56
65
  *
57
66
  * @template PikkuMiddleware The middleware type.
58
67
  * @param {string} tag - The tag that the middleware should apply to.
59
- * @param {PikkuMiddleware[]} middleware - The middleware array to apply for the specified tag.
68
+ * @param {CorePikkuMiddlewareGroup} middleware - Array of middleware for this tag.
60
69
  *
61
- * @throws {Error} If middleware for the tag already exists.
70
+ * @returns {CorePikkuMiddlewareGroup} The middleware array (for chaining/wrapping).
62
71
  *
63
72
  * @example
64
73
  * ```typescript
65
- * // Add admin middleware for admin endpoints
66
- * addMiddleware('admin', [adminMiddleware])
74
+ * // Recommended: tree-shakeable
75
+ * export const adminMiddleware = () => addMiddleware('admin', [
76
+ * authMiddleware,
77
+ * loggingMiddleware({ level: 'info' })
78
+ * ])
67
79
  *
68
- * // Add authentication middleware for auth endpoints
69
- * addMiddleware('auth', [authMiddleware])
70
- *
71
- * // Add logging middleware for all API endpoints
72
- * addMiddleware('api', [loggingMiddleware])
80
+ * // Also works: no tree-shaking
81
+ * export const apiMiddleware = addMiddleware('api', [
82
+ * rateLimitMiddleware
83
+ * ])
73
84
  * ```
74
85
  */
75
86
  export const addMiddleware = <PikkuMiddleware extends CorePikkuMiddleware>(
76
87
  tag: string,
77
- middleware: PikkuMiddleware[]
78
- ) => {
79
- const middlewareStore = pikkuState('misc', 'middleware')
80
-
81
- // Check if tag already exists
82
- if (middlewareStore[tag]) {
83
- throw new Error(
84
- `Middleware for tag '${tag}' already exists. Use a different tag or remove the existing middleware first.`
85
- )
86
- }
87
-
88
- middlewareStore[tag] = middleware as CorePikkuMiddleware[]
88
+ middleware: CorePikkuMiddlewareGroup
89
+ ): CorePikkuMiddlewareGroup => {
90
+ const tagGroups = pikkuState('middleware', 'tagGroup')
91
+ tagGroups[tag] = middleware
92
+ return middleware
89
93
  }
90
94
 
91
95
  /**
92
- * Retrieves middleware for a given set of tags.
96
+ * Registers a single middleware function by name in the global middleware store.
93
97
  *
94
- * This function looks up all middleware registered for any of the provided tags
95
- * and returns them as a flattened array.
98
+ * This function is used by CLI-generated code to register middleware functions
99
+ * that can be referenced by name in metadata. It stores middleware in a special
100
+ * namespace to avoid conflicts with tag-based middleware.
96
101
  *
97
- * @param {string[]} tags - Array of tags to look up middleware for.
98
- * @returns {CorePikkuMiddleware[]} Array of middleware functions that apply to the given tags.
102
+ * @param {string} name - The unique name (pikkuFuncName) of the middleware function.
103
+ * @param {CorePikkuMiddleware} middleware - The middleware function to register.
99
104
  *
100
105
  * @example
101
106
  * ```typescript
102
- * // Get all middleware for tags 'api' and 'auth'
103
- * const middleware = getMiddlewareForTags(['api', 'auth'])
107
+ * // Called by CLI-generated pikku-middleware.gen.ts
108
+ * registerMiddleware('authMiddleware_src_middleware_ts_10_5', authMiddleware)
109
+ * registerMiddleware('loggingMiddleware_src_middleware_ts_20_10', loggingMiddleware)
104
110
  * ```
105
111
  */
106
- export const getMiddlewareForTags = (
107
- tags?: string[]
108
- ): CorePikkuMiddleware[] => {
109
- if (!tags || tags.length === 0) {
110
- return []
111
- }
112
-
112
+ export const registerMiddleware = (
113
+ name: string,
114
+ middleware: CorePikkuMiddleware
115
+ ) => {
113
116
  const middlewareStore = pikkuState('misc', 'middleware')
114
- const applicableMiddleware: CorePikkuMiddleware[] = []
115
-
116
- // Collect middleware for all matching tags
117
- for (const tag of tags) {
118
- const tagMiddleware = middlewareStore[tag]
119
- if (tagMiddleware) {
120
- applicableMiddleware.push(...tagMiddleware)
121
- }
122
- }
117
+ middlewareStore[name] = [middleware]
118
+ }
123
119
 
124
- return applicableMiddleware
120
+ /**
121
+ * Retrieves a registered middleware function by its name.
122
+ *
123
+ * This function looks up middleware that was registered with registerMiddleware.
124
+ * It's used internally by the framework to resolve middleware references in metadata.
125
+ *
126
+ * @param {string} name - The unique name (pikkuFuncName) of the middleware function.
127
+ * @returns {CorePikkuMiddleware | undefined} The middleware function, or undefined if not found.
128
+ *
129
+ * @internal
130
+ */
131
+ export const getMiddlewareByName = (
132
+ name: string
133
+ ): CorePikkuMiddleware | undefined => {
134
+ const middlewareStore = pikkuState('misc', 'middleware')
135
+ const middleware = middlewareStore[name]
136
+ return middleware?.[0]
125
137
  }
126
138
 
127
139
  const middlewareCache: Record<
@@ -134,28 +146,28 @@ const middlewareCache: Record<
134
146
  [PikkuWiringTypes.queue]: {},
135
147
  [PikkuWiringTypes.scheduler]: {},
136
148
  [PikkuWiringTypes.mcp]: {},
149
+ [PikkuWiringTypes.cli]: {},
137
150
  }
138
151
 
139
152
  /**
140
- * Combines tag-based middleware with wiring-specific middleware and function-level middleware.
153
+ * Combines wiring-specific middleware with function-level middleware.
141
154
  *
142
- * This helper function gets middleware for tags and combines it with any
143
- * wiring-specific middleware and function-level middleware, avoiding the need for manual spreading.
155
+ * This function resolves middleware metadata into actual middleware functions and combines them.
156
+ * It filters out wire middleware without tags from inheritedMiddleware to avoid duplication
157
+ * (those are passed separately as wireMiddleware).
144
158
  *
145
159
  * @param {object} options - Configuration object for combining middleware.
146
- * @param {CorePikkuMiddleware[] | undefined} options.wiringMiddleware - Wiring-specific middleware.
147
- * @param {string[] | undefined} options.wiringTags - Array of wiring-level tags to look up middleware for.
148
- * @param {CorePikkuMiddleware[] | undefined} options.funcMiddleware - Function-level middleware.
149
- * @param {string[] | undefined} options.funcTags - Array of function-level tags to look up middleware for.
150
- * @returns {CorePikkuMiddleware[]} Combined array of tag-based and wiring-specific middleware.
160
+ * @param {MiddlewareMetadata[] | undefined} options.inheritedMiddleware - Metadata from wiring (HTTP + tags + wire with tags).
161
+ * @param {CorePikkuMiddleware[] | undefined} options.wireMiddleware - Inline wire middleware.
162
+ * @param {MiddlewareMetadata[] | undefined} options.funcMiddleware - Function middleware metadata.
163
+ * @returns {CorePikkuMiddleware[]} Combined array of resolved middleware.
151
164
  *
152
165
  * @example
153
166
  * ```typescript
154
- * const combined = combineMiddleware({
155
- * wiringMiddleware: httpRoute.middleware,
156
- * wiringTags: httpRoute.tags,
157
- * funcMiddleware: funcConfig.middleware,
158
- * funcTags: funcConfig.tags
167
+ * const combined = combineMiddleware(wireType, wireId, {
168
+ * inheritedMiddleware: meta.middleware,
169
+ * wireMiddleware: [inlineMiddleware],
170
+ * funcMiddleware: funcMeta.middleware
159
171
  * })
160
172
  * ```
161
173
  */
@@ -163,38 +175,79 @@ export const combineMiddleware = (
163
175
  wireType: PikkuWiringTypes,
164
176
  uid: string,
165
177
  {
166
- httpMiddleware,
167
- wiringMiddleware,
168
- wiringTags,
178
+ wireInheritedMiddleware,
179
+ wireMiddleware,
180
+ funcInheritedMiddleware,
169
181
  funcMiddleware,
170
- funcTags,
171
182
  }: {
172
- httpMiddleware?: CorePikkuMiddleware[]
173
- wiringMiddleware?: CorePikkuMiddleware[]
174
- wiringTags?: string[]
183
+ wireInheritedMiddleware?: MiddlewareMetadata[]
184
+ wireMiddleware?: CorePikkuMiddleware[]
185
+ funcInheritedMiddleware?: MiddlewareMetadata[]
175
186
  funcMiddleware?: CorePikkuMiddleware[]
176
- funcTags?: string[]
177
187
  } = {}
178
188
  ): readonly CorePikkuMiddleware[] => {
179
189
  if (middlewareCache[wireType][uid]) {
180
190
  return middlewareCache[wireType][uid]
181
191
  }
182
192
 
183
- // Run middleware in specific order:
184
- // 1) wiringTags middleware
185
- // 2) wiringMiddleware
186
- // 3) funcMiddleware
187
- // 4) funcTags middleware
188
- const wiringTaggedMiddleware = getMiddlewareForTags(wiringTags)
189
- const funcTaggedMiddleware = getMiddlewareForTags(funcTags)
193
+ const resolved: CorePikkuMiddleware[] = []
194
+
195
+ // 1. Resolve wire inherited middleware (HTTP + tag groups + individual wire middleware)
196
+ if (wireInheritedMiddleware) {
197
+ for (const meta of wireInheritedMiddleware) {
198
+ if (meta.type === 'http') {
199
+ // Look up HTTP middleware group from pikkuState
200
+ const group = pikkuState('middleware', 'httpGroup')[meta.route]
201
+ if (group) {
202
+ // At runtime, all factories should be resolved to middleware
203
+ resolved.push(...(group as CorePikkuMiddleware[]))
204
+ }
205
+ } else if (meta.type === 'tag') {
206
+ // Look up tag middleware group from pikkuState
207
+ const group = pikkuState('middleware', 'tagGroup')[meta.tag]
208
+ if (group) {
209
+ // At runtime, all factories should be resolved to middleware
210
+ resolved.push(...(group as CorePikkuMiddleware[]))
211
+ }
212
+ } else if (meta.type === 'wire') {
213
+ // Individual wire middleware (exported, not inline)
214
+ const middleware = getMiddlewareByName(meta.name)
215
+ if (middleware) {
216
+ resolved.push(middleware)
217
+ }
218
+ }
219
+ }
220
+ }
221
+
222
+ // 2. Add inline wire middleware
223
+ if (wireMiddleware) {
224
+ resolved.push(...wireMiddleware)
225
+ }
226
+
227
+ // 3. Resolve function inherited middleware (only tags, wire middleware already handled)
228
+ if (funcInheritedMiddleware) {
229
+ for (const meta of funcInheritedMiddleware) {
230
+ if (meta.type === 'tag') {
231
+ // Look up tag middleware group from pikkuState
232
+ const group = pikkuState('middleware', 'tagGroup')[meta.tag]
233
+ if (group) {
234
+ // At runtime, all factories should be resolved to middleware
235
+ resolved.push(...(group as CorePikkuMiddleware[]))
236
+ }
237
+ }
238
+ // Note: wire middleware is already handled in wireInheritedMiddleware
239
+ }
240
+ }
241
+
242
+ // 4. Add inline function middleware
243
+ if (funcMiddleware) {
244
+ resolved.push(...funcMiddleware)
245
+ }
190
246
 
191
- middlewareCache[wireType][uid] = freezeDedupe([
192
- ...(httpMiddleware || []),
193
- ...wiringTaggedMiddleware,
194
- ...(wiringMiddleware || []),
195
- ...(funcMiddleware || []),
196
- ...funcTaggedMiddleware,
197
- ]) as readonly CorePikkuMiddleware[]
247
+ // Deduplicate and freeze
248
+ middlewareCache[wireType][uid] = freezeDedupe(
249
+ resolved
250
+ ) as readonly CorePikkuMiddleware[]
198
251
 
199
252
  return middlewareCache[wireType][uid]
200
253
  }
@@ -152,6 +152,7 @@ const permissionCache: Record<
152
152
  [PikkuWiringTypes.queue]: {},
153
153
  [PikkuWiringTypes.scheduler]: {},
154
154
  [PikkuWiringTypes.mcp]: {},
155
+ [PikkuWiringTypes.cli]: {},
155
156
  }
156
157
 
157
158
  /**
@@ -4,7 +4,12 @@ import {
4
4
  HTTPMethod,
5
5
  HTTPWiringsMeta,
6
6
  } from './wirings/http/http.types.js'
7
- import { FunctionsMeta, CorePikkuMiddleware } from './types/core.types.js'
7
+ import {
8
+ FunctionsMeta,
9
+ CorePikkuMiddleware,
10
+ CorePikkuMiddlewareGroup,
11
+ FunctionServicesMeta,
12
+ } from './types/core.types.js'
8
13
  import {
9
14
  CoreScheduledTask,
10
15
  ScheduledTasksMeta,
@@ -27,6 +32,7 @@ import {
27
32
  MCPToolMeta,
28
33
  MCPPromptMeta,
29
34
  } from './wirings/mcp/mcp.types.js'
35
+ import { CLIProgramMeta, CLIProgramState } from './wirings/cli/cli.types.js'
30
36
 
31
37
  interface PikkuState {
32
38
  function: {
@@ -69,6 +75,36 @@ interface PikkuState {
69
75
  prompts: Map<string, CoreMCPPrompt>
70
76
  promptsMeta: MCPPromptMeta
71
77
  }
78
+ cli: {
79
+ meta: Record<string, CLIProgramMeta>
80
+ programs: Record<string, CLIProgramState>
81
+ }
82
+ middleware: {
83
+ tagGroup: Record<string, CorePikkuMiddlewareGroup>
84
+ httpGroup: Record<string, CorePikkuMiddlewareGroup>
85
+ tagGroupMeta: Record<
86
+ string,
87
+ {
88
+ exportName: string | null
89
+ sourceFile: string
90
+ position: number
91
+ services: FunctionServicesMeta
92
+ middlewareCount: number
93
+ isFactory: boolean
94
+ }
95
+ >
96
+ httpGroupMeta: Record<
97
+ string,
98
+ {
99
+ exportName: string | null
100
+ sourceFile: string
101
+ position: number
102
+ services: FunctionServicesMeta
103
+ middlewareCount: number
104
+ isFactory: boolean
105
+ }
106
+ >
107
+ }
72
108
  misc: {
73
109
  errors: Map<PikkuError, ErrorDetails>
74
110
  schemas: Map<string, any>
@@ -89,7 +125,6 @@ export const resetPikkuState = () => {
89
125
  },
90
126
  http: {
91
127
  permissions: new Map(),
92
- middleware: new Map(),
93
128
  routes: new Map(),
94
129
  meta: {},
95
130
  },
@@ -113,6 +148,16 @@ export const resetPikkuState = () => {
113
148
  prompts: new Map(),
114
149
  promptsMeta: {} as MCPPromptMeta,
115
150
  },
151
+ cli: {
152
+ meta: {},
153
+ programs: {},
154
+ },
155
+ middleware: {
156
+ tagGroup: {},
157
+ httpGroup: {},
158
+ tagGroupMeta: {},
159
+ httpGroupMeta: {},
160
+ },
116
161
  misc: {
117
162
  errors: globalThis.pikkuState?.misc?.errors || new Map(),
118
163
  schemas: new Map(),
@@ -1,5 +1,3 @@
1
- import { ReadStream } from 'fs'
2
-
3
1
  export interface ContentService {
4
2
  /**
5
3
  * Signs a content key to generate a secure, time-limited access URL.
@@ -49,7 +47,10 @@ export interface ContentService {
49
47
  * @param stream - A readable stream of the file contents.
50
48
  * @returns A boolean indicating success.
51
49
  */
52
- writeFile(assetKey: string, stream: ReadStream): Promise<boolean>
50
+ writeFile(
51
+ assetKey: string,
52
+ stream: ReadableStream | NodeJS.ReadableStream
53
+ ): Promise<boolean>
53
54
 
54
55
  /**
55
56
  * Copies a file from a local absolute path into storage under a new asset key.
@@ -64,5 +65,5 @@ export interface ContentService {
64
65
  * @param assetKey - The key of the file to read.
65
66
  * @returns A readable file stream.
66
67
  */
67
- readFile(assetKey: string): Promise<ReadStream>
68
+ readFile(assetKey: string): Promise<ReadableStream | NodeJS.ReadableStream>
68
69
  }
@@ -15,4 +15,3 @@ export * from './user-session-service.js'
15
15
  export * from './local-secrets.js'
16
16
  export * from './local-variables.js'
17
17
  export * from './logger-console.js'
18
- export * from './local-content.js'
@@ -1,4 +1,4 @@
1
- import { createReadStream, createWriteStream, promises, ReadStream } from 'fs'
1
+ import { createReadStream, createWriteStream, promises } from 'fs'
2
2
  import { mkdir } from 'fs/promises'
3
3
  import { ContentService, Logger } from '@pikku/core/services'
4
4
  import { pipeline } from 'stream/promises'
@@ -38,7 +38,10 @@ export class LocalContent implements ContentService {
38
38
  }
39
39
  }
40
40
 
41
- public async writeFile(assetKey: string, stream: Readable): Promise<boolean> {
41
+ public async writeFile(
42
+ assetKey: string,
43
+ stream: ReadableStream | NodeJS.ReadableStream
44
+ ): Promise<boolean> {
42
45
  this.logger.debug(`Writing file: ${assetKey}`)
43
46
 
44
47
  const path = `${this.config.localFileUploadPath}/${assetKey}`
@@ -47,7 +50,7 @@ export class LocalContent implements ContentService {
47
50
  await this.createDirectoryForFile(path)
48
51
  const fileStream = createWriteStream(path)
49
52
  // Use pipeline to properly manage stream piping and errors
50
- await pipeline(stream, fileStream)
53
+ await pipeline(stream as Readable, fileStream)
51
54
  return true
52
55
  } catch (e) {
53
56
  console.error(e)
@@ -72,7 +75,9 @@ export class LocalContent implements ContentService {
72
75
  return false
73
76
  }
74
77
 
75
- public async readFile(assetKey: string): Promise<ReadStream> {
78
+ public async readFile(
79
+ assetKey: string
80
+ ): Promise<ReadableStream | NodeJS.ReadableStream> {
76
81
  this.logger.debug(`Getting key: ${assetKey}`)
77
82
 
78
83
  const filePath = `${this.config.localFileUploadPath}/${assetKey}`
@@ -8,6 +8,7 @@ import { PikkuRPC } from '../wirings/rpc/rpc-types.js'
8
8
  import { PikkuMCP } from '../wirings/mcp/mcp.types.js'
9
9
  import { PikkuScheduledTask } from '../wirings/scheduler/scheduler.types.js'
10
10
  import { PikkuQueue } from '../wirings/queue/queue.types.js'
11
+ import { PikkuCLI } from '../wirings/cli/cli.types.js'
11
12
 
12
13
  export enum PikkuWiringTypes {
13
14
  http = 'http',
@@ -16,6 +17,7 @@ export enum PikkuWiringTypes {
16
17
  rpc = 'rpc',
17
18
  queue = 'queue',
18
19
  mcp = 'mcp',
20
+ cli = 'cli',
19
21
  }
20
22
 
21
23
  export interface FunctionServicesMeta {
@@ -23,6 +25,27 @@ export interface FunctionServicesMeta {
23
25
  services: string[]
24
26
  }
25
27
 
28
+ /**
29
+ * Metadata for middleware at any level
30
+ * - type: 'http' = HTTP route middleware group (references httpGroup in pikkuState)
31
+ * - type: 'tag' = Tag-based middleware group (references tagGroup in pikkuState)
32
+ * - type: 'wire' = Wire-level individual middleware
33
+ */
34
+ export type MiddlewareMetadata =
35
+ | {
36
+ type: 'http'
37
+ route: string // Route pattern (e.g., '*' for all, '/api/*' for specific)
38
+ }
39
+ | {
40
+ type: 'tag'
41
+ tag: string // Tag name
42
+ }
43
+ | {
44
+ type: 'wire'
45
+ name: string
46
+ inline?: boolean // true if inline middleware
47
+ }
48
+
26
49
  export type FunctionRuntimeMeta = {
27
50
  pikkuFuncName: string
28
51
  inputSchemaName: string | null
@@ -39,6 +62,7 @@ export type FunctionMeta = FunctionRuntimeMeta &
39
62
  tags: string[]
40
63
  docs: PikkuDocs
41
64
  isDirectFunction: boolean // true if it's pikkuFunc(fn), false if it's pikkuFunc({ func: fn })
65
+ middleware: MiddlewareMetadata[] // Function-level middleware (type will be undefined)
42
66
  }>
43
67
 
44
68
  export type FunctionsRuntimeMeta = Record<string, FunctionRuntimeMeta>
@@ -121,6 +145,7 @@ export type PikkuInteraction<In = unknown, Out = unknown> = Partial<{
121
145
  channel: PikkuChannel<unknown, Out>
122
146
  scheduledTask: PikkuScheduledTask
123
147
  queue: PikkuQueue
148
+ cli: PikkuCLI
124
149
  }>
125
150
 
126
151
  /**
@@ -137,6 +162,28 @@ export type CorePikkuMiddleware<
137
162
  next: () => Promise<void>
138
163
  ) => Promise<void>
139
164
 
165
+ /**
166
+ * A factory function that takes input and returns middleware
167
+ * Used when middleware needs configuration/input parameters
168
+ */
169
+ export type CorePikkuMiddlewareFactory<
170
+ In = any,
171
+ SingletonServices extends CoreSingletonServices = CoreSingletonServices,
172
+ UserSession extends CoreUserSession = CoreUserSession,
173
+ > = (input: In) => CorePikkuMiddleware<SingletonServices, UserSession>
174
+
175
+ /**
176
+ * A group of middleware (combination of regular middleware and factories)
177
+ * Used with addMiddleware() and addHTTPMiddleware() to group related middleware together
178
+ */
179
+ export type CorePikkuMiddlewareGroup<
180
+ SingletonServices extends CoreSingletonServices = CoreSingletonServices,
181
+ UserSession extends CoreUserSession = CoreUserSession,
182
+ > = Array<
183
+ | CorePikkuMiddleware<SingletonServices, UserSession>
184
+ | CorePikkuMiddlewareFactory<any, SingletonServices, UserSession>
185
+ >
186
+
140
187
  /**
141
188
  * Factory function for creating middleware with tree-shaking support
142
189
  */
@@ -149,6 +196,29 @@ export const pikkuMiddleware = <
149
196
  return middleware
150
197
  }
151
198
 
199
+ /**
200
+ * Factory function for creating middleware factories
201
+ * Use this when your middleware needs configuration/input parameters
202
+ *
203
+ * @example
204
+ * ```typescript
205
+ * export const logMiddleware = pikkuMiddlewareFactory<LogOptions>(({
206
+ * message,
207
+ * level = 'info'
208
+ * }) => {
209
+ * return pikkuMiddleware(async ({ logger }, _interaction, next) => {
210
+ * logger[level](message)
211
+ * await next()
212
+ * })
213
+ * })
214
+ * ```
215
+ */
216
+ export const pikkuMiddlewareFactory = <In = any>(
217
+ factory: CorePikkuMiddlewareFactory<In>
218
+ ): CorePikkuMiddlewareFactory<In> => {
219
+ return factory
220
+ }
221
+
152
222
  /**
153
223
  * Represents the core services used by Pikku, including singleton services and the request/response interaction.
154
224
  */
@@ -193,9 +263,10 @@ export type CreateSessionServices<
193
263
  /**
194
264
  * Defines a function type for creating config.
195
265
  */
196
- export type CreateConfig<Config extends CoreConfig> = (
197
- variables?: VariablesService
198
- ) => Promise<Config>
266
+ export type CreateConfig<
267
+ Config extends CoreConfig,
268
+ RemainingArgs extends any[] = unknown[],
269
+ > = (variables?: VariablesService, ...args: RemainingArgs) => Promise<Config>
199
270
 
200
271
  /**
201
272
  * Represents the documentation for a route, including summary, description, tags, and errors.
@@ -61,7 +61,6 @@ const validateAuth = (
61
61
 
62
62
  export const processMessageHandlers = (
63
63
  services: CoreServices,
64
- session: CoreUserSession | undefined,
65
64
  channelConfig: CoreChannel<any, any>,
66
65
  channelHandler: PikkuChannelHandler
67
66
  ) => {
@@ -71,7 +70,6 @@ export const processMessageHandlers = (
71
70
  const processMessage = async (
72
71
  data: JSONValue,
73
72
  onMessage: any,
74
- session: CoreUserSession | undefined,
75
73
  routingProperty?: string,
76
74
  routerValue?: string
77
75
  ): Promise<unknown> => {
@@ -87,7 +85,7 @@ export const processMessageHandlers = (
87
85
  return
88
86
  }
89
87
 
90
- const { pikkuFuncName } = getRouteMeta(
88
+ const { pikkuFuncName, middleware: inheritedMiddleware } = getRouteMeta(
91
89
  channelConfig.name,
92
90
  routingProperty,
93
91
  routerValue
@@ -104,15 +102,18 @@ export const processMessageHandlers = (
104
102
  channelConfig.name,
105
103
  pikkuFuncName,
106
104
  {
105
+ singletonServices: services,
107
106
  getAllServices: () => ({
108
107
  ...services,
109
108
  channel: channelHandler.getChannel(),
110
109
  }),
111
- data,
112
- session,
110
+ data: () => data,
111
+ userSession: services.userSession,
113
112
  permissions,
114
- middleware,
113
+ inheritedMiddleware,
114
+ wireMiddleware: middleware,
115
115
  tags: channelConfig.tags,
116
+ interaction: { channel: channelHandler.getChannel() },
116
117
  }
117
118
  )
118
119
  }
@@ -133,7 +134,6 @@ export const processMessageHandlers = (
133
134
  result = await processMessage(
134
135
  messageData,
135
136
  routes[routerValue],
136
- session,
137
137
  routingProperty,
138
138
  routerValue
139
139
  )
@@ -144,11 +144,7 @@ export const processMessageHandlers = (
144
144
  // Default handler if no routes matched but json data was parsed
145
145
  if (!processed && channelConfig.onMessage) {
146
146
  processed = true
147
- result = await processMessage(
148
- messageData,
149
- channelConfig.onMessage,
150
- session
151
- )
147
+ result = await processMessage(messageData, channelConfig.onMessage)
152
148
  }
153
149
  } catch (error) {
154
150
  // Most likely a json error.. ignore
@@ -158,7 +154,7 @@ export const processMessageHandlers = (
158
154
  // Default handler if no routes matched and json data wasn't parsed
159
155
  if (!processed && channelConfig.onMessage) {
160
156
  processed = true
161
- result = await processMessage(rawData, channelConfig.onMessage, session)
157
+ result = await processMessage(rawData, channelConfig.onMessage)
162
158
  }
163
159
 
164
160
  if (!processed) {