@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
|
@@ -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 { addMiddlewareForTags, 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,57 @@ 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 middleware for tags and combine with queue-specific middleware
|
|
117
|
+
await runMiddleware(singletonServices, { queue }, addMiddlewareForTags(queueWorker.middleware, queueWorker.tags), runMain);
|
|
70
118
|
return result;
|
|
71
119
|
}
|
|
72
120
|
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,14 @@ 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 | 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
|
+
s?: any;
|
|
23
|
+
userSession?: import("../../index.js").UserSessionService<import("../../types/core.types.js").CoreUserSession> | undefined;
|
|
21
24
|
};
|
|
22
25
|
}
|
|
23
26
|
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 { addMiddlewareForTags, 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,62 @@ 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
|
-
data: undefined,
|
|
50
|
-
});
|
|
73
|
+
// Get middleware for tags and run middleware
|
|
74
|
+
await runMiddleware(singletonServices, { scheduledTask }, addMiddlewareForTags(task.middleware, task.tags), runMain);
|
|
75
|
+
return result;
|
|
51
76
|
}
|
|
52
77
|
catch (e) {
|
|
53
78
|
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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ForbiddenError } from '../errors/errors.js'
|
|
2
2
|
import { runMiddleware } from '../middleware-runner.js'
|
|
3
|
-
import { verifyPermissions } from '../permissions.js'
|
|
3
|
+
import { verifyPermissions, getPermissionsForTags } from '../permissions.js'
|
|
4
4
|
import { pikkuState } from '../pikku-state.js'
|
|
5
5
|
import { coerceTopLevelDataFromSchema, validateSchema } from '../schema.js'
|
|
6
6
|
import {
|
|
@@ -50,6 +50,7 @@ export const runPikkuFunc = async <In = any, Out = any>(
|
|
|
50
50
|
permissions: transportPermissions,
|
|
51
51
|
middleware: transportMiddleware,
|
|
52
52
|
coerceDataFromSchema,
|
|
53
|
+
tags = [],
|
|
53
54
|
}: {
|
|
54
55
|
getAllServices: () => Promise<CoreServices> | CoreServices
|
|
55
56
|
data: In
|
|
@@ -57,6 +58,7 @@ export const runPikkuFunc = async <In = any, Out = any>(
|
|
|
57
58
|
permissions?: CorePermissionGroup
|
|
58
59
|
middleware?: CorePikkuMiddleware[]
|
|
59
60
|
coerceDataFromSchema?: boolean
|
|
61
|
+
tags?: string[]
|
|
60
62
|
}
|
|
61
63
|
): Promise<Out> => {
|
|
62
64
|
const funcConfig = pikkuState('function', 'functions').get(funcName)
|
|
@@ -92,7 +94,23 @@ export const runPikkuFunc = async <In = any, Out = any>(
|
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
let permissioned = true
|
|
95
|
-
|
|
97
|
+
|
|
98
|
+
// Check tagged permissions first - ALL must pass
|
|
99
|
+
const taggedPermissions = getPermissionsForTags([
|
|
100
|
+
...tags,
|
|
101
|
+
...(funcConfig.tags || []),
|
|
102
|
+
])
|
|
103
|
+
if (taggedPermissions.length > 0) {
|
|
104
|
+
const taggedResults = await Promise.all(
|
|
105
|
+
taggedPermissions.map((permission) =>
|
|
106
|
+
permission(allServices, data, session)
|
|
107
|
+
)
|
|
108
|
+
)
|
|
109
|
+
permissioned = taggedResults.every((result) => result)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Only check function permissions if tagged permissions passed
|
|
113
|
+
if (permissioned && funcConfig.permissions) {
|
|
96
114
|
permissioned = await verifyPermissions(
|
|
97
115
|
funcConfig.permissions,
|
|
98
116
|
allServices,
|
|
@@ -100,6 +118,7 @@ export const runPikkuFunc = async <In = any, Out = any>(
|
|
|
100
118
|
session
|
|
101
119
|
)
|
|
102
120
|
}
|
|
121
|
+
|
|
103
122
|
if (!permissioned && transportPermissions) {
|
|
104
123
|
permissioned = await verifyPermissions(
|
|
105
124
|
transportPermissions,
|
|
@@ -87,12 +87,14 @@ export type CorePikkuFunctionConfig<
|
|
|
87
87
|
any,
|
|
88
88
|
any
|
|
89
89
|
> = CorePikkuPermission<any>,
|
|
90
|
+
PikkuMiddleware extends CorePikkuMiddleware<any> = CorePikkuMiddleware<any>,
|
|
90
91
|
> = {
|
|
91
92
|
name?: string
|
|
92
93
|
expose?: boolean
|
|
93
94
|
func: PikkuFunction
|
|
94
95
|
auth?: boolean
|
|
95
96
|
permissions?: CorePermissionGroup<PikkuPermission>
|
|
96
|
-
middleware?:
|
|
97
|
+
middleware?: PikkuMiddleware[]
|
|
98
|
+
tags?: string[]
|
|
97
99
|
docs?: PikkuDocs
|
|
98
100
|
}
|
package/src/index.ts
CHANGED
|
@@ -21,7 +21,12 @@ export * from './middleware/index.js'
|
|
|
21
21
|
export * from './time-utils.js'
|
|
22
22
|
export * from './utils.js'
|
|
23
23
|
export { pikkuState } from './pikku-state.js'
|
|
24
|
-
export {
|
|
24
|
+
export {
|
|
25
|
+
runMiddleware,
|
|
26
|
+
addMiddleware,
|
|
27
|
+
addMiddlewareForTags,
|
|
28
|
+
} from './middleware-runner.js'
|
|
29
|
+
export { addPermission, getPermissionsForTags } from './permissions.js'
|
|
25
30
|
export { wireHTTP, addHTTPMiddleware } from './wirings/http/http-runner.js'
|
|
26
31
|
export { wireChannel } from './wirings/channel/channel-runner.js'
|
|
27
32
|
export { wireScheduler } from './wirings/scheduler/scheduler-runner.js'
|
package/src/middleware-runner.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
PikkuInteraction,
|
|
5
5
|
CorePikkuMiddleware,
|
|
6
6
|
} from './types/core.types.js'
|
|
7
|
+
import { pikkuState } from './pikku-state.js'
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Runs a chain of middleware functions in sequence before executing the main function.
|
|
@@ -30,10 +31,13 @@ export const runMiddleware = async <Middleware extends CorePikkuMiddleware>(
|
|
|
30
31
|
middlewares: Middleware[],
|
|
31
32
|
main?: () => Promise<unknown>
|
|
32
33
|
): Promise<unknown> => {
|
|
34
|
+
// Deduplicate middleware using Set to avoid running the same middleware multiple times
|
|
35
|
+
const uniqueMiddleware = Array.from(new Set(middlewares))
|
|
36
|
+
|
|
33
37
|
let result: any
|
|
34
38
|
const dispatch = async (index: number): Promise<any> => {
|
|
35
|
-
if (
|
|
36
|
-
return await
|
|
39
|
+
if (uniqueMiddleware && index < uniqueMiddleware.length) {
|
|
40
|
+
return await uniqueMiddleware[index]!(services as any, interaction, () =>
|
|
37
41
|
dispatch(index + 1)
|
|
38
42
|
)
|
|
39
43
|
} else if (main) {
|
|
@@ -43,3 +47,107 @@ export const runMiddleware = async <Middleware extends CorePikkuMiddleware>(
|
|
|
43
47
|
await dispatch(0)
|
|
44
48
|
return result
|
|
45
49
|
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Adds global middleware for a specific tag.
|
|
53
|
+
*
|
|
54
|
+
* This function allows you to register middleware that will be applied to
|
|
55
|
+
* any wiring (HTTP, Channel, Queue, Scheduler, MCP) that includes the matching tag.
|
|
56
|
+
*
|
|
57
|
+
* @template PikkuMiddleware The middleware type.
|
|
58
|
+
* @param {string} tag - The tag that the middleware should apply to.
|
|
59
|
+
* @param {PikkuMiddleware[]} middleware - The middleware array to apply for the specified tag.
|
|
60
|
+
*
|
|
61
|
+
* @throws {Error} If middleware for the tag already exists.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* // Add admin middleware for admin endpoints
|
|
66
|
+
* addMiddleware('admin', [adminMiddleware])
|
|
67
|
+
*
|
|
68
|
+
* // Add authentication middleware for auth endpoints
|
|
69
|
+
* addMiddleware('auth', [authMiddleware])
|
|
70
|
+
*
|
|
71
|
+
* // Add logging middleware for all API endpoints
|
|
72
|
+
* addMiddleware('api', [loggingMiddleware])
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export const addMiddleware = <PikkuMiddleware extends CorePikkuMiddleware>(
|
|
76
|
+
tag: string,
|
|
77
|
+
middleware: PikkuMiddleware[]
|
|
78
|
+
) => {
|
|
79
|
+
const middlewareStore = pikkuState('misc', 'middleware')
|
|
80
|
+
|
|
81
|
+
// Check if tag already exists
|
|
82
|
+
if (middlewareStore[tag]) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Middleware for tag '${tag}' already exists. Use a different tag or remove the existing middleware first.`
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
middlewareStore[tag] = middleware as CorePikkuMiddleware[]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Retrieves middleware for a given set of tags.
|
|
93
|
+
*
|
|
94
|
+
* This function looks up all middleware registered for any of the provided tags
|
|
95
|
+
* and returns them as a flattened array.
|
|
96
|
+
*
|
|
97
|
+
* @param {string[]} tags - Array of tags to look up middleware for.
|
|
98
|
+
* @returns {CorePikkuMiddleware[]} Array of middleware functions that apply to the given tags.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```typescript
|
|
102
|
+
* // Get all middleware for tags 'api' and 'auth'
|
|
103
|
+
* const middleware = getMiddlewareForTags(['api', 'auth'])
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export const getMiddlewareForTags = (
|
|
107
|
+
tags?: string[]
|
|
108
|
+
): CorePikkuMiddleware[] => {
|
|
109
|
+
if (!tags || tags.length === 0) {
|
|
110
|
+
return []
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const middlewareStore = pikkuState('misc', 'middleware')
|
|
114
|
+
const applicableMiddleware: CorePikkuMiddleware[] = []
|
|
115
|
+
|
|
116
|
+
// Collect middleware for all matching tags
|
|
117
|
+
for (const tag of tags) {
|
|
118
|
+
const tagMiddleware = middlewareStore[tag]
|
|
119
|
+
if (tagMiddleware) {
|
|
120
|
+
applicableMiddleware.push(...tagMiddleware)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return applicableMiddleware
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Combines tag-based middleware with wiring-specific middleware.
|
|
129
|
+
*
|
|
130
|
+
* This helper function gets middleware for tags and combines it with any
|
|
131
|
+
* wiring-specific middleware, avoiding the need for manual spreading.
|
|
132
|
+
*
|
|
133
|
+
* @param {CorePikkuMiddleware[] | undefined} wiringMiddleware - Wiring-specific middleware.
|
|
134
|
+
* @param {string[] | undefined} tags - Array of tags to look up middleware for.
|
|
135
|
+
* @returns {CorePikkuMiddleware[]} Combined array of tag-based and wiring-specific middleware.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```typescript
|
|
139
|
+
* // Instead of:
|
|
140
|
+
* const taggedMiddleware = getMiddlewareForTags(tags)
|
|
141
|
+
* const combined = [...taggedMiddleware, ...(middleware || [])]
|
|
142
|
+
*
|
|
143
|
+
* // Use:
|
|
144
|
+
* const combined = addMiddlewareForTags(middleware, tags)
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
export const addMiddlewareForTags = (
|
|
148
|
+
wiringMiddleware?: CorePikkuMiddleware[],
|
|
149
|
+
tags?: string[]
|
|
150
|
+
): CorePikkuMiddleware[] => {
|
|
151
|
+
const taggedMiddleware = getMiddlewareForTags(tags)
|
|
152
|
+
return [...taggedMiddleware, ...(wiringMiddleware || [])]
|
|
153
|
+
}
|