@pikku/core 0.9.2 → 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 +14 -0
- package/dist/function/function-runner.d.ts +4 -3
- package/dist/function/function-runner.js +20 -7
- 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 +3 -2
- package/dist/pikku-state.js +2 -0
- package/dist/types/core.types.d.ts +23 -11
- 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 +14 -6
- package/dist/wirings/http/http.types.d.ts +1 -0
- package/dist/wirings/mcp/mcp-runner.js +25 -23
- 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 +65 -19
- package/dist/wirings/queue/queue.types.d.ts +19 -2
- package/dist/wirings/rpc/rpc-runner.d.ts +5 -2
- package/dist/wirings/rpc/rpc-runner.js +15 -3
- package/dist/wirings/rpc/rpc-types.d.ts +1 -1
- package/dist/wirings/scheduler/scheduler-runner.js +50 -27
- package/dist/wirings/scheduler/scheduler.types.d.ts +17 -2
- package/package.json +1 -1
- package/src/function/function-runner.ts +34 -7
- 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 +5 -2
- package/src/types/core.types.ts +30 -16
- 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 +14 -6
- package/src/wirings/http/http.types.ts +1 -0
- package/src/wirings/mcp/mcp-runner.ts +35 -26
- package/src/wirings/mcp/mcp.types.ts +7 -0
- package/src/wirings/queue/index.ts +2 -0
- package/src/wirings/queue/queue-runner.ts +83 -22
- package/src/wirings/queue/queue.types.ts +20 -1
- package/src/wirings/rpc/rpc-runner.ts +16 -4
- package/src/wirings/rpc/rpc-types.ts +1 -1
- package/src/wirings/scheduler/scheduler-runner.ts +74 -35
- package/src/wirings/scheduler/scheduler.types.ts +22 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -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) {
|
|
@@ -56,9 +57,7 @@ export const wireMCPResource = <
|
|
|
56
57
|
if (!mcpResourceMeta) {
|
|
57
58
|
throw new Error(`MCP resource metadata not found for '${mcpResource.uri}'`)
|
|
58
59
|
}
|
|
59
|
-
addFunction(mcpResourceMeta.pikkuFuncName,
|
|
60
|
-
func: mcpResource.func,
|
|
61
|
-
})
|
|
60
|
+
addFunction(mcpResourceMeta.pikkuFuncName, mcpResource.func)
|
|
62
61
|
const resources = pikkuState('mcp', 'resources')
|
|
63
62
|
if (resources.has(mcpResource.uri)) {
|
|
64
63
|
throw new Error(`MCP resource already exists: ${mcpResource.uri}`)
|
|
@@ -76,9 +75,7 @@ export const wireMCPTool = <
|
|
|
76
75
|
if (!mcpToolMeta) {
|
|
77
76
|
throw new Error(`MCP tool metadata not found for '${mcpTool.name}'`)
|
|
78
77
|
}
|
|
79
|
-
addFunction(mcpToolMeta.pikkuFuncName,
|
|
80
|
-
func: mcpTool.func,
|
|
81
|
-
})
|
|
78
|
+
addFunction(mcpToolMeta.pikkuFuncName, mcpTool.func as any)
|
|
82
79
|
const tools = pikkuState('mcp', 'tools')
|
|
83
80
|
if (tools.has(mcpTool.name)) {
|
|
84
81
|
throw new Error(`MCP tool already exists: ${mcpTool.name}`)
|
|
@@ -96,9 +93,7 @@ export const wireMCPPrompt = <
|
|
|
96
93
|
if (!mcpPromptMeta) {
|
|
97
94
|
throw new Error(`MCP prompt metadata not found for '${mcpPrompt.name}'`)
|
|
98
95
|
}
|
|
99
|
-
addFunction(mcpPromptMeta.pikkuFuncName,
|
|
100
|
-
func: mcpPrompt.func,
|
|
101
|
-
})
|
|
96
|
+
addFunction(mcpPromptMeta.pikkuFuncName, mcpPrompt.func as any)
|
|
102
97
|
const prompts = pikkuState('mcp', 'prompts')
|
|
103
98
|
if (prompts.has(mcpPrompt.name)) {
|
|
104
99
|
throw new Error(`MCP prompt already exists: ${mcpPrompt.name}`)
|
|
@@ -234,31 +229,45 @@ async function runMCPPikkuFunc(
|
|
|
234
229
|
|
|
235
230
|
singletonServices.logger.debug(`Running MCP ${type}: ${name}`)
|
|
236
231
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
+
}
|
|
245
250
|
return rpcService.injectRPCService({
|
|
246
251
|
...singletonServices,
|
|
247
|
-
...services,
|
|
248
252
|
mcp: interaction,
|
|
249
253
|
})
|
|
250
254
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
255
|
+
|
|
256
|
+
result = await runPikkuFunc(pikkuFuncName, {
|
|
257
|
+
getAllServices,
|
|
258
|
+
session,
|
|
259
|
+
data: request.params,
|
|
260
|
+
tags: mcp.tags,
|
|
254
261
|
})
|
|
255
262
|
}
|
|
256
263
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
+
)
|
|
262
271
|
|
|
263
272
|
return {
|
|
264
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
|
*/
|
|
@@ -37,9 +58,7 @@ export const wireQueueWorker = <
|
|
|
37
58
|
}
|
|
38
59
|
|
|
39
60
|
// Register the function with pikku
|
|
40
|
-
addFunction(processorMeta.pikkuFuncName,
|
|
41
|
-
func: queueWorker.func,
|
|
42
|
-
})
|
|
61
|
+
addFunction(processorMeta.pikkuFuncName, queueWorker.func)
|
|
43
62
|
|
|
44
63
|
// Store processor definition in state - runtime adapters will pick this up
|
|
45
64
|
const registrations = pikkuState('queue', 'registrations')
|
|
@@ -74,10 +93,12 @@ export async function runQueueJob({
|
|
|
74
93
|
singletonServices,
|
|
75
94
|
createSessionServices,
|
|
76
95
|
job,
|
|
96
|
+
updateProgress,
|
|
77
97
|
}: {
|
|
78
98
|
singletonServices: CoreServices
|
|
79
99
|
createSessionServices?: CreateSessionServices
|
|
80
100
|
job: QueueJob
|
|
101
|
+
updateProgress?: (progress: number | string | object) => Promise<void>
|
|
81
102
|
}): Promise<void> {
|
|
82
103
|
const logger = singletonServices.logger
|
|
83
104
|
|
|
@@ -87,26 +108,66 @@ export async function runQueueJob({
|
|
|
87
108
|
throw new Error(`Processor metadata not found for: ${job.queueName}`)
|
|
88
109
|
}
|
|
89
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
|
+
|
|
90
136
|
try {
|
|
91
137
|
logger.info(`Processing job ${job.id} in queue ${job.queueName}`)
|
|
92
138
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
109
169
|
)
|
|
170
|
+
|
|
110
171
|
return result
|
|
111
172
|
} catch (error: any) {
|
|
112
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
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { CoreServices } from '../../types/core.types.js'
|
|
2
2
|
import { runPikkuFunc } from '../../function/function-runner.js'
|
|
3
3
|
import { pikkuState } from '../../pikku-state.js'
|
|
4
|
-
import {
|
|
4
|
+
import { ForbiddenError } from '../../errors/errors.js'
|
|
5
5
|
|
|
6
6
|
// Type for the RPC service configuration
|
|
7
7
|
type RPCServiceConfig = {
|
|
8
8
|
coerceDataFromSchema: boolean
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
const
|
|
11
|
+
const getPikkuFunctionName = (rpcName: string): string => {
|
|
12
12
|
const rpc = pikkuState('rpc', 'meta')
|
|
13
13
|
const rpcMeta = rpc[rpcName]
|
|
14
14
|
if (!rpcMeta) {
|
|
@@ -26,14 +26,25 @@ class ContextAwareRPCService {
|
|
|
26
26
|
}
|
|
27
27
|
) {}
|
|
28
28
|
|
|
29
|
+
public async rpcExposed(funcName: string, data: any): Promise<any> {
|
|
30
|
+
const functionMeta = pikkuState('function', 'meta')[funcName]
|
|
31
|
+
if (!functionMeta) {
|
|
32
|
+
throw new Error(`Function not found: ${funcName}`)
|
|
33
|
+
}
|
|
34
|
+
if (!functionMeta.expose) {
|
|
35
|
+
throw new ForbiddenError()
|
|
36
|
+
}
|
|
37
|
+
return await this.rpc(funcName, data)
|
|
38
|
+
}
|
|
39
|
+
|
|
29
40
|
public async rpc<In = any, Out = any>(
|
|
30
41
|
funcName: string,
|
|
31
42
|
data: In
|
|
32
43
|
): Promise<Out> {
|
|
33
44
|
const session = await this.services.userSession?.get()
|
|
34
45
|
const rpcDepth = this.services.rpc?.depth || 0
|
|
35
|
-
const
|
|
36
|
-
return runPikkuFunc<In, Out>(
|
|
46
|
+
const pikkuFuncName = getPikkuFunctionName(funcName)
|
|
47
|
+
return runPikkuFunc<In, Out>(pikkuFuncName, {
|
|
37
48
|
getAllServices: () => {
|
|
38
49
|
this.services.rpc = this.services.rpc
|
|
39
50
|
? ({
|
|
@@ -72,6 +83,7 @@ export class PikkuRPCService {
|
|
|
72
83
|
depth,
|
|
73
84
|
global: false,
|
|
74
85
|
invoke: serviceRPC.rpc.bind(serviceRPC),
|
|
86
|
+
invokeExposed: serviceRPC.rpc.bind(serviceRPC),
|
|
75
87
|
} as any
|
|
76
88
|
return serviceCopy
|
|
77
89
|
}
|
|
@@ -4,13 +4,17 @@ import type {
|
|
|
4
4
|
CoreUserSession,
|
|
5
5
|
CreateSessionServices,
|
|
6
6
|
} from '../../types/core.types.js'
|
|
7
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
CoreScheduledTask,
|
|
9
|
+
PikkuScheduledTask,
|
|
10
|
+
} from './scheduler.types.js'
|
|
8
11
|
import type { CorePikkuFunctionSessionless } from '../../function/functions.types.js'
|
|
9
|
-
import { getErrorResponse } from '../../errors/error-handler.js'
|
|
12
|
+
import { getErrorResponse, PikkuError } from '../../errors/error-handler.js'
|
|
10
13
|
import { closeSessionServices } from '../../utils.js'
|
|
11
14
|
import { pikkuState } from '../../pikku-state.js'
|
|
12
15
|
import { addFunction, runPikkuFunc } from '../../function/function-runner.js'
|
|
13
16
|
import { rpcService } from '../rpc/rpc-runner.js'
|
|
17
|
+
import { addMiddlewareForTags, runMiddleware } from '../../middleware-runner.js'
|
|
14
18
|
|
|
15
19
|
export type RunScheduledTasksParams = {
|
|
16
20
|
name: string
|
|
@@ -33,9 +37,7 @@ export const wireScheduler = <
|
|
|
33
37
|
if (!taskMeta) {
|
|
34
38
|
throw new Error('Task metadata not found')
|
|
35
39
|
}
|
|
36
|
-
addFunction(taskMeta.pikkuFuncName,
|
|
37
|
-
func: scheduledTask.func,
|
|
38
|
-
})
|
|
40
|
+
addFunction(taskMeta.pikkuFuncName, scheduledTask.func)
|
|
39
41
|
|
|
40
42
|
const tasks = pikkuState('scheduler', 'tasks')
|
|
41
43
|
if (tasks.has(scheduledTask.name)) {
|
|
@@ -44,55 +46,92 @@ export const wireScheduler = <
|
|
|
44
46
|
tasks.set(scheduledTask.name, scheduledTask)
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
class ScheduledTaskNotFoundError extends
|
|
49
|
+
class ScheduledTaskNotFoundError extends PikkuError {
|
|
48
50
|
constructor(title: string) {
|
|
49
51
|
super(`Scheduled task not found: ${title}`)
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
54
|
|
|
55
|
+
class ScheduledTaskSkippedError extends PikkuError {
|
|
56
|
+
constructor(taskName: string, reason?: string) {
|
|
57
|
+
super(
|
|
58
|
+
`Scheduled task '${taskName}' was skipped${reason ? `: ${reason}` : ''}`
|
|
59
|
+
)
|
|
60
|
+
this.name = 'ScheduledTaskSkippedError'
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
53
64
|
export async function runScheduledTask({
|
|
54
65
|
name,
|
|
55
66
|
session,
|
|
56
67
|
singletonServices,
|
|
57
68
|
createSessionServices,
|
|
58
69
|
}: RunScheduledTasksParams): Promise<void> {
|
|
59
|
-
let sessionServices:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const meta = pikkuState('scheduler', 'meta')[name]
|
|
63
|
-
if (!task) {
|
|
64
|
-
throw new ScheduledTaskNotFoundError(`Scheduled task not found: ${name}`)
|
|
65
|
-
}
|
|
66
|
-
if (!meta) {
|
|
67
|
-
throw new ScheduledTaskNotFoundError(
|
|
68
|
-
`Scheduled task meta not found: ${name}`
|
|
69
|
-
)
|
|
70
|
-
}
|
|
70
|
+
let sessionServices: any
|
|
71
|
+
const task = pikkuState('scheduler', 'tasks').get(name)
|
|
72
|
+
const meta = pikkuState('scheduler', 'meta')[name]
|
|
71
73
|
|
|
74
|
+
if (!task) {
|
|
75
|
+
throw new ScheduledTaskNotFoundError(`Scheduled task not found: ${name}`)
|
|
76
|
+
}
|
|
77
|
+
if (!meta) {
|
|
78
|
+
throw new ScheduledTaskNotFoundError(
|
|
79
|
+
`Scheduled task meta not found: ${name}`
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Create the scheduled task interaction object
|
|
84
|
+
const scheduledTask: PikkuScheduledTask = {
|
|
85
|
+
name,
|
|
86
|
+
schedule: task.schedule,
|
|
87
|
+
executionTime: new Date(),
|
|
88
|
+
skip: (reason?: string) => {
|
|
89
|
+
throw new ScheduledTaskSkippedError(name, reason)
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try {
|
|
72
94
|
singletonServices.logger.info(
|
|
73
95
|
`Running schedule task: ${name} | schedule: ${task.schedule}`
|
|
74
96
|
)
|
|
75
97
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
98
|
+
let result: any
|
|
99
|
+
|
|
100
|
+
// Main scheduled task execution logic wrapped for middleware handling
|
|
101
|
+
const runMain = async () => {
|
|
102
|
+
const getAllServices = async () => {
|
|
103
|
+
if (createSessionServices) {
|
|
104
|
+
const services = await createSessionServices(
|
|
105
|
+
singletonServices,
|
|
106
|
+
{ scheduledTask },
|
|
107
|
+
session
|
|
108
|
+
)
|
|
109
|
+
sessionServices = services
|
|
110
|
+
return rpcService.injectRPCService({
|
|
111
|
+
...singletonServices,
|
|
112
|
+
...services,
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
return singletonServices
|
|
87
116
|
}
|
|
88
|
-
|
|
117
|
+
|
|
118
|
+
result = await runPikkuFunc(meta.pikkuFuncName, {
|
|
119
|
+
getAllServices,
|
|
120
|
+
session,
|
|
121
|
+
data: undefined,
|
|
122
|
+
tags: task.tags,
|
|
123
|
+
})
|
|
89
124
|
}
|
|
90
125
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
126
|
+
// Get middleware for tags and run middleware
|
|
127
|
+
await runMiddleware(
|
|
128
|
+
singletonServices,
|
|
129
|
+
{ scheduledTask },
|
|
130
|
+
addMiddlewareForTags(task.middleware, task.tags),
|
|
131
|
+
runMain
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
return result
|
|
96
135
|
} catch (e: any) {
|
|
97
136
|
const errorResponse = getErrorResponse(e)
|
|
98
137
|
if (errorResponse != null) {
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
PikkuDocs,
|
|
3
|
+
CoreUserSession,
|
|
4
|
+
CorePikkuMiddleware,
|
|
5
|
+
} from '../../types/core.types.js'
|
|
2
6
|
import { CorePikkuFunctionSessionless } from '../../function/functions.types.js'
|
|
3
7
|
|
|
4
8
|
/**
|
|
@@ -22,10 +26,27 @@ export type ScheduledTasksMeta<UserSession extends CoreUserSession = any> =
|
|
|
22
26
|
*/
|
|
23
27
|
export type CoreScheduledTask<
|
|
24
28
|
PikkuFunction = CorePikkuFunctionSessionless<void, void>,
|
|
29
|
+
PikkuMiddleware = CorePikkuMiddleware<any>,
|
|
25
30
|
> = {
|
|
26
31
|
name: string
|
|
27
32
|
schedule: string
|
|
28
33
|
func: PikkuFunction
|
|
29
34
|
docs?: PikkuDocs
|
|
30
35
|
tags?: string[]
|
|
36
|
+
middleware?: PikkuMiddleware[]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Represents a scheduled task interaction object for middleware
|
|
41
|
+
* Provides information about the current scheduled task execution
|
|
42
|
+
*/
|
|
43
|
+
export interface PikkuScheduledTask {
|
|
44
|
+
/** The name of the scheduled task being executed */
|
|
45
|
+
name: string
|
|
46
|
+
/** The cron schedule expression */
|
|
47
|
+
schedule: string
|
|
48
|
+
/** Current execution timestamp */
|
|
49
|
+
executionTime: Date
|
|
50
|
+
/** Skip the current task execution */
|
|
51
|
+
skip: (reason?: string) => void
|
|
31
52
|
}
|