@pikku/core 0.9.3 → 0.9.4

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 (49) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/function/function-runner.d.ts +2 -1
  3. package/dist/function/function-runner.js +13 -3
  4. package/dist/function/functions.types.d.ts +3 -2
  5. package/dist/index.d.ts +2 -1
  6. package/dist/index.js +2 -1
  7. package/dist/middleware-runner.d.ts +62 -0
  8. package/dist/middleware-runner.js +91 -2
  9. package/dist/permissions.d.ts +40 -0
  10. package/dist/permissions.js +62 -0
  11. package/dist/pikku-state.d.ts +2 -0
  12. package/dist/pikku-state.js +2 -0
  13. package/dist/types/core.types.d.ts +11 -5
  14. package/dist/wirings/channel/channel-handler.js +1 -0
  15. package/dist/wirings/channel/local/local-channel-runner.js +23 -15
  16. package/dist/wirings/channel/serverless/serverless-channel-runner.js +2 -2
  17. package/dist/wirings/http/http-runner.js +13 -3
  18. package/dist/wirings/http/http.types.d.ts +1 -0
  19. package/dist/wirings/mcp/mcp-runner.js +22 -14
  20. package/dist/wirings/mcp/mcp.types.d.ts +7 -3
  21. package/dist/wirings/queue/index.d.ts +1 -1
  22. package/dist/wirings/queue/index.js +1 -1
  23. package/dist/wirings/queue/queue-runner.d.ts +15 -1
  24. package/dist/wirings/queue/queue-runner.js +64 -16
  25. package/dist/wirings/queue/queue.types.d.ts +19 -2
  26. package/dist/wirings/rpc/rpc-runner.d.ts +5 -2
  27. package/dist/wirings/scheduler/scheduler-runner.js +49 -24
  28. package/dist/wirings/scheduler/scheduler.types.d.ts +17 -2
  29. package/package.json +1 -1
  30. package/src/function/function-runner.ts +21 -2
  31. package/src/function/functions.types.ts +3 -1
  32. package/src/index.ts +6 -1
  33. package/src/middleware-runner.ts +110 -2
  34. package/src/permissions.ts +71 -0
  35. package/src/pikku-state.ts +4 -0
  36. package/src/types/core.types.ts +11 -5
  37. package/src/wirings/channel/channel-handler.ts +1 -0
  38. package/src/wirings/channel/local/local-channel-runner.ts +33 -16
  39. package/src/wirings/channel/serverless/serverless-channel-runner.ts +5 -2
  40. package/src/wirings/http/http-runner.ts +13 -3
  41. package/src/wirings/http/http.types.ts +1 -0
  42. package/src/wirings/mcp/mcp-runner.ts +32 -17
  43. package/src/wirings/mcp/mcp.types.ts +7 -0
  44. package/src/wirings/queue/index.ts +2 -0
  45. package/src/wirings/queue/queue-runner.ts +82 -19
  46. package/src/wirings/queue/queue.types.ts +20 -1
  47. package/src/wirings/scheduler/scheduler-runner.ts +73 -32
  48. package/src/wirings/scheduler/scheduler.types.ts +22 -1
  49. package/tsconfig.tsbuildinfo +1 -1
@@ -1,5 +1,6 @@
1
1
  import { CoreServices, CoreUserSession } from './types/core.types.js'
2
2
  import { CorePermissionGroup } from './function/functions.types.js'
3
+ import { pikkuState } from './pikku-state.js'
3
4
 
4
5
  /**
5
6
  * 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.
@@ -41,3 +42,73 @@ export const verifyPermissions = async (
41
42
  }
42
43
  return false
43
44
  }
45
+
46
+ /**
47
+ * Adds global permissions for a specific tag.
48
+ *
49
+ * This function allows you to register permissions that will be applied to
50
+ * any wiring (HTTP, Channel, Queue, Scheduler, MCP) that includes the matching tag.
51
+ *
52
+ * @param {string} tag - The tag that the permissions should apply to.
53
+ * @param {any[]} permissions - The permissions array to apply for the specified tag.
54
+ *
55
+ * @throws {Error} If permissions for the tag already exist.
56
+ *
57
+ * @example
58
+ * ```typescript
59
+ * // Add admin permissions for admin endpoints
60
+ * addPermission('admin', [adminPermission])
61
+ *
62
+ * // Add authentication permissions for auth endpoints
63
+ * addPermission('auth', [authPermission])
64
+ *
65
+ * // Add read permissions for all API endpoints
66
+ * addPermission('api', [readPermission])
67
+ * ```
68
+ */
69
+ export const addPermission = (tag: string, permissions: any[]) => {
70
+ const permissionsStore = pikkuState('misc', 'permissions')
71
+
72
+ // Check if tag already exists
73
+ if (permissionsStore[tag]) {
74
+ throw new Error(
75
+ `Permissions for tag '${tag}' already exist. Use a different tag or remove the existing permissions first.`
76
+ )
77
+ }
78
+
79
+ permissionsStore[tag] = permissions
80
+ }
81
+
82
+ /**
83
+ * Retrieves permissions for a given set of tags.
84
+ *
85
+ * This function looks up all permissions registered for any of the provided tags
86
+ * and returns them as a flattened array.
87
+ *
88
+ * @param {string[]} tags - Array of tags to look up permissions for.
89
+ * @returns {any[]} Array of permission functions that apply to the given tags.
90
+ *
91
+ * @example
92
+ * ```typescript
93
+ * // Get all permissions for tags 'api' and 'auth'
94
+ * const permissions = getPermissionsForTags(['api', 'auth'])
95
+ * ```
96
+ */
97
+ export const getPermissionsForTags = (tags?: string[]): any[] => {
98
+ if (!tags || tags.length === 0) {
99
+ return []
100
+ }
101
+
102
+ const permissionsStore = pikkuState('misc', 'permissions')
103
+ const applicablePermissions: any[] = []
104
+
105
+ // Collect permissions for all matching tags
106
+ for (const tag of new Set(tags)) {
107
+ const tagPermissions = permissionsStore[tag]
108
+ if (tagPermissions) {
109
+ applicablePermissions.push(...tagPermissions)
110
+ }
111
+ }
112
+
113
+ return applicablePermissions
114
+ }
@@ -67,6 +67,8 @@ interface PikkuState {
67
67
  misc: {
68
68
  errors: Map<PikkuError, ErrorDetails>
69
69
  schemas: Map<string, any>
70
+ middleware: Record<string, CorePikkuMiddleware[]>
71
+ permissions: Record<string, any[]>
70
72
  }
71
73
  }
72
74
 
@@ -108,6 +110,8 @@ export const resetPikkuState = () => {
108
110
  misc: {
109
111
  errors: globalThis.pikkuState?.misc?.errors || new Map(),
110
112
  schemas: globalThis.pikkuState?.misc?.schema || new Map(),
113
+ middleware: globalThis.pikkuState?.misc?.middleware || {},
114
+ permissions: globalThis.pikkuState?.misc?.permissions || {},
111
115
  },
112
116
  } as PikkuState
113
117
  }
@@ -6,6 +6,8 @@ import { UserSessionService } from '../services/user-session-service.js'
6
6
  import { PikkuChannel } from '../wirings/channel/channel.types.js'
7
7
  import { PikkuRPC } from '../wirings/rpc/rpc-types.js'
8
8
  import { PikkuMCP } from '../wirings/mcp/mcp.types.js'
9
+ import { PikkuScheduledTask } from '../wirings/scheduler/scheduler.types.js'
10
+ import { PikkuQueue } from '../wirings/queue/queue.types.js'
9
11
 
10
12
  export enum PikkuWiringTypes {
11
13
  http = 'http',
@@ -112,11 +114,15 @@ export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
112
114
  /**
113
115
  * Represents different forms of interaction within Pikku and the outside world.
114
116
  */
115
- export interface PikkuInteraction {
116
- http?: PikkuHTTP
117
- mcp?: PikkuMCP
118
- rpc?: PikkuRPC
119
- }
117
+ export type PikkuInteraction = Partial<{
118
+ http: PikkuHTTP
119
+ mcp: PikkuMCP
120
+ rpc: PikkuRPC
121
+ channel: PikkuChannel<unknown, unknown>
122
+ scheduledTask: PikkuScheduledTask
123
+ queue: PikkuQueue
124
+ s
125
+ }>
120
126
 
121
127
  /**
122
128
  * A function that can wrap an interaction and be called before or after
@@ -107,6 +107,7 @@ export const processMessageHandlers = (
107
107
  session,
108
108
  permissions,
109
109
  middleware,
110
+ tags: channelConfig.tags,
110
111
  })
111
112
  }
112
113
 
@@ -10,7 +10,10 @@ import {
10
10
  import { PikkuLocalChannelHandler } from './local-channel-handler.js'
11
11
  import { SessionServices } from '../../../types/core.types.js'
12
12
  import { handleHTTPError } from '../../../handle-error.js'
13
- import { runMiddleware } from '../../../middleware-runner.js'
13
+ import {
14
+ addMiddlewareForTags,
15
+ runMiddleware,
16
+ } from '../../../middleware-runner.js'
14
17
  import { PikkuUserSessionService } from '../../../services/user-session-service.js'
15
18
  import { PikkuHTTP } from '../../http/http.types.js'
16
19
  import { runPikkuFuncDirectly } from '../../../function/function-runner.js'
@@ -42,21 +45,35 @@ export const runLocalChannel = async ({
42
45
  route = http?.request?.path()
43
46
  }
44
47
 
48
+ let openingData, channelConfig, meta
49
+ try {
50
+ ;({ openingData, channelConfig, meta } = await openChannel({
51
+ channelId,
52
+ createSessionServices,
53
+ respondWith404,
54
+ request,
55
+ response,
56
+ route,
57
+ singletonServices,
58
+ skipUserSession,
59
+ coerceDataFromSchema,
60
+ userSession,
61
+ }))
62
+ } catch (e) {
63
+ handleHTTPError(
64
+ e,
65
+ http,
66
+ channelId,
67
+ singletonServices.logger,
68
+ logWarningsForStatusCodes,
69
+ respondWith404,
70
+ bubbleErrors
71
+ )
72
+ return
73
+ }
74
+
45
75
  const main = async () => {
46
76
  try {
47
- const { openingData, channelConfig, meta } = await openChannel({
48
- channelId,
49
- createSessionServices,
50
- respondWith404,
51
- request,
52
- response,
53
- route,
54
- singletonServices,
55
- skipUserSession,
56
- coerceDataFromSchema,
57
- userSession,
58
- })
59
-
60
77
  channelHandler = new PikkuLocalChannelHandler(
61
78
  channelId,
62
79
  channelConfig.name,
@@ -67,7 +84,7 @@ export const runLocalChannel = async ({
67
84
  if (createSessionServices) {
68
85
  sessionServices = await createSessionServices(
69
86
  singletonServices,
70
- { http },
87
+ { http, channel },
71
88
  session
72
89
  )
73
90
  }
@@ -132,7 +149,7 @@ export const runLocalChannel = async ({
132
149
  userSession,
133
150
  },
134
151
  { http },
135
- route.middleware || [],
152
+ addMiddlewareForTags(channelConfig.middleware, channelConfig.tags),
136
153
  main
137
154
  )
138
155
 
@@ -12,7 +12,10 @@ import { createHTTPInteraction } from '../../http/http-runner.js'
12
12
  import { ChannelStore } from '../channel-store.js'
13
13
  import { handleHTTPError } from '../../../handle-error.js'
14
14
  import { PikkuUserSessionService } from '../../../services/user-session-service.js'
15
- import { runMiddleware } from '../../../middleware-runner.js'
15
+ import {
16
+ addMiddlewareForTags,
17
+ runMiddleware,
18
+ } from '../../../middleware-runner.js'
16
19
  import { pikkuState } from '../../../pikku-state.js'
17
20
  import { PikkuFetchHTTPRequest } from '../../http/pikku-fetch-http-request.js'
18
21
  import { PikkuHTTP } from '../../http/http.types.js'
@@ -146,7 +149,7 @@ export const runChannelConnect = async ({
146
149
  userSession,
147
150
  },
148
151
  { http },
149
- channelConfig.middleware || [],
152
+ addMiddlewareForTags(channelConfig.middleware, channelConfig.tags),
150
153
  main
151
154
  )
152
155
  }
@@ -24,7 +24,7 @@ import {
24
24
  PikkuUserSessionService,
25
25
  UserSessionService,
26
26
  } from '../../services/user-session-service.js'
27
- import { runMiddleware } from '../../middleware-runner.js'
27
+ import { addMiddlewareForTags, runMiddleware } from '../../middleware-runner.js'
28
28
  import { handleHTTPError } from '../../handle-error.js'
29
29
  import { pikkuState } from '../../pikku-state.js'
30
30
  import { PikkuFetchHTTPResponse } from './pikku-fetch-http-response.js'
@@ -213,7 +213,16 @@ export const createHTTPInteraction = (
213
213
  /**
214
214
  * Validates the input data and executes the route handler with associated middleware.
215
215
  *
216
- * This function is the central execution point for a route. It performs these steps:
216
+ * NOTE: HTTP wiring handles middleware differently from other wirings (RPC, MCP, Queue, etc.)
217
+ * because HTTP needs to:
218
+ * 1. Check session early for performance (before expensive body parsing)
219
+ * 2. Handle HTTP-specific concerns (headers, cookies, SSE setup)
220
+ * 3. Process middleware that may set up authentication/session state
221
+ *
222
+ * Other wirings (RPC/MCP/Queue/Scheduler) simply pass middleware/permissions/auth
223
+ * directly to runPikkuFunc without processing them.
224
+ *
225
+ * This function performs these steps:
217
226
  * 1. Sets URL parameters on the request.
218
227
  * 2. Validates the user session if required.
219
228
  * 3. Creates session-specific services.
@@ -344,6 +353,7 @@ const executeRouteWithMiddleware = async (
344
353
  data,
345
354
  permissions: route.permissions,
346
355
  coerceDataFromSchema: options.coerceDataFromSchema,
356
+ tags: route.tags,
347
357
  })
348
358
 
349
359
  // Respond with either a binary or JSON response based on configuration
@@ -362,7 +372,7 @@ const executeRouteWithMiddleware = async (
362
372
  await runMiddleware(
363
373
  { ...singletonServices, userSession },
364
374
  { http },
365
- middleware,
375
+ addMiddlewareForTags(middleware, route.tags),
366
376
  runMain
367
377
  )
368
378
 
@@ -72,6 +72,7 @@ export type CoreHTTPFunction = {
72
72
  eventChannel?: false
73
73
  returnsJSON?: false
74
74
  timeout?: number
75
+ tags?: string[]
75
76
  docs?: Partial<{
76
77
  description: string
77
78
  response: string
@@ -20,6 +20,7 @@ import { pikkuState } from '../../pikku-state.js'
20
20
  import { addFunction, runPikkuFunc } from '../../function/function-runner.js'
21
21
  import { rpcService } from '../rpc/rpc-runner.js'
22
22
  import { BadRequestError, NotFoundError } from '../../errors/errors.js'
23
+ import { addMiddlewareForTags, runMiddleware } from '../../middleware-runner.js'
23
24
 
24
25
  export class MCPError extends Error {
25
26
  constructor(public readonly error: JsonRpcErrorResponse) {
@@ -228,31 +229,45 @@ async function runMCPPikkuFunc(
228
229
 
229
230
  singletonServices.logger.debug(`Running MCP ${type}: ${name}`)
230
231
 
231
- const getAllServices = async () => {
232
- if (createSessionServices) {
233
- const services = await createSessionServices(
234
- singletonServices,
235
- { mcp: interaction },
236
- session
237
- )
238
- sessionServices = services
232
+ let result: any
233
+
234
+ // Main MCP execution logic wrapped for middleware handling
235
+ const runMain = async () => {
236
+ const getAllServices = async () => {
237
+ if (createSessionServices) {
238
+ const services = await createSessionServices(
239
+ singletonServices,
240
+ { mcp: interaction },
241
+ session
242
+ )
243
+ sessionServices = services
244
+ return rpcService.injectRPCService({
245
+ ...singletonServices,
246
+ ...services,
247
+ mcp: interaction,
248
+ })
249
+ }
239
250
  return rpcService.injectRPCService({
240
251
  ...singletonServices,
241
- ...services,
242
252
  mcp: interaction,
243
253
  })
244
254
  }
245
- return rpcService.injectRPCService({
246
- ...singletonServices,
247
- mcp: interaction,
255
+
256
+ result = await runPikkuFunc(pikkuFuncName, {
257
+ getAllServices,
258
+ session,
259
+ data: request.params,
260
+ tags: mcp.tags,
248
261
  })
249
262
  }
250
263
 
251
- const result = await runPikkuFunc(pikkuFuncName, {
252
- getAllServices,
253
- session,
254
- data: request.params,
255
- })
264
+ // Get middleware for tags and run middleware
265
+ await runMiddleware(
266
+ singletonServices,
267
+ { mcp: interaction },
268
+ addMiddlewareForTags(mcp.middleware, mcp.tags),
269
+ runMain
270
+ )
256
271
 
257
272
  return {
258
273
  id: request.id,
@@ -1,4 +1,5 @@
1
1
  import { CorePikkuFunctionSessionless } from '../../function/functions.types.js'
2
+ import { CorePikkuMiddleware } from '../../types/core.types.js'
2
3
 
3
4
  export type PikkuMCP<Tools extends string = any> = {
4
5
  // elicitInput: <Input>(message: string) => Promise<{ action: string, content: Input }>
@@ -55,6 +56,7 @@ export type MCPPromptMeta = Record<
55
56
  */
56
57
  export type CoreMCPResource<
57
58
  PikkuFunction = CorePikkuFunctionSessionless<any, any>,
59
+ PikkuMiddleware = CorePikkuMiddleware<any>,
58
60
  > = {
59
61
  uri: string
60
62
  title: string
@@ -64,6 +66,7 @@ export type CoreMCPResource<
64
66
  streaming?: boolean
65
67
  func: PikkuFunction
66
68
  tags?: string[]
69
+ middleware?: PikkuMiddleware[]
67
70
  }
68
71
 
69
72
  /**
@@ -71,6 +74,7 @@ export type CoreMCPResource<
71
74
  */
72
75
  export type CoreMCPTool<
73
76
  PikkuFunction = CorePikkuFunctionSessionless<any, any>,
77
+ PikkuMiddleware = CorePikkuMiddleware<any>,
74
78
  > = {
75
79
  name: string
76
80
  title?: string
@@ -79,6 +83,7 @@ export type CoreMCPTool<
79
83
  func: PikkuFunction
80
84
  tags?: string[]
81
85
  streaming?: boolean
86
+ middleware?: PikkuMiddleware[]
82
87
  }
83
88
 
84
89
  /**
@@ -86,11 +91,13 @@ export type CoreMCPTool<
86
91
  */
87
92
  export type CoreMCPPrompt<
88
93
  PikkuFunction = CorePikkuFunctionSessionless<any, MCPPromptResponse>,
94
+ PikkuMiddleware = CorePikkuMiddleware<any>,
89
95
  > = {
90
96
  name: string
91
97
  description?: string
92
98
  func: PikkuFunction
93
99
  tags?: string[]
100
+ middleware?: PikkuMiddleware[]
94
101
  }
95
102
 
96
103
  export type JsonRpcRequest = {
@@ -7,6 +7,8 @@ export {
7
7
  runQueueJob,
8
8
  getQueueWorkers,
9
9
  removeQueueWorker,
10
+ QueueJobDiscardedError,
11
+ QueueJobFailedError,
10
12
  } from './queue-runner.js'
11
13
 
12
14
  // Configuration validation
@@ -1,19 +1,40 @@
1
1
  import type { CoreServices } from '../../types/core.types.js'
2
- import type { CoreQueueWorker, QueueJob } from './queue.types.js'
2
+ import type { CoreQueueWorker, QueueJob, PikkuQueue } from './queue.types.js'
3
3
  import type { CorePikkuFunctionSessionless } from '../../function/functions.types.js'
4
- import { getErrorResponse } from '../../errors/error-handler.js'
4
+ import { getErrorResponse, PikkuError } from '../../errors/error-handler.js'
5
5
  import { pikkuState } from '../../pikku-state.js'
6
6
  import { addFunction, runPikkuFunc } from '../../function/function-runner.js'
7
7
  import { CreateSessionServices } from '../../types/core.types.js'
8
+ import { addMiddlewareForTags, runMiddleware } from '../../middleware-runner.js'
8
9
 
9
10
  /**
10
11
  * Error class for queue processor not found
11
12
  */
12
- class QueueWorkerNotFoundError extends Error {
13
+ class QueueWorkerNotFoundError extends PikkuError {
13
14
  constructor(name: string) {
14
15
  super(`Queue processor not found: ${name}`)
15
16
  }
16
17
  }
18
+
19
+ /**
20
+ * Error class for when a queue job is explicitly failed
21
+ */
22
+ export class QueueJobFailedError extends PikkuError {
23
+ constructor(jobId: string, reason?: string) {
24
+ super(`Queue job ${jobId} failed${reason ? `: ${reason}` : ''}`)
25
+ this.name = 'QueueJobFailedError'
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Error class for when a queue job is explicitly discarded
31
+ */
32
+ export class QueueJobDiscardedError extends PikkuError {
33
+ constructor(jobId: string, reason?: string) {
34
+ super(`Queue job ${jobId} discarded${reason ? `: ${reason}` : ''}`)
35
+ this.name = 'QueueJobDiscardedError'
36
+ }
37
+ }
17
38
  /**
18
39
  * Add a queue processor to the system
19
40
  */
@@ -72,10 +93,12 @@ export async function runQueueJob({
72
93
  singletonServices,
73
94
  createSessionServices,
74
95
  job,
96
+ updateProgress,
75
97
  }: {
76
98
  singletonServices: CoreServices
77
99
  createSessionServices?: CreateSessionServices
78
100
  job: QueueJob
101
+ updateProgress?: (progress: number | string | object) => Promise<void>
79
102
  }): Promise<void> {
80
103
  const logger = singletonServices.logger
81
104
 
@@ -85,26 +108,66 @@ export async function runQueueJob({
85
108
  throw new Error(`Processor metadata not found for: ${job.queueName}`)
86
109
  }
87
110
 
111
+ // Get the queue worker registration to access middleware
112
+ const registrations = pikkuState('queue', 'registrations')
113
+ const queueWorker = registrations.get(job.queueName)
114
+ if (!queueWorker) {
115
+ throw new Error(`Queue worker registration not found for: ${job.queueName}`)
116
+ }
117
+
118
+ // Create the queue interaction object
119
+ const queue: PikkuQueue = {
120
+ queueName: job.queueName,
121
+ jobId: job.id,
122
+ updateProgress:
123
+ updateProgress ||
124
+ (async (progress: number | string | object) => {
125
+ logger.info(`Job ${job.id} progress: ${progress}`)
126
+ // Default implementation - just log the progress
127
+ }),
128
+ fail: async (reason?: string) => {
129
+ throw new QueueJobFailedError(job.id, reason)
130
+ },
131
+ discard: async (reason?: string) => {
132
+ throw new QueueJobDiscardedError(job.id, reason)
133
+ },
134
+ }
135
+
88
136
  try {
89
137
  logger.info(`Processing job ${job.id} in queue ${job.queueName}`)
90
138
 
91
- // Use provided singleton services
92
- const getAllServices = () => ({
93
- ...singletonServices,
94
- ...(createSessionServices
95
- ? createSessionServices(singletonServices, {}, undefined)
96
- : {}),
97
- })
98
-
99
- // Execute the pikku function with the job data
100
- const result = await runPikkuFunc(processorMeta.pikkuFuncName, {
101
- getAllServices,
102
- data: job.data,
103
- })
104
-
105
- logger.debug(
106
- `Successfully processed job ${job.id} in queue ${job.queueName}`
139
+ let result: any
140
+
141
+ // Main job execution logic wrapped for middleware handling
142
+ const runMain = async () => {
143
+ // Use provided singleton services
144
+ const getAllServices = () => ({
145
+ ...singletonServices,
146
+ ...(createSessionServices
147
+ ? createSessionServices(singletonServices, { queue }, undefined)
148
+ : {}),
149
+ })
150
+
151
+ // Execute the pikku function with the job data
152
+ result = await runPikkuFunc(processorMeta.pikkuFuncName, {
153
+ getAllServices,
154
+ data: job.data,
155
+ tags: queueWorker.tags,
156
+ })
157
+
158
+ logger.debug(
159
+ `Successfully processed job ${job.id} in queue ${job.queueName}`
160
+ )
161
+ }
162
+
163
+ // Get middleware for tags and combine with queue-specific middleware
164
+ await runMiddleware(
165
+ singletonServices,
166
+ { queue },
167
+ addMiddlewareForTags(queueWorker.middleware, queueWorker.tags),
168
+ runMain
107
169
  )
170
+
108
171
  return result
109
172
  } catch (error: any) {
110
173
  logger.error(
@@ -1,4 +1,4 @@
1
- import { PikkuDocs } from '../../types/core.types.js'
1
+ import { PikkuDocs, CorePikkuMiddleware } from '../../types/core.types.js'
2
2
  import { CorePikkuFunctionSessionless } from '../../function/functions.types.js'
3
3
  import { QueueConfigMapping } from './validate-worker-config.js'
4
4
 
@@ -166,6 +166,7 @@ export type queueWorkersMeta = Record<
166
166
  */
167
167
  export type CoreQueueWorker<
168
168
  PikkuFunction = CorePikkuFunctionSessionless<any, any>,
169
+ PikkuMiddleware = CorePikkuMiddleware<any>,
169
170
  > = {
170
171
  queueName: string
171
172
  func: PikkuFunction
@@ -173,4 +174,22 @@ export type CoreQueueWorker<
173
174
  docs?: PikkuDocs
174
175
  session?: undefined
175
176
  tags?: string[]
177
+ middleware?: PikkuMiddleware[]
178
+ }
179
+
180
+ /**
181
+ * Represents a queue interaction object for middleware
182
+ * Provides information and actions for the current queue job execution
183
+ */
184
+ export interface PikkuQueue {
185
+ /** The name of the queue being processed */
186
+ queueName: string
187
+ /** The current job ID */
188
+ jobId: string
189
+ /** Update job progress (0-100 or custom value) */
190
+ updateProgress: (progress: number | string | object) => Promise<void>
191
+ /** Fail the current job with optional reason */
192
+ fail: (reason?: string) => Promise<void>
193
+ /** Discard/delete the job without retrying */
194
+ discard: (reason?: string) => Promise<void>
176
195
  }