@pikku/core 0.9.3 → 0.9.5
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 +25 -0
- package/dist/function/function-runner.d.ts +2 -1
- package/dist/function/function-runner.js +22 -15
- 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 +70 -0
- package/dist/middleware-runner.js +105 -2
- package/dist/permissions.d.ts +57 -1
- package/dist/permissions.js +122 -0
- package/dist/pikku-state.d.ts +3 -1
- package/dist/pikku-state.js +3 -1
- package/dist/types/core.types.d.ts +10 -5
- package/dist/wirings/channel/channel-handler.js +1 -0
- package/dist/wirings/channel/local/local-channel-runner.js +26 -15
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +5 -2
- package/dist/wirings/http/http-runner.js +20 -3
- package/dist/wirings/http/http.types.d.ts +5 -4
- package/dist/wirings/mcp/mcp-runner.js +27 -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 +71 -16
- package/dist/wirings/queue/queue.types.d.ts +19 -2
- package/dist/wirings/rpc/rpc-runner.d.ts +4 -2
- package/dist/wirings/scheduler/scheduler-runner.js +54 -24
- package/dist/wirings/scheduler/scheduler.types.d.ts +17 -2
- package/package.json +1 -1
- package/src/function/function-runner.test.ts +450 -0
- package/src/function/function-runner.ts +25 -26
- package/src/function/functions.types.ts +3 -1
- package/src/index.ts +10 -1
- package/src/middleware-runner.test.ts +329 -0
- package/src/middleware-runner.ts +131 -2
- package/src/permissions.test.ts +403 -42
- package/src/permissions.ts +182 -1
- package/src/pikku-state.ts +10 -2
- package/src/types/core.types.ts +10 -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 +21 -3
- package/src/wirings/http/http.types.ts +5 -4
- package/src/wirings/mcp/mcp-runner.ts +37 -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 +92 -19
- package/src/wirings/queue/queue.types.ts +20 -1
- package/src/wirings/scheduler/scheduler-runner.ts +80 -32
- package/src/wirings/scheduler/scheduler.types.ts +22 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -4,7 +4,7 @@ import { closeSessionServices } from '../../../utils.js';
|
|
|
4
4
|
import { processMessageHandlers } from '../channel-handler.js';
|
|
5
5
|
import { PikkuLocalChannelHandler } from './local-channel-handler.js';
|
|
6
6
|
import { handleHTTPError } from '../../../handle-error.js';
|
|
7
|
-
import { runMiddleware } from '../../../middleware-runner.js';
|
|
7
|
+
import { combineMiddleware, runMiddleware } from '../../../middleware-runner.js';
|
|
8
8
|
import { PikkuUserSessionService } from '../../../services/user-session-service.js';
|
|
9
9
|
import { runPikkuFuncDirectly } from '../../../function/function-runner.js';
|
|
10
10
|
import { rpcService } from '../../rpc/rpc-runner.js';
|
|
@@ -17,25 +17,33 @@ export const runLocalChannel = async ({ singletonServices, channelId, request, r
|
|
|
17
17
|
http = createHTTPInteraction(request, response);
|
|
18
18
|
route = http?.request?.path();
|
|
19
19
|
}
|
|
20
|
+
let openingData, channelConfig, meta;
|
|
21
|
+
try {
|
|
22
|
+
;
|
|
23
|
+
({ openingData, channelConfig, meta } = await openChannel({
|
|
24
|
+
channelId,
|
|
25
|
+
createSessionServices,
|
|
26
|
+
respondWith404,
|
|
27
|
+
request,
|
|
28
|
+
response,
|
|
29
|
+
route,
|
|
30
|
+
singletonServices,
|
|
31
|
+
skipUserSession,
|
|
32
|
+
coerceDataFromSchema,
|
|
33
|
+
userSession,
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
handleHTTPError(e, http, channelId, singletonServices.logger, logWarningsForStatusCodes, respondWith404, bubbleErrors);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
20
40
|
const main = async () => {
|
|
21
41
|
try {
|
|
22
|
-
const { openingData, channelConfig, meta } = await openChannel({
|
|
23
|
-
channelId,
|
|
24
|
-
createSessionServices,
|
|
25
|
-
respondWith404,
|
|
26
|
-
request,
|
|
27
|
-
response,
|
|
28
|
-
route,
|
|
29
|
-
singletonServices,
|
|
30
|
-
skipUserSession,
|
|
31
|
-
coerceDataFromSchema,
|
|
32
|
-
userSession,
|
|
33
|
-
});
|
|
34
42
|
channelHandler = new PikkuLocalChannelHandler(channelId, channelConfig.name, openingData);
|
|
35
43
|
const channel = channelHandler.getChannel();
|
|
36
44
|
const session = await userSession.get();
|
|
37
45
|
if (createSessionServices) {
|
|
38
|
-
sessionServices = await createSessionServices(singletonServices, { http }, session);
|
|
46
|
+
sessionServices = await createSessionServices(singletonServices, { http, channel }, session);
|
|
39
47
|
}
|
|
40
48
|
const allServices = rpcService.injectRPCService({
|
|
41
49
|
...singletonServices,
|
|
@@ -69,6 +77,9 @@ export const runLocalChannel = async ({ singletonServices, channelId, request, r
|
|
|
69
77
|
await runMiddleware({
|
|
70
78
|
...singletonServices,
|
|
71
79
|
userSession,
|
|
72
|
-
}, { http },
|
|
80
|
+
}, { http }, combineMiddleware({
|
|
81
|
+
wiringMiddleware: channelConfig.middleware,
|
|
82
|
+
wiringTags: channelConfig.tags,
|
|
83
|
+
}), main);
|
|
73
84
|
return channelHandler;
|
|
74
85
|
};
|
|
@@ -4,7 +4,7 @@ import { openChannel } from '../channel-runner.js';
|
|
|
4
4
|
import { createHTTPInteraction } from '../../http/http-runner.js';
|
|
5
5
|
import { handleHTTPError } from '../../../handle-error.js';
|
|
6
6
|
import { PikkuUserSessionService } from '../../../services/user-session-service.js';
|
|
7
|
-
import { runMiddleware } from '../../../middleware-runner.js';
|
|
7
|
+
import { combineMiddleware, runMiddleware } from '../../../middleware-runner.js';
|
|
8
8
|
import { pikkuState } from '../../../pikku-state.js';
|
|
9
9
|
import { PikkuFetchHTTPRequest } from '../../http/pikku-fetch-http-request.js';
|
|
10
10
|
import { runPikkuFuncDirectly } from '../../../function/function-runner.js';
|
|
@@ -76,7 +76,10 @@ export const runChannelConnect = async ({ singletonServices, channelId, channelO
|
|
|
76
76
|
await runMiddleware({
|
|
77
77
|
...singletonServices,
|
|
78
78
|
userSession,
|
|
79
|
-
}, { http },
|
|
79
|
+
}, { http }, combineMiddleware({
|
|
80
|
+
wiringMiddleware: channelConfig.middleware,
|
|
81
|
+
wiringTags: channelConfig.tags,
|
|
82
|
+
}), main);
|
|
80
83
|
};
|
|
81
84
|
export const runChannelDisconnect = async ({ singletonServices, ...params }) => {
|
|
82
85
|
let sessionServices;
|
|
@@ -2,7 +2,7 @@ import { match } from 'path-to-regexp';
|
|
|
2
2
|
import { MissingSessionError, NotFoundError } from '../../errors/errors.js';
|
|
3
3
|
import { closeSessionServices, createWeakUID, isSerializable, } from '../../utils.js';
|
|
4
4
|
import { PikkuUserSessionService, } from '../../services/user-session-service.js';
|
|
5
|
-
import { runMiddleware } from '../../middleware-runner.js';
|
|
5
|
+
import { combineMiddleware, runMiddleware } from '../../middleware-runner.js';
|
|
6
6
|
import { handleHTTPError } from '../../handle-error.js';
|
|
7
7
|
import { pikkuState } from '../../pikku-state.js';
|
|
8
8
|
import { PikkuFetchHTTPResponse } from './pikku-fetch-http-response.js';
|
|
@@ -143,7 +143,16 @@ export const createHTTPInteraction = (request, response) => {
|
|
|
143
143
|
/**
|
|
144
144
|
* Validates the input data and executes the route handler with associated middleware.
|
|
145
145
|
*
|
|
146
|
-
*
|
|
146
|
+
* NOTE: HTTP wiring handles middleware differently from other wirings (RPC, MCP, Queue, etc.)
|
|
147
|
+
* because HTTP needs to:
|
|
148
|
+
* 1. Check session early for performance (before expensive body parsing)
|
|
149
|
+
* 2. Handle HTTP-specific concerns (headers, cookies, SSE setup)
|
|
150
|
+
* 3. Process middleware that may set up authentication/session state
|
|
151
|
+
*
|
|
152
|
+
* Other wirings (RPC/MCP/Queue/Scheduler) simply pass middleware/permissions/auth
|
|
153
|
+
* directly to runPikkuFunc without processing them.
|
|
154
|
+
*
|
|
155
|
+
* This function performs these steps:
|
|
147
156
|
* 1. Sets URL parameters on the request.
|
|
148
157
|
* 2. Validates the user session if required.
|
|
149
158
|
* 3. Creates session-specific services.
|
|
@@ -228,6 +237,7 @@ const executeRouteWithMiddleware = async (services, matchedRoute, http, options)
|
|
|
228
237
|
data,
|
|
229
238
|
permissions: route.permissions,
|
|
230
239
|
coerceDataFromSchema: options.coerceDataFromSchema,
|
|
240
|
+
tags: route.tags,
|
|
231
241
|
});
|
|
232
242
|
// Respond with either a binary or JSON response based on configuration
|
|
233
243
|
if (route.returnsJSON === false) {
|
|
@@ -240,8 +250,15 @@ const executeRouteWithMiddleware = async (services, matchedRoute, http, options)
|
|
|
240
250
|
// TODO: Evaluate if the response stream should be explicitly ended.
|
|
241
251
|
// http?.response?.end()
|
|
242
252
|
};
|
|
253
|
+
// Get function config for middleware and tags
|
|
254
|
+
const funcConfig = pikkuState('function', 'functions').get(meta.pikkuFuncName);
|
|
243
255
|
// Execute middleware, then run the main logic
|
|
244
|
-
await runMiddleware({ ...singletonServices, userSession }, { http },
|
|
256
|
+
await runMiddleware({ ...singletonServices, userSession }, { http }, combineMiddleware({
|
|
257
|
+
wiringMiddleware: middleware,
|
|
258
|
+
wiringTags: route.tags,
|
|
259
|
+
funcMiddleware: funcConfig?.middleware,
|
|
260
|
+
funcTags: funcConfig?.tags,
|
|
261
|
+
}), runMain);
|
|
245
262
|
return sessionServices ? { result, sessionServices } : { result };
|
|
246
263
|
};
|
|
247
264
|
/**
|
|
@@ -35,6 +35,7 @@ export type CoreHTTPFunction = {
|
|
|
35
35
|
eventChannel?: false;
|
|
36
36
|
returnsJSON?: false;
|
|
37
37
|
timeout?: number;
|
|
38
|
+
tags?: string[];
|
|
38
39
|
docs?: Partial<{
|
|
39
40
|
description: string;
|
|
40
41
|
response: string;
|
|
@@ -45,8 +46,8 @@ export type CoreHTTPFunction = {
|
|
|
45
46
|
/**
|
|
46
47
|
* Represents a http interaction within Pikku, including a request and response.
|
|
47
48
|
*/
|
|
48
|
-
export interface PikkuHTTP {
|
|
49
|
-
request?: PikkuHTTPRequest
|
|
49
|
+
export interface PikkuHTTP<In = unknown> {
|
|
50
|
+
request?: PikkuHTTPRequest<In>;
|
|
50
51
|
response?: PikkuHTTPResponse;
|
|
51
52
|
}
|
|
52
53
|
/**
|
|
@@ -168,12 +169,12 @@ export interface PikkuHTTPRequest<In = unknown> {
|
|
|
168
169
|
setParams(params: Record<string, string | string[] | undefined>): void;
|
|
169
170
|
query(): PikkuQuery;
|
|
170
171
|
}
|
|
171
|
-
export interface PikkuHTTPResponse {
|
|
172
|
+
export interface PikkuHTTPResponse<Out = unknown> {
|
|
172
173
|
status(code: number): this;
|
|
173
174
|
cookie(name: string, value: string | null, options: SerializeOptions): this;
|
|
174
175
|
header(name: string, value: string | string[]): this;
|
|
175
176
|
arrayBuffer(data: ArrayBuffer | ArrayBufferView | Blob | string | FormData | URLSearchParams | ReadableStream): this;
|
|
176
|
-
json(data:
|
|
177
|
+
json(data: Out): this;
|
|
177
178
|
redirect(location: string, status?: number): this;
|
|
178
179
|
close?: () => void;
|
|
179
180
|
setMode?: (mode: 'stream') => void;
|
|
@@ -4,6 +4,7 @@ import { pikkuState } from '../../pikku-state.js';
|
|
|
4
4
|
import { addFunction, runPikkuFunc } from '../../function/function-runner.js';
|
|
5
5
|
import { rpcService } from '../rpc/rpc-runner.js';
|
|
6
6
|
import { BadRequestError, NotFoundError } from '../../errors/errors.js';
|
|
7
|
+
import { combineMiddleware, runMiddleware } from '../../middleware-runner.js';
|
|
7
8
|
export class MCPError extends Error {
|
|
8
9
|
error;
|
|
9
10
|
constructor(error) {
|
|
@@ -113,26 +114,38 @@ async function runMCPPikkuFunc(request, type, name, mcp, pikkuFuncName, { sessio
|
|
|
113
114
|
throw new NotFoundError(`MCP '${type}' PikkuFunction Mapping not found for '${name}'`);
|
|
114
115
|
}
|
|
115
116
|
singletonServices.logger.debug(`Running MCP ${type}: ${name}`);
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
let result;
|
|
118
|
+
// Main MCP execution logic wrapped for middleware handling
|
|
119
|
+
const runMain = async () => {
|
|
120
|
+
const getAllServices = async () => {
|
|
121
|
+
if (createSessionServices) {
|
|
122
|
+
const services = await createSessionServices(singletonServices, { mcp: interaction }, session);
|
|
123
|
+
sessionServices = services;
|
|
124
|
+
return rpcService.injectRPCService({
|
|
125
|
+
...singletonServices,
|
|
126
|
+
...services,
|
|
127
|
+
mcp: interaction,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
120
130
|
return rpcService.injectRPCService({
|
|
121
131
|
...singletonServices,
|
|
122
|
-
...services,
|
|
123
132
|
mcp: interaction,
|
|
124
133
|
});
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
134
|
+
};
|
|
135
|
+
result = await runPikkuFunc(pikkuFuncName, {
|
|
136
|
+
getAllServices,
|
|
137
|
+
session,
|
|
138
|
+
data: request.params,
|
|
139
|
+
tags: mcp.tags,
|
|
129
140
|
});
|
|
130
141
|
};
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
142
|
+
const funcConfig = pikkuState('function', 'functions').get(pikkuFuncName);
|
|
143
|
+
await runMiddleware(singletonServices, { mcp: interaction }, combineMiddleware({
|
|
144
|
+
wiringMiddleware: mcp.middleware,
|
|
145
|
+
wiringTags: mcp.tags,
|
|
146
|
+
funcMiddleware: funcConfig?.middleware,
|
|
147
|
+
funcTags: funcConfig?.tags,
|
|
148
|
+
}), runMain);
|
|
136
149
|
return {
|
|
137
150
|
id: request.id,
|
|
138
151
|
result,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CorePikkuFunctionSessionless } from '../../function/functions.types.js';
|
|
2
|
+
import { CorePikkuMiddleware } from '../../types/core.types.js';
|
|
2
3
|
export type PikkuMCP<Tools extends string = any> = {
|
|
3
4
|
uri?: string;
|
|
4
5
|
sendResourceUpdated: (uri: string) => void;
|
|
@@ -38,7 +39,7 @@ export type MCPPromptMeta = Record<string, Omit<CoreMCPPrompt, 'func'> & {
|
|
|
38
39
|
/**
|
|
39
40
|
* Represents an MCP resource with specific properties.
|
|
40
41
|
*/
|
|
41
|
-
export type CoreMCPResource<PikkuFunction = CorePikkuFunctionSessionless<any, any>> = {
|
|
42
|
+
export type CoreMCPResource<PikkuFunction = CorePikkuFunctionSessionless<any, any>, PikkuMiddleware = CorePikkuMiddleware<any>> = {
|
|
42
43
|
uri: string;
|
|
43
44
|
title: string;
|
|
44
45
|
description?: string;
|
|
@@ -47,11 +48,12 @@ export type CoreMCPResource<PikkuFunction = CorePikkuFunctionSessionless<any, an
|
|
|
47
48
|
streaming?: boolean;
|
|
48
49
|
func: PikkuFunction;
|
|
49
50
|
tags?: string[];
|
|
51
|
+
middleware?: PikkuMiddleware[];
|
|
50
52
|
};
|
|
51
53
|
/**
|
|
52
54
|
* Represents an MCP tool with specific properties.
|
|
53
55
|
*/
|
|
54
|
-
export type CoreMCPTool<PikkuFunction = CorePikkuFunctionSessionless<any, any>> = {
|
|
56
|
+
export type CoreMCPTool<PikkuFunction = CorePikkuFunctionSessionless<any, any>, PikkuMiddleware = CorePikkuMiddleware<any>> = {
|
|
55
57
|
name: string;
|
|
56
58
|
title?: string;
|
|
57
59
|
description: string;
|
|
@@ -59,15 +61,17 @@ export type CoreMCPTool<PikkuFunction = CorePikkuFunctionSessionless<any, any>>
|
|
|
59
61
|
func: PikkuFunction;
|
|
60
62
|
tags?: string[];
|
|
61
63
|
streaming?: boolean;
|
|
64
|
+
middleware?: PikkuMiddleware[];
|
|
62
65
|
};
|
|
63
66
|
/**
|
|
64
67
|
* Represents an MCP prompt with specific properties.
|
|
65
68
|
*/
|
|
66
|
-
export type CoreMCPPrompt<PikkuFunction = CorePikkuFunctionSessionless<any, MCPPromptResponse>> = {
|
|
69
|
+
export type CoreMCPPrompt<PikkuFunction = CorePikkuFunctionSessionless<any, MCPPromptResponse>, PikkuMiddleware = CorePikkuMiddleware<any>> = {
|
|
67
70
|
name: string;
|
|
68
71
|
description?: string;
|
|
69
72
|
func: PikkuFunction;
|
|
70
73
|
tags?: string[];
|
|
74
|
+
middleware?: PikkuMiddleware[];
|
|
71
75
|
};
|
|
72
76
|
export type JsonRpcRequest = {
|
|
73
77
|
jsonrpc: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type * from './queue.types.js';
|
|
2
|
-
export { wireQueueWorker, runQueueJob, getQueueWorkers, removeQueueWorker, } from './queue-runner.js';
|
|
2
|
+
export { wireQueueWorker, runQueueJob, getQueueWorkers, removeQueueWorker, QueueJobDiscardedError, QueueJobFailedError, } from './queue-runner.js';
|
|
3
3
|
export { validateWorkerConfig } from './validate-worker-config.js';
|
|
4
4
|
export type { QueueConfigMapping } from './validate-worker-config.js';
|
|
5
5
|
export { registerQueueWorkers } from './register-queue-helper.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Queue processor management
|
|
2
|
-
export { wireQueueWorker, runQueueJob, getQueueWorkers, removeQueueWorker, } from './queue-runner.js';
|
|
2
|
+
export { wireQueueWorker, runQueueJob, getQueueWorkers, removeQueueWorker, QueueJobDiscardedError, QueueJobFailedError, } from './queue-runner.js';
|
|
3
3
|
// Configuration validation
|
|
4
4
|
export { validateWorkerConfig } from './validate-worker-config.js';
|
|
5
5
|
// Queue registration helper
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import type { CoreServices } from '../../types/core.types.js';
|
|
2
2
|
import type { CoreQueueWorker, QueueJob } from './queue.types.js';
|
|
3
3
|
import type { CorePikkuFunctionSessionless } from '../../function/functions.types.js';
|
|
4
|
+
import { PikkuError } from '../../errors/error-handler.js';
|
|
4
5
|
import { CreateSessionServices } from '../../types/core.types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Error class for when a queue job is explicitly failed
|
|
8
|
+
*/
|
|
9
|
+
export declare class QueueJobFailedError extends PikkuError {
|
|
10
|
+
constructor(jobId: string, reason?: string);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Error class for when a queue job is explicitly discarded
|
|
14
|
+
*/
|
|
15
|
+
export declare class QueueJobDiscardedError extends PikkuError {
|
|
16
|
+
constructor(jobId: string, reason?: string);
|
|
17
|
+
}
|
|
5
18
|
/**
|
|
6
19
|
* Add a queue processor to the system
|
|
7
20
|
*/
|
|
@@ -17,8 +30,9 @@ export declare function removeQueueWorker(name: string): Promise<void>;
|
|
|
17
30
|
/**
|
|
18
31
|
* Process a single queue job - this function is called by queue consumers
|
|
19
32
|
*/
|
|
20
|
-
export declare function runQueueJob({ singletonServices, createSessionServices, job, }: {
|
|
33
|
+
export declare function runQueueJob({ singletonServices, createSessionServices, job, updateProgress, }: {
|
|
21
34
|
singletonServices: CoreServices;
|
|
22
35
|
createSessionServices?: CreateSessionServices;
|
|
23
36
|
job: QueueJob;
|
|
37
|
+
updateProgress?: (progress: number | string | object) => Promise<void>;
|
|
24
38
|
}): Promise<void>;
|
|
@@ -1,14 +1,33 @@
|
|
|
1
|
-
import { getErrorResponse } from '../../errors/error-handler.js';
|
|
1
|
+
import { getErrorResponse, PikkuError } from '../../errors/error-handler.js';
|
|
2
2
|
import { pikkuState } from '../../pikku-state.js';
|
|
3
3
|
import { addFunction, runPikkuFunc } from '../../function/function-runner.js';
|
|
4
|
+
import { combineMiddleware, runMiddleware } from '../../middleware-runner.js';
|
|
4
5
|
/**
|
|
5
6
|
* Error class for queue processor not found
|
|
6
7
|
*/
|
|
7
|
-
class QueueWorkerNotFoundError extends
|
|
8
|
+
class QueueWorkerNotFoundError extends PikkuError {
|
|
8
9
|
constructor(name) {
|
|
9
10
|
super(`Queue processor not found: ${name}`);
|
|
10
11
|
}
|
|
11
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Error class for when a queue job is explicitly failed
|
|
15
|
+
*/
|
|
16
|
+
export class QueueJobFailedError extends PikkuError {
|
|
17
|
+
constructor(jobId, reason) {
|
|
18
|
+
super(`Queue job ${jobId} failed${reason ? `: ${reason}` : ''}`);
|
|
19
|
+
this.name = 'QueueJobFailedError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Error class for when a queue job is explicitly discarded
|
|
24
|
+
*/
|
|
25
|
+
export class QueueJobDiscardedError extends PikkuError {
|
|
26
|
+
constructor(jobId, reason) {
|
|
27
|
+
super(`Queue job ${jobId} discarded${reason ? `: ${reason}` : ''}`);
|
|
28
|
+
this.name = 'QueueJobDiscardedError';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
12
31
|
/**
|
|
13
32
|
* Add a queue processor to the system
|
|
14
33
|
*/
|
|
@@ -45,28 +64,64 @@ export async function removeQueueWorker(name) {
|
|
|
45
64
|
/**
|
|
46
65
|
* Process a single queue job - this function is called by queue consumers
|
|
47
66
|
*/
|
|
48
|
-
export async function runQueueJob({ singletonServices, createSessionServices, job, }) {
|
|
67
|
+
export async function runQueueJob({ singletonServices, createSessionServices, job, updateProgress, }) {
|
|
49
68
|
const logger = singletonServices.logger;
|
|
50
69
|
const meta = pikkuState('queue', 'meta');
|
|
51
70
|
const processorMeta = meta[job.queueName];
|
|
52
71
|
if (!processorMeta) {
|
|
53
72
|
throw new Error(`Processor metadata not found for: ${job.queueName}`);
|
|
54
73
|
}
|
|
74
|
+
// Get the queue worker registration to access middleware
|
|
75
|
+
const registrations = pikkuState('queue', 'registrations');
|
|
76
|
+
const queueWorker = registrations.get(job.queueName);
|
|
77
|
+
if (!queueWorker) {
|
|
78
|
+
throw new Error(`Queue worker registration not found for: ${job.queueName}`);
|
|
79
|
+
}
|
|
80
|
+
// Create the queue interaction object
|
|
81
|
+
const queue = {
|
|
82
|
+
queueName: job.queueName,
|
|
83
|
+
jobId: job.id,
|
|
84
|
+
updateProgress: updateProgress ||
|
|
85
|
+
(async (progress) => {
|
|
86
|
+
logger.info(`Job ${job.id} progress: ${progress}`);
|
|
87
|
+
// Default implementation - just log the progress
|
|
88
|
+
}),
|
|
89
|
+
fail: async (reason) => {
|
|
90
|
+
throw new QueueJobFailedError(job.id, reason);
|
|
91
|
+
},
|
|
92
|
+
discard: async (reason) => {
|
|
93
|
+
throw new QueueJobDiscardedError(job.id, reason);
|
|
94
|
+
},
|
|
95
|
+
};
|
|
55
96
|
try {
|
|
56
97
|
logger.info(`Processing job ${job.id} in queue ${job.queueName}`);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
98
|
+
let result;
|
|
99
|
+
// Main job execution logic wrapped for middleware handling
|
|
100
|
+
const runMain = async () => {
|
|
101
|
+
// Use provided singleton services
|
|
102
|
+
const getAllServices = () => ({
|
|
103
|
+
...singletonServices,
|
|
104
|
+
...(createSessionServices
|
|
105
|
+
? createSessionServices(singletonServices, { queue }, undefined)
|
|
106
|
+
: {}),
|
|
107
|
+
});
|
|
108
|
+
// Execute the pikku function with the job data
|
|
109
|
+
result = await runPikkuFunc(processorMeta.pikkuFuncName, {
|
|
110
|
+
getAllServices,
|
|
111
|
+
data: job.data,
|
|
112
|
+
tags: queueWorker.tags,
|
|
113
|
+
});
|
|
114
|
+
logger.debug(`Successfully processed job ${job.id} in queue ${job.queueName}`);
|
|
115
|
+
};
|
|
116
|
+
// Get function config for middleware and tags
|
|
117
|
+
const funcConfig = pikkuState('function', 'functions').get(processorMeta.pikkuFuncName);
|
|
118
|
+
// Get middleware for tags and combine with queue-specific middleware
|
|
119
|
+
await runMiddleware(singletonServices, { queue }, combineMiddleware({
|
|
120
|
+
wiringMiddleware: queueWorker.middleware,
|
|
121
|
+
wiringTags: queueWorker.tags,
|
|
122
|
+
funcMiddleware: funcConfig?.middleware,
|
|
123
|
+
funcTags: funcConfig?.tags,
|
|
124
|
+
}), runMain);
|
|
70
125
|
return result;
|
|
71
126
|
}
|
|
72
127
|
catch (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
|
/**
|
|
@@ -144,11 +144,28 @@ export type queueWorkersMeta = Record<string, {
|
|
|
144
144
|
/**
|
|
145
145
|
* Core queue processor definition
|
|
146
146
|
*/
|
|
147
|
-
export type CoreQueueWorker<PikkuFunction = CorePikkuFunctionSessionless<any, any>> = {
|
|
147
|
+
export type CoreQueueWorker<PikkuFunction = CorePikkuFunctionSessionless<any, any>, PikkuMiddleware = CorePikkuMiddleware<any>> = {
|
|
148
148
|
queueName: string;
|
|
149
149
|
func: PikkuFunction;
|
|
150
150
|
config?: PikkuWorkerConfig;
|
|
151
151
|
docs?: PikkuDocs;
|
|
152
152
|
session?: undefined;
|
|
153
153
|
tags?: string[];
|
|
154
|
+
middleware?: PikkuMiddleware[];
|
|
154
155
|
};
|
|
156
|
+
/**
|
|
157
|
+
* Represents a queue interaction object for middleware
|
|
158
|
+
* Provides information and actions for the current queue job execution
|
|
159
|
+
*/
|
|
160
|
+
export interface PikkuQueue {
|
|
161
|
+
/** The name of the queue being processed */
|
|
162
|
+
queueName: string;
|
|
163
|
+
/** The current job ID */
|
|
164
|
+
jobId: string;
|
|
165
|
+
/** Update job progress (0-100 or custom value) */
|
|
166
|
+
updateProgress: (progress: number | string | object) => Promise<void>;
|
|
167
|
+
/** Fail the current job with optional reason */
|
|
168
|
+
fail: (reason?: string) => Promise<void>;
|
|
169
|
+
/** Discard/delete the job without retrying */
|
|
170
|
+
discard: (reason?: string) => Promise<void>;
|
|
171
|
+
}
|
|
@@ -13,11 +13,13 @@ export declare class PikkuRPCService {
|
|
|
13
13
|
};
|
|
14
14
|
logger: import("../../index.js").Logger;
|
|
15
15
|
variables: import("../../services/variables-service.js").VariablesService;
|
|
16
|
-
http?: import("../http/http.types.js").PikkuHTTP;
|
|
16
|
+
http?: import("../http/http.types.js").PikkuHTTP<unknown> | undefined;
|
|
17
17
|
mcp?: import("../mcp/mcp.types.js").PikkuMCP;
|
|
18
18
|
rpc?: import("./rpc-types.js").PikkuRPC;
|
|
19
|
-
userSession?: import("../../index.js").UserSessionService<import("../../types/core.types.js").CoreUserSession> | undefined;
|
|
20
19
|
channel?: import("../channel/channel.types.js").PikkuChannel<unknown, unknown>;
|
|
20
|
+
scheduledTask?: import("../scheduler/scheduler.types.js").PikkuScheduledTask | undefined;
|
|
21
|
+
queue?: import("../queue/queue.types.js").PikkuQueue | undefined;
|
|
22
|
+
userSession?: import("../../index.js").UserSessionService<import("../../types/core.types.js").CoreUserSession> | undefined;
|
|
21
23
|
};
|
|
22
24
|
}
|
|
23
25
|
export declare const rpcService: PikkuRPCService;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { getErrorResponse } from '../../errors/error-handler.js';
|
|
1
|
+
import { getErrorResponse, PikkuError } from '../../errors/error-handler.js';
|
|
2
2
|
import { closeSessionServices } from '../../utils.js';
|
|
3
3
|
import { pikkuState } from '../../pikku-state.js';
|
|
4
4
|
import { addFunction, runPikkuFunc } from '../../function/function-runner.js';
|
|
5
5
|
import { rpcService } from '../rpc/rpc-runner.js';
|
|
6
|
+
import { combineMiddleware, runMiddleware } from '../../middleware-runner.js';
|
|
6
7
|
export const wireScheduler = (scheduledTask) => {
|
|
7
8
|
const meta = pikkuState('scheduler', 'meta');
|
|
8
9
|
const taskMeta = meta[scheduledTask.name];
|
|
@@ -16,38 +17,67 @@ export const wireScheduler = (scheduledTask) => {
|
|
|
16
17
|
}
|
|
17
18
|
tasks.set(scheduledTask.name, scheduledTask);
|
|
18
19
|
};
|
|
19
|
-
class ScheduledTaskNotFoundError extends
|
|
20
|
+
class ScheduledTaskNotFoundError extends PikkuError {
|
|
20
21
|
constructor(title) {
|
|
21
22
|
super(`Scheduled task not found: ${title}`);
|
|
22
23
|
}
|
|
23
24
|
}
|
|
25
|
+
class ScheduledTaskSkippedError extends PikkuError {
|
|
26
|
+
constructor(taskName, reason) {
|
|
27
|
+
super(`Scheduled task '${taskName}' was skipped${reason ? `: ${reason}` : ''}`);
|
|
28
|
+
this.name = 'ScheduledTaskSkippedError';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
24
31
|
export async function runScheduledTask({ name, session, singletonServices, createSessionServices, }) {
|
|
25
32
|
let sessionServices;
|
|
33
|
+
const task = pikkuState('scheduler', 'tasks').get(name);
|
|
34
|
+
const meta = pikkuState('scheduler', 'meta')[name];
|
|
35
|
+
if (!task) {
|
|
36
|
+
throw new ScheduledTaskNotFoundError(`Scheduled task not found: ${name}`);
|
|
37
|
+
}
|
|
38
|
+
if (!meta) {
|
|
39
|
+
throw new ScheduledTaskNotFoundError(`Scheduled task meta not found: ${name}`);
|
|
40
|
+
}
|
|
41
|
+
// Create the scheduled task interaction object
|
|
42
|
+
const scheduledTask = {
|
|
43
|
+
name,
|
|
44
|
+
schedule: task.schedule,
|
|
45
|
+
executionTime: new Date(),
|
|
46
|
+
skip: (reason) => {
|
|
47
|
+
throw new ScheduledTaskSkippedError(name, reason);
|
|
48
|
+
},
|
|
49
|
+
};
|
|
26
50
|
try {
|
|
27
|
-
const task = pikkuState('scheduler', 'tasks').get(name);
|
|
28
|
-
const meta = pikkuState('scheduler', 'meta')[name];
|
|
29
|
-
if (!task) {
|
|
30
|
-
throw new ScheduledTaskNotFoundError(`Scheduled task not found: ${name}`);
|
|
31
|
-
}
|
|
32
|
-
if (!meta) {
|
|
33
|
-
throw new ScheduledTaskNotFoundError(`Scheduled task meta not found: ${name}`);
|
|
34
|
-
}
|
|
35
51
|
singletonServices.logger.info(`Running schedule task: ${name} | schedule: ${task.schedule}`);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
52
|
+
let result;
|
|
53
|
+
// Main scheduled task execution logic wrapped for middleware handling
|
|
54
|
+
const runMain = async () => {
|
|
55
|
+
const getAllServices = async () => {
|
|
56
|
+
if (createSessionServices) {
|
|
57
|
+
const services = await createSessionServices(singletonServices, { scheduledTask }, session);
|
|
58
|
+
sessionServices = services;
|
|
59
|
+
return rpcService.injectRPCService({
|
|
60
|
+
...singletonServices,
|
|
61
|
+
...services,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return singletonServices;
|
|
65
|
+
};
|
|
66
|
+
result = await runPikkuFunc(meta.pikkuFuncName, {
|
|
67
|
+
getAllServices,
|
|
68
|
+
session,
|
|
69
|
+
data: undefined,
|
|
70
|
+
tags: task.tags,
|
|
71
|
+
});
|
|
45
72
|
};
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
73
|
+
const funcConfig = pikkuState('function', 'functions').get(meta.pikkuFuncName);
|
|
74
|
+
await runMiddleware(singletonServices, { scheduledTask }, combineMiddleware({
|
|
75
|
+
wiringMiddleware: task.middleware,
|
|
76
|
+
wiringTags: task.tags,
|
|
77
|
+
funcMiddleware: funcConfig?.middleware,
|
|
78
|
+
funcTags: funcConfig?.tags,
|
|
79
|
+
}), runMain);
|
|
80
|
+
return result;
|
|
51
81
|
}
|
|
52
82
|
catch (e) {
|
|
53
83
|
const errorResponse = getErrorResponse(e);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PikkuDocs, CoreUserSession } from '../../types/core.types.js';
|
|
1
|
+
import { PikkuDocs, CoreUserSession, CorePikkuMiddleware } from '../../types/core.types.js';
|
|
2
2
|
import { CorePikkuFunctionSessionless } from '../../function/functions.types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Represents metadata for scheduled tasks, including title, schedule, and documentation.
|
|
@@ -14,10 +14,25 @@ export type ScheduledTasksMeta<UserSession extends CoreUserSession = any> = Reco
|
|
|
14
14
|
/**
|
|
15
15
|
* Represents a core scheduled task.
|
|
16
16
|
*/
|
|
17
|
-
export type CoreScheduledTask<PikkuFunction = CorePikkuFunctionSessionless<void, void>> = {
|
|
17
|
+
export type CoreScheduledTask<PikkuFunction = CorePikkuFunctionSessionless<void, void>, PikkuMiddleware = CorePikkuMiddleware<any>> = {
|
|
18
18
|
name: string;
|
|
19
19
|
schedule: string;
|
|
20
20
|
func: PikkuFunction;
|
|
21
21
|
docs?: PikkuDocs;
|
|
22
22
|
tags?: string[];
|
|
23
|
+
middleware?: PikkuMiddleware[];
|
|
23
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* Represents a scheduled task interaction object for middleware
|
|
27
|
+
* Provides information about the current scheduled task execution
|
|
28
|
+
*/
|
|
29
|
+
export interface PikkuScheduledTask {
|
|
30
|
+
/** The name of the scheduled task being executed */
|
|
31
|
+
name: string;
|
|
32
|
+
/** The cron schedule expression */
|
|
33
|
+
schedule: string;
|
|
34
|
+
/** Current execution timestamp */
|
|
35
|
+
executionTime: Date;
|
|
36
|
+
/** Skip the current task execution */
|
|
37
|
+
skip: (reason?: string) => void;
|
|
38
|
+
}
|