@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.
Files changed (52) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/function/function-runner.d.ts +2 -1
  3. package/dist/function/function-runner.js +22 -15
  4. package/dist/function/functions.types.d.ts +3 -2
  5. package/dist/index.d.ts +2 -1
  6. package/dist/index.js +2 -1
  7. package/dist/middleware-runner.d.ts +70 -0
  8. package/dist/middleware-runner.js +105 -2
  9. package/dist/permissions.d.ts +57 -1
  10. package/dist/permissions.js +122 -0
  11. package/dist/pikku-state.d.ts +3 -1
  12. package/dist/pikku-state.js +3 -1
  13. package/dist/types/core.types.d.ts +10 -5
  14. package/dist/wirings/channel/channel-handler.js +1 -0
  15. package/dist/wirings/channel/local/local-channel-runner.js +26 -15
  16. package/dist/wirings/channel/serverless/serverless-channel-runner.js +5 -2
  17. package/dist/wirings/http/http-runner.js +20 -3
  18. package/dist/wirings/http/http.types.d.ts +5 -4
  19. package/dist/wirings/mcp/mcp-runner.js +27 -14
  20. package/dist/wirings/mcp/mcp.types.d.ts +7 -3
  21. package/dist/wirings/queue/index.d.ts +1 -1
  22. package/dist/wirings/queue/index.js +1 -1
  23. package/dist/wirings/queue/queue-runner.d.ts +15 -1
  24. package/dist/wirings/queue/queue-runner.js +71 -16
  25. package/dist/wirings/queue/queue.types.d.ts +19 -2
  26. package/dist/wirings/rpc/rpc-runner.d.ts +4 -2
  27. package/dist/wirings/scheduler/scheduler-runner.js +54 -24
  28. package/dist/wirings/scheduler/scheduler.types.d.ts +17 -2
  29. package/package.json +1 -1
  30. package/src/function/function-runner.test.ts +450 -0
  31. package/src/function/function-runner.ts +25 -26
  32. package/src/function/functions.types.ts +3 -1
  33. package/src/index.ts +10 -1
  34. package/src/middleware-runner.test.ts +329 -0
  35. package/src/middleware-runner.ts +131 -2
  36. package/src/permissions.test.ts +403 -42
  37. package/src/permissions.ts +182 -1
  38. package/src/pikku-state.ts +10 -2
  39. package/src/types/core.types.ts +10 -5
  40. package/src/wirings/channel/channel-handler.ts +1 -0
  41. package/src/wirings/channel/local/local-channel-runner.ts +33 -16
  42. package/src/wirings/channel/serverless/serverless-channel-runner.ts +5 -2
  43. package/src/wirings/http/http-runner.ts +21 -3
  44. package/src/wirings/http/http.types.ts +5 -4
  45. package/src/wirings/mcp/mcp-runner.ts +37 -17
  46. package/src/wirings/mcp/mcp.types.ts +7 -0
  47. package/src/wirings/queue/index.ts +2 -0
  48. package/src/wirings/queue/queue-runner.ts +92 -19
  49. package/src/wirings/queue/queue.types.ts +20 -1
  50. package/src/wirings/scheduler/scheduler-runner.ts +80 -32
  51. package/src/wirings/scheduler/scheduler.types.ts +22 -1
  52. 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 }, route.middleware || [], main);
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 }, channelConfig.middleware || [], main);
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
- * This function is the central execution point for a route. It performs these steps:
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 }, middleware, runMain);
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: unknown): this;
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
- const getAllServices = async () => {
117
- if (createSessionServices) {
118
- const services = await createSessionServices(singletonServices, { mcp: interaction }, session);
119
- sessionServices = services;
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
- return rpcService.injectRPCService({
127
- ...singletonServices,
128
- mcp: interaction,
134
+ };
135
+ result = await runPikkuFunc(pikkuFuncName, {
136
+ getAllServices,
137
+ session,
138
+ data: request.params,
139
+ tags: mcp.tags,
129
140
  });
130
141
  };
131
- const result = await runPikkuFunc(pikkuFuncName, {
132
- getAllServices,
133
- session,
134
- data: request.params,
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 Error {
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
- // Use provided singleton services
58
- const getAllServices = () => ({
59
- ...singletonServices,
60
- ...(createSessionServices
61
- ? createSessionServices(singletonServices, {}, undefined)
62
- : {}),
63
- });
64
- // Execute the pikku function with the job data
65
- const result = await runPikkuFunc(processorMeta.pikkuFuncName, {
66
- getAllServices,
67
- data: job.data,
68
- });
69
- logger.debug(`Successfully processed job ${job.id} in queue ${job.queueName}`);
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 Error {
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
- const getAllServices = async () => {
37
- if (createSessionServices) {
38
- const sessionServices = await createSessionServices(singletonServices, {}, session);
39
- return rpcService.injectRPCService({
40
- ...singletonServices,
41
- ...sessionServices,
42
- });
43
- }
44
- return singletonServices;
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
- return await runPikkuFunc(meta.pikkuFuncName, {
47
- getAllServices,
48
- session,
49
- data: undefined,
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/core",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "module": "dist/index.js",