@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.
- package/CHANGELOG.md +6 -0
- package/dist/function/function-runner.d.ts +2 -1
- package/dist/function/function-runner.js +13 -3
- package/dist/function/functions.types.d.ts +3 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/middleware-runner.d.ts +62 -0
- package/dist/middleware-runner.js +91 -2
- package/dist/permissions.d.ts +40 -0
- package/dist/permissions.js +62 -0
- package/dist/pikku-state.d.ts +2 -0
- package/dist/pikku-state.js +2 -0
- package/dist/types/core.types.d.ts +11 -5
- package/dist/wirings/channel/channel-handler.js +1 -0
- package/dist/wirings/channel/local/local-channel-runner.js +23 -15
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +2 -2
- package/dist/wirings/http/http-runner.js +13 -3
- package/dist/wirings/http/http.types.d.ts +1 -0
- package/dist/wirings/mcp/mcp-runner.js +22 -14
- package/dist/wirings/mcp/mcp.types.d.ts +7 -3
- package/dist/wirings/queue/index.d.ts +1 -1
- package/dist/wirings/queue/index.js +1 -1
- package/dist/wirings/queue/queue-runner.d.ts +15 -1
- package/dist/wirings/queue/queue-runner.js +64 -16
- package/dist/wirings/queue/queue.types.d.ts +19 -2
- package/dist/wirings/rpc/rpc-runner.d.ts +5 -2
- package/dist/wirings/scheduler/scheduler-runner.js +49 -24
- package/dist/wirings/scheduler/scheduler.types.d.ts +17 -2
- package/package.json +1 -1
- package/src/function/function-runner.ts +21 -2
- package/src/function/functions.types.ts +3 -1
- package/src/index.ts +6 -1
- package/src/middleware-runner.ts +110 -2
- package/src/permissions.ts +71 -0
- package/src/pikku-state.ts +4 -0
- package/src/types/core.types.ts +11 -5
- package/src/wirings/channel/channel-handler.ts +1 -0
- package/src/wirings/channel/local/local-channel-runner.ts +33 -16
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +5 -2
- package/src/wirings/http/http-runner.ts +13 -3
- package/src/wirings/http/http.types.ts +1 -0
- package/src/wirings/mcp/mcp-runner.ts +32 -17
- package/src/wirings/mcp/mcp.types.ts +7 -0
- package/src/wirings/queue/index.ts +2 -0
- package/src/wirings/queue/queue-runner.ts +82 -19
- package/src/wirings/queue/queue.types.ts +20 -1
- package/src/wirings/scheduler/scheduler-runner.ts +73 -32
- package/src/wirings/scheduler/scheduler.types.ts +22 -1
- package/tsconfig.tsbuildinfo +1 -1
package/src/permissions.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/pikku-state.ts
CHANGED
|
@@ -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
|
}
|
package/src/types/core.types.ts
CHANGED
|
@@ -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
|
|
116
|
-
http
|
|
117
|
-
mcp
|
|
118
|
-
rpc
|
|
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
|
|
@@ -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 {
|
|
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
|
-
|
|
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 {
|
|
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
|
-
*
|
|
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
|
|
|
@@ -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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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 = {
|
|
@@ -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
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
}
|