@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
@@ -2,6 +2,7 @@ import {
2
2
  CoreServices,
3
3
  CoreUserSession,
4
4
  PikkuWiringTypes,
5
+ PermissionMetadata,
5
6
  } from './types/core.types.js'
6
7
  import {
7
8
  CorePermissionGroup,
@@ -9,6 +10,7 @@ import {
9
10
  } from './function/functions.types.js'
10
11
  import { pikkuState } from './pikku-state.js'
11
12
  import { ForbiddenError } from './errors/errors.js'
13
+ import { freezeDedupe } from './utils.js'
12
14
 
13
15
  /**
14
16
  * This function validates permissions by iterating over permission groups and executing the corresponding permission functions. If all functions in at least one group return true, the permission is considered valid.
@@ -51,43 +53,93 @@ export const verifyPermissions = async (
51
53
  return false
52
54
  }
53
55
 
56
+ /**
57
+ * Registers a single permission function by name in the global permission store.
58
+ *
59
+ * This function is used by CLI-generated code to register permission functions
60
+ * that can be referenced by name in metadata. It stores permissions in a special
61
+ * namespace to avoid conflicts with tag-based permissions.
62
+ *
63
+ * @param {string} name - The unique name (pikkuFuncName) of the permission function.
64
+ * @param {CorePikkuPermission} permission - The permission function to register.
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * // Called by CLI-generated pikku-permissions.gen.ts
69
+ * registerPermission('adminPermission_src_permissions_ts_10_5', adminPermission)
70
+ * registerPermission('readPermission_src_permissions_ts_20_10', readPermission)
71
+ * ```
72
+ */
73
+ export const registerPermission = (
74
+ name: string,
75
+ permission: CorePikkuPermission<any>
76
+ ) => {
77
+ const permissionStore = pikkuState('misc', 'permissions')
78
+ permissionStore[name] = [permission]
79
+ }
80
+
81
+ /**
82
+ * Retrieves a registered permission function by its name.
83
+ *
84
+ * This function looks up permissions that was registered with registerPermission.
85
+ * It's used internally by the framework to resolve permission references in metadata.
86
+ *
87
+ * @param {string} name - The unique name (pikkuFuncName) of the permission function.
88
+ * @returns {CorePikkuPermission | undefined} The permission function, or undefined if not found.
89
+ *
90
+ * @internal
91
+ */
92
+ export const getPermissionByName = (
93
+ name: string
94
+ ): CorePikkuPermission | undefined => {
95
+ const permissionStore = pikkuState('misc', 'permissions')
96
+ const permission = permissionStore[name]
97
+ if (Array.isArray(permission) && permission.length === 1) {
98
+ return permission[0]
99
+ }
100
+ return undefined
101
+ }
102
+
54
103
  /**
55
104
  * Adds global permissions for a specific tag.
56
105
  *
57
106
  * This function allows you to register permissions that will be applied to
58
107
  * any wiring (HTTP, Channel, Queue, Scheduler, MCP) that includes the matching tag.
59
108
  *
109
+ * For tree-shaking benefits, wrap in a factory function:
110
+ * `export const x = () => addPermission('tag', [...])`
111
+ *
60
112
  * @param {string} tag - The tag that the permissions should apply to.
61
113
  * @param {any[]} permissions - The permissions array to apply for the specified tag.
62
114
  *
63
- * @throws {Error} If permissions for the tag already exist.
115
+ * @returns {CorePermissionGroup | CorePikkuPermission[]} The permissions (for chaining/wrapping).
64
116
  *
65
117
  * @example
66
118
  * ```typescript
67
- * // Add admin permissions for admin endpoints
68
- * addPermission('admin', [adminPermission])
69
- *
70
- * // Add authentication permissions for auth endpoints
71
- * addPermission('auth', [authPermission])
119
+ * // Recommended: tree-shakeable
120
+ * export const adminPermissions = () => addPermission('admin', [
121
+ * adminPermission,
122
+ * rolePermission({ role: 'admin' })
123
+ * ])
72
124
  *
73
- * // Add read permissions for all API endpoints
74
- * addPermission('api', [readPermission])
125
+ * // Also works: no tree-shaking
126
+ * export const apiPermissions = addPermission('api', [
127
+ * readPermission
128
+ * ])
75
129
  * ```
76
130
  */
77
131
  export const addPermission = (
78
132
  tag: string,
79
133
  permissions: CorePermissionGroup | CorePikkuPermission[]
80
- ) => {
81
- const permissionsStore = pikkuState('misc', 'permissions')
82
-
83
- // Check if tag already exists
84
- if (permissionsStore[tag]) {
134
+ ): CorePermissionGroup | CorePikkuPermission[] => {
135
+ const tagGroups = pikkuState('permissions', 'tagGroup')
136
+ if (tagGroups[tag]) {
85
137
  throw new Error(
86
138
  `Permissions for tag '${tag}' already exist. Use a different tag or remove the existing permissions first.`
87
139
  )
88
140
  }
89
-
90
- permissionsStore[tag] = permissions
141
+ tagGroups[tag] = permissions
142
+ return permissions
91
143
  }
92
144
 
93
145
  const EMPTY: readonly (CorePermissionGroup | CorePikkuPermission)[] = []
@@ -114,7 +166,7 @@ export const getPermissionsForTags = (
114
166
  return EMPTY
115
167
  }
116
168
 
117
- const permissionsStore = pikkuState('misc', 'permissions')
169
+ const permissionsStore = pikkuState('permissions', 'tagGroup')
118
170
  const applicablePermissions: Array<
119
171
  CorePermissionGroup | CorePikkuPermission
120
172
  > = []
@@ -134,17 +186,9 @@ export const getPermissionsForTags = (
134
186
  return applicablePermissions
135
187
  }
136
188
 
137
- const permissionCache: Record<
189
+ const combinedPermissionsCache: Record<
138
190
  PikkuWiringTypes,
139
- Partial<
140
- Record<
141
- string,
142
- {
143
- wiringTags: readonly (CorePermissionGroup | CorePikkuPermission)[]
144
- funcTags: readonly (CorePermissionGroup | CorePikkuPermission)[]
145
- }
146
- >
147
- >
191
+ Record<string, readonly (CorePermissionGroup | CorePikkuPermission)[]>
148
192
  > = {
149
193
  [PikkuWiringTypes.http]: {},
150
194
  [PikkuWiringTypes.rpc]: {},
@@ -152,114 +196,182 @@ const permissionCache: Record<
152
196
  [PikkuWiringTypes.queue]: {},
153
197
  [PikkuWiringTypes.scheduler]: {},
154
198
  [PikkuWiringTypes.mcp]: {},
199
+ [PikkuWiringTypes.cli]: {},
155
200
  }
156
201
 
157
202
  /**
158
- * Runs permission checks in the specified order:
159
- * 1) wiring tag permissions - at least one must pass if any exist
160
- * 2) wiring permissions - must pass if defined
161
- * 3) function tag permissions - at least one must pass if any exist
162
- * 4) function permissions - must pass if defined
203
+ * Combines wiring-specific permissions with function-level permissions.
204
+ *
205
+ * This function resolves permission metadata into actual permission functions and combines them.
206
+ * It filters out wire permissions without tags from inheritedPermissions to avoid duplication
207
+ * (those are passed separately as wirePermissions).
208
+ *
209
+ * @param {object} options - Configuration object for combining permissions.
210
+ * @param {PermissionMetadata[] | undefined} options.wireInheritedPermissions - Metadata from wiring (HTTP + tags + wire with tags).
211
+ * @param {CorePermissionGroup | CorePikkuPermission[] | undefined} options.wirePermissions - Inline wire permissions.
212
+ * @param {PermissionMetadata[] | undefined} options.funcInheritedPermissions - Function permissions metadata (only tags).
213
+ * @param {CorePermissionGroup | CorePikkuPermission[] | undefined} options.funcPermissions - Inline function permissions.
214
+ * @returns {(CorePermissionGroup | CorePikkuPermission)[]} Combined array of resolved permissions.
215
+ *
216
+ * @example
217
+ * ```typescript
218
+ * const combined = combinePermissions(wireType, wireId, {
219
+ * wireInheritedPermissions: meta.permissions,
220
+ * wirePermissions: inlinePermissions,
221
+ * funcInheritedPermissions: funcMeta.permissions,
222
+ * funcPermissions: funcConfig.permissions
223
+ * })
224
+ * ```
163
225
  */
164
- export const runPermissions = async (
226
+ export const combinePermissions = (
165
227
  wireType: PikkuWiringTypes,
166
228
  uid: string,
167
229
  {
168
- wiringTags,
169
- wiringPermissions,
170
- funcTags,
230
+ wireInheritedPermissions,
231
+ wirePermissions,
232
+ funcInheritedPermissions,
171
233
  funcPermissions,
172
- allServices,
173
- data,
174
- session,
175
234
  }: {
176
- wiringTags?: string[]
177
- wiringPermissions?: CorePermissionGroup
178
- funcTags?: string[]
179
- funcPermissions?: CorePermissionGroup
180
- allServices: CoreServices
181
- data: any
182
- session?: CoreUserSession
235
+ wireInheritedPermissions?: PermissionMetadata[]
236
+ wirePermissions?: CorePermissionGroup | CorePikkuPermission[]
237
+ funcInheritedPermissions?: PermissionMetadata[]
238
+ funcPermissions?: CorePermissionGroup | CorePikkuPermission[]
239
+ } = {}
240
+ ): readonly (CorePermissionGroup | CorePikkuPermission)[] => {
241
+ if (combinedPermissionsCache[wireType][uid]) {
242
+ return combinedPermissionsCache[wireType][uid]
183
243
  }
184
- ) => {
185
- let cachedPermission = permissionCache[wireType][uid]
186
- if (!cachedPermission) {
187
- cachedPermission = {
188
- wiringTags: getPermissionsForTags(wiringTags),
189
- funcTags: getPermissionsForTags(funcTags),
244
+
245
+ const resolved: (CorePermissionGroup | CorePikkuPermission)[] = []
246
+
247
+ // 1. Resolve wire inherited permissions (HTTP + tag groups + individual wire permissions)
248
+ if (wireInheritedPermissions) {
249
+ for (const meta of wireInheritedPermissions) {
250
+ if (meta.type === 'http') {
251
+ // Look up HTTP permission group from pikkuState
252
+ const group = pikkuState('permissions', 'httpGroup')[meta.route]
253
+ if (group) {
254
+ if (Array.isArray(group)) {
255
+ resolved.push(...group)
256
+ } else {
257
+ resolved.push(group)
258
+ }
259
+ }
260
+ } else if (meta.type === 'tag') {
261
+ // Look up tag permission group from pikkuState
262
+ const group = pikkuState('permissions', 'tagGroup')[meta.tag]
263
+ if (group) {
264
+ if (Array.isArray(group)) {
265
+ resolved.push(...group)
266
+ } else {
267
+ resolved.push(group)
268
+ }
269
+ }
270
+ } else if (meta.type === 'wire') {
271
+ // Individual wire permission (exported, not inline)
272
+ const permission = getPermissionByName(meta.name)
273
+ if (permission) {
274
+ resolved.push(permission)
275
+ }
276
+ }
190
277
  }
191
- permissionCache[wireType][uid] = cachedPermission
192
278
  }
193
- let permissioned = true
194
279
 
195
- // 1. Wiring tag permissions - at least one must pass if any exist
196
- const wiringTaggedPermissions = cachedPermission.wiringTags
197
- if (wiringTaggedPermissions.length > 0) {
198
- permissioned = false // Start false, need at least one to pass
199
- for (const permissions of wiringTaggedPermissions) {
200
- const result = await verifyPermissions(
201
- typeof permissions === 'function' ? { permissions } : permissions,
202
- allServices,
203
- data,
204
- session
205
- )
206
- if (result) {
207
- permissioned = true
208
- break // At least one passed, we're good at this level
209
- }
280
+ // 2. Add inline wire permissions
281
+ if (wirePermissions) {
282
+ if (Array.isArray(wirePermissions)) {
283
+ resolved.push(...wirePermissions)
284
+ } else {
285
+ resolved.push(wirePermissions)
210
286
  }
211
- if (!permissioned) {
212
- allServices.logger.debug('Permission denied - wiring tag permissions')
213
- throw new ForbiddenError('Permission denied')
287
+ }
288
+
289
+ // 3. Resolve function inherited permissions (only tags, wire permissions already handled)
290
+ if (funcInheritedPermissions) {
291
+ for (const meta of funcInheritedPermissions) {
292
+ if (meta.type === 'tag') {
293
+ // Look up tag permission group from pikkuState
294
+ const group = pikkuState('permissions', 'tagGroup')[meta.tag]
295
+ if (group) {
296
+ if (Array.isArray(group)) {
297
+ resolved.push(...group)
298
+ } else {
299
+ resolved.push(group)
300
+ }
301
+ }
302
+ }
303
+ // Note: wire permissions are already handled in wireInheritedPermissions
214
304
  }
215
305
  }
216
306
 
217
- // 2. Wiring permissions - must pass if defined
218
- if (wiringPermissions) {
219
- permissioned = await verifyPermissions(
220
- wiringPermissions,
221
- allServices,
222
- data,
223
- session
224
- )
225
- if (!permissioned) {
226
- allServices.logger.debug('Permission denied - wiring permissions')
227
- throw new ForbiddenError('Permission denied')
307
+ // 4. Add inline function permissions
308
+ if (funcPermissions) {
309
+ if (Array.isArray(funcPermissions)) {
310
+ resolved.push(...funcPermissions)
311
+ } else {
312
+ resolved.push(funcPermissions)
228
313
  }
229
314
  }
230
315
 
231
- // 3. Function tag permissions - at least one must pass if any exist
232
- const funcTaggedPermissions = cachedPermission.funcTags
233
- if (funcTaggedPermissions.length > 0) {
234
- permissioned = false // Start false, need at least one to pass
235
- for (const permissions of funcTaggedPermissions) {
316
+ // Deduplicate and freeze
317
+ combinedPermissionsCache[wireType][uid] = freezeDedupe(resolved) as readonly (
318
+ | CorePermissionGroup
319
+ | CorePikkuPermission
320
+ )[]
321
+
322
+ return combinedPermissionsCache[wireType][uid]
323
+ }
324
+
325
+ /**
326
+ * Runs permission checks using combined permissions from all sources.
327
+ * Combines permissions from wire and function levels, then validates them.
328
+ */
329
+ export const runPermissions = async (
330
+ wireType: PikkuWiringTypes,
331
+ uid: string,
332
+ {
333
+ wireInheritedPermissions,
334
+ wirePermissions,
335
+ funcInheritedPermissions,
336
+ funcPermissions,
337
+ allServices,
338
+ data,
339
+ session,
340
+ }: {
341
+ wireInheritedPermissions?: PermissionMetadata[]
342
+ wirePermissions?: CorePermissionGroup | CorePikkuPermission[]
343
+ funcInheritedPermissions?: PermissionMetadata[]
344
+ funcPermissions?: CorePermissionGroup | CorePikkuPermission[]
345
+ allServices: CoreServices
346
+ data: any
347
+ session?: CoreUserSession
348
+ }
349
+ ) => {
350
+ // Combine all permissions: wireInheritedPermissions → wirePermissions → funcInheritedPermissions → funcPermissions
351
+ const allPermissions = combinePermissions(wireType, uid, {
352
+ wireInheritedPermissions,
353
+ wirePermissions,
354
+ funcInheritedPermissions,
355
+ funcPermissions,
356
+ })
357
+
358
+ // Check all combined permissions - at least one must pass if any exist
359
+ if (allPermissions.length > 0) {
360
+ let permissioned = false
361
+ for (const permission of allPermissions) {
236
362
  const result = await verifyPermissions(
237
- typeof permissions === 'function' ? { permissions } : permissions,
363
+ typeof permission === 'function' ? { permission } : permission,
238
364
  allServices,
239
365
  data,
240
366
  session
241
367
  )
242
368
  if (result) {
243
369
  permissioned = true
244
- break // At least one passed, we're good at this level
370
+ // Continue executing all permissions (don't break early)
245
371
  }
246
372
  }
247
373
  if (!permissioned) {
248
- allServices.logger.debug('Permission denied - function tag permissions')
249
- throw new ForbiddenError('Permission denied')
250
- }
251
- }
252
-
253
- // 4. Function permissions - must pass if defined
254
- if (funcPermissions) {
255
- permissioned = await verifyPermissions(
256
- funcPermissions,
257
- allServices,
258
- data,
259
- session
260
- )
261
- if (!permissioned) {
262
- allServices.logger.debug('Permission denied - function permissions')
374
+ allServices.logger.debug('Permission denied - combined permissions')
263
375
  throw new ForbiddenError('Permission denied')
264
376
  }
265
377
  }
@@ -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 { CLIMeta, CLIProgramState } from './wirings/cli/cli.types.js'
30
36
 
31
37
  interface PikkuState {
32
38
  function: {
@@ -69,6 +75,62 @@ interface PikkuState {
69
75
  prompts: Map<string, CoreMCPPrompt>
70
76
  promptsMeta: MCPPromptMeta
71
77
  }
78
+ cli: {
79
+ meta: CLIMeta | Record<string, any> // Backward compatible with old published CLI format
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
+ }
108
+ permissions: {
109
+ tagGroup: Record<string, CorePermissionGroup | CorePikkuPermission[]>
110
+ httpGroup: Record<string, CorePermissionGroup | CorePikkuPermission[]>
111
+ tagGroupMeta: Record<
112
+ string,
113
+ {
114
+ exportName: string | null
115
+ sourceFile: string
116
+ position: number
117
+ services: FunctionServicesMeta
118
+ permissionCount: number
119
+ isFactory: boolean
120
+ }
121
+ >
122
+ httpGroupMeta: Record<
123
+ string,
124
+ {
125
+ exportName: string | null
126
+ sourceFile: string
127
+ position: number
128
+ services: FunctionServicesMeta
129
+ permissionCount: number
130
+ isFactory: boolean
131
+ }
132
+ >
133
+ }
72
134
  misc: {
73
135
  errors: Map<PikkuError, ErrorDetails>
74
136
  schemas: Map<string, any>
@@ -89,7 +151,6 @@ export const resetPikkuState = () => {
89
151
  },
90
152
  http: {
91
153
  permissions: new Map(),
92
- middleware: new Map(),
93
154
  routes: new Map(),
94
155
  meta: {},
95
156
  },
@@ -113,6 +174,22 @@ export const resetPikkuState = () => {
113
174
  prompts: new Map(),
114
175
  promptsMeta: {} as MCPPromptMeta,
115
176
  },
177
+ cli: {
178
+ meta: { programs: {}, renderers: {} },
179
+ programs: {},
180
+ },
181
+ middleware: {
182
+ tagGroup: {},
183
+ httpGroup: {},
184
+ tagGroupMeta: {},
185
+ httpGroupMeta: {},
186
+ },
187
+ permissions: {
188
+ tagGroup: {},
189
+ httpGroup: {},
190
+ tagGroupMeta: {},
191
+ httpGroupMeta: {},
192
+ },
116
193
  misc: {
117
194
  errors: globalThis.pikkuState?.misc?.errors || new Map(),
118
195
  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}`