@pikku/core 0.10.1 → 0.11.0
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 +47 -0
- package/dist/function/function-runner.js +2 -2
- package/dist/function/functions.types.d.ts +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/middleware/auth-apikey.d.ts +1 -0
- package/dist/middleware/auth-bearer.d.ts +1 -0
- package/dist/middleware/auth-cookie.d.ts +1 -0
- package/dist/middleware/timeout.d.ts +1 -0
- package/dist/middleware-runner.js +1 -0
- package/dist/permissions.js +1 -0
- package/dist/pikku-state.d.ts +7 -2
- package/dist/pikku-state.js +4 -0
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/scheduler-service.d.ts +63 -0
- package/dist/services/scheduler-service.js +6 -0
- package/dist/time-utils.d.ts +14 -0
- package/dist/time-utils.js +62 -0
- package/dist/types/core.types.d.ts +24 -2
- package/dist/types/core.types.js +1 -0
- package/dist/wirings/channel/channel-common.d.ts +28 -0
- package/dist/wirings/channel/channel-common.js +42 -0
- package/dist/wirings/channel/channel-handler.js +37 -11
- package/dist/wirings/channel/channel-runner.js +9 -4
- package/dist/wirings/channel/channel.types.d.ts +8 -2
- package/dist/wirings/channel/local/local-channel-handler.js +3 -0
- package/dist/wirings/channel/local/local-channel-runner.js +47 -14
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +90 -41
- package/dist/wirings/cli/channel/cli-channel-runner.js +10 -1
- package/dist/wirings/cli/cli-runner.d.ts +1 -1
- package/dist/wirings/cli/cli-runner.js +1 -1
- package/dist/wirings/http/http-runner.js +0 -1
- package/dist/wirings/mcp/mcp-runner.js +3 -2
- package/dist/wirings/queue/queue-runner.js +6 -3
- package/dist/wirings/queue/queue.types.d.ts +1 -3
- package/dist/wirings/rpc/index.d.ts +1 -1
- package/dist/wirings/rpc/index.js +1 -1
- package/dist/wirings/rpc/rpc-runner.d.ts +15 -0
- package/dist/wirings/rpc/rpc-runner.js +38 -3
- package/dist/wirings/rpc/rpc-types.d.ts +1 -0
- package/dist/wirings/workflow/index.d.ts +6 -0
- package/dist/wirings/workflow/index.js +6 -0
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +152 -0
- package/dist/wirings/workflow/pikku-workflow-service.js +448 -0
- package/dist/wirings/workflow/workflow-runner.d.ts +35 -0
- package/dist/wirings/workflow/workflow-runner.js +68 -0
- package/dist/wirings/workflow/workflow.types.d.ts +247 -0
- package/dist/wirings/workflow/workflow.types.js +1 -0
- package/package.json +2 -3
- package/src/function/function-runner.ts +2 -2
- package/src/function/functions.types.ts +1 -0
- package/src/index.ts +2 -0
- package/src/middleware-runner.ts +1 -0
- package/src/permissions.ts +1 -0
- package/src/pikku-state.ts +14 -2
- package/src/services/index.ts +1 -0
- package/src/services/scheduler-service.ts +76 -0
- package/src/time-utils.ts +75 -0
- package/src/types/core.types.ts +30 -1
- package/src/wirings/channel/channel-common.ts +80 -0
- package/src/wirings/channel/channel-handler.ts +48 -18
- package/src/wirings/channel/channel-runner.ts +15 -4
- package/src/wirings/channel/channel.types.ts +12 -2
- package/src/wirings/channel/local/local-channel-handler.ts +3 -0
- package/src/wirings/channel/local/local-channel-runner.ts +48 -36
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +134 -66
- package/src/wirings/cli/channel/cli-channel-runner.ts +13 -1
- package/src/wirings/cli/cli-runner.ts +2 -2
- package/src/wirings/http/http-runner.ts +0 -2
- package/src/wirings/mcp/mcp-runner.ts +5 -2
- package/src/wirings/queue/queue-runner.ts +14 -6
- package/src/wirings/queue/queue.types.ts +1 -4
- package/src/wirings/rpc/index.ts +1 -1
- package/src/wirings/rpc/rpc-runner.ts +57 -4
- package/src/wirings/rpc/rpc-types.ts +1 -0
- package/src/wirings/workflow/index.ts +22 -0
- package/src/wirings/workflow/pikku-workflow-service.ts +756 -0
- package/src/wirings/workflow/workflow-runner.ts +85 -0
- package/src/wirings/workflow/workflow.types.ts +332 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { pikkuState } from '../../pikku-state.js';
|
|
2
|
+
import { PikkuError } from '../../errors/error-handler.js';
|
|
3
|
+
import { addFunction } from '../../function/function-runner.js';
|
|
4
|
+
/**
|
|
5
|
+
* Exception thrown when workflow needs to pause for async step
|
|
6
|
+
*/
|
|
7
|
+
export class WorkflowAsyncException extends Error {
|
|
8
|
+
runId;
|
|
9
|
+
stepName;
|
|
10
|
+
constructor(runId, stepName) {
|
|
11
|
+
super(`Workflow paused at step: ${stepName}`);
|
|
12
|
+
this.runId = runId;
|
|
13
|
+
this.stepName = stepName;
|
|
14
|
+
this.name = 'WorkflowAsyncException';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Exception thrown when workflow is cancelled
|
|
19
|
+
*/
|
|
20
|
+
export class WorkflowCancelledException extends Error {
|
|
21
|
+
runId;
|
|
22
|
+
reason;
|
|
23
|
+
constructor(runId, reason) {
|
|
24
|
+
super(reason || 'Workflow cancelled');
|
|
25
|
+
this.runId = runId;
|
|
26
|
+
this.reason = reason;
|
|
27
|
+
this.name = 'WorkflowCancelledException';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Error class for workflow not found
|
|
32
|
+
*/
|
|
33
|
+
export class WorkflowNotFoundError extends PikkuError {
|
|
34
|
+
constructor(name) {
|
|
35
|
+
super(`Workflow not found: ${name}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Error class for workflow not found
|
|
40
|
+
*/
|
|
41
|
+
export class WorkflowRunNotFound extends PikkuError {
|
|
42
|
+
constructor(runId) {
|
|
43
|
+
super(`Workflow run not found: ${runId}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Register a workflow with the system
|
|
48
|
+
*/
|
|
49
|
+
export const wireWorkflow = (workflow) => {
|
|
50
|
+
// Get workflow metadata from inspector
|
|
51
|
+
const meta = pikkuState('workflows', 'meta');
|
|
52
|
+
const workflowMeta = meta[workflow.name];
|
|
53
|
+
if (!workflowMeta) {
|
|
54
|
+
throw new Error(`Workflow metadata not found for '${workflow.name}'. Make sure to run the CLI to generate metadata.`);
|
|
55
|
+
}
|
|
56
|
+
// Register the function with pikku
|
|
57
|
+
addFunction(workflowMeta.pikkuFuncName, {
|
|
58
|
+
func: workflow.func.func,
|
|
59
|
+
auth: workflow.func.auth,
|
|
60
|
+
permissions: workflow.func.permissions,
|
|
61
|
+
middleware: workflow.func.middleware,
|
|
62
|
+
tags: workflow.func.tags,
|
|
63
|
+
docs: workflow.func.docs,
|
|
64
|
+
});
|
|
65
|
+
// Store workflow definition in state
|
|
66
|
+
const registrations = pikkuState('workflows', 'registrations');
|
|
67
|
+
registrations.set(workflow.name, workflow);
|
|
68
|
+
};
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { PikkuDocs, MiddlewareMetadata, SerializedError, CoreSingletonServices, CreateSessionServices, CoreConfig } from '../../types/core.types.js';
|
|
2
|
+
import { CorePikkuFunctionConfig } from '../../function/functions.types.js';
|
|
3
|
+
export interface WorkflowServiceConfig {
|
|
4
|
+
retries: number;
|
|
5
|
+
retryDelay: number;
|
|
6
|
+
orchestratorQueueName: string;
|
|
7
|
+
stepWorkerQueueName: string;
|
|
8
|
+
sleeperRPCName: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Workflow run status
|
|
12
|
+
*/
|
|
13
|
+
export type WorkflowStatus = 'running' | 'completed' | 'failed' | 'cancelled';
|
|
14
|
+
/**
|
|
15
|
+
* Workflow step status
|
|
16
|
+
*/
|
|
17
|
+
export type StepStatus = 'pending' | 'running' | 'scheduled' | 'succeeded' | 'failed';
|
|
18
|
+
/**
|
|
19
|
+
* Workflow run representation
|
|
20
|
+
*/
|
|
21
|
+
export interface WorkflowRun {
|
|
22
|
+
/** Unique run ID */
|
|
23
|
+
id: string;
|
|
24
|
+
/** Workflow name */
|
|
25
|
+
workflow: string;
|
|
26
|
+
/** Current status */
|
|
27
|
+
status: WorkflowStatus;
|
|
28
|
+
/** Input data */
|
|
29
|
+
input: any;
|
|
30
|
+
/** Output data (if completed) */
|
|
31
|
+
output?: any;
|
|
32
|
+
/** Error (if failed) */
|
|
33
|
+
error?: SerializedError;
|
|
34
|
+
/** Creation timestamp */
|
|
35
|
+
createdAt: Date;
|
|
36
|
+
/** Last update timestamp */
|
|
37
|
+
updatedAt: Date;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Step state representation
|
|
41
|
+
*/
|
|
42
|
+
export interface StepState {
|
|
43
|
+
/** Unique step ID */
|
|
44
|
+
stepId: string;
|
|
45
|
+
/** Step status */
|
|
46
|
+
status: StepStatus;
|
|
47
|
+
/** Step result (if done) */
|
|
48
|
+
result?: any;
|
|
49
|
+
/** Step error (if error) */
|
|
50
|
+
error?: SerializedError;
|
|
51
|
+
/** Number of attempts made (starts at 1) */
|
|
52
|
+
attemptCount: number;
|
|
53
|
+
/** Maximum retry attempts allowed */
|
|
54
|
+
retries?: number;
|
|
55
|
+
/** Delay between retries */
|
|
56
|
+
retryDelay?: string | number;
|
|
57
|
+
/** Creation timestamp */
|
|
58
|
+
createdAt: Date;
|
|
59
|
+
/** Last update timestamp */
|
|
60
|
+
updatedAt: Date;
|
|
61
|
+
/** Timestamp when step started running */
|
|
62
|
+
runningAt?: Date;
|
|
63
|
+
/** Timestamp when step was scheduled */
|
|
64
|
+
scheduledAt?: Date;
|
|
65
|
+
/** Timestamp when step succeeded */
|
|
66
|
+
succeededAt?: Date;
|
|
67
|
+
/** Timestamp when step failed */
|
|
68
|
+
failedAt?: Date;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Core workflow definition
|
|
72
|
+
*/
|
|
73
|
+
export type CoreWorkflow<PikkuFunctionConfig extends CorePikkuFunctionConfig<any, any, any> = CorePikkuFunctionConfig<any, any, any>> = {
|
|
74
|
+
/** Unique workflow name */
|
|
75
|
+
name: string;
|
|
76
|
+
/** Description of the workflow */
|
|
77
|
+
description?: string;
|
|
78
|
+
/** The workflow function */
|
|
79
|
+
func: PikkuFunctionConfig;
|
|
80
|
+
/** Middleware chain for this workflow */
|
|
81
|
+
middleware?: PikkuFunctionConfig['middleware'];
|
|
82
|
+
/** Permission requirements */
|
|
83
|
+
permissions?: PikkuFunctionConfig['permissions'];
|
|
84
|
+
/** Tags for organization and filtering */
|
|
85
|
+
tags?: string[];
|
|
86
|
+
/** Documentation metadata */
|
|
87
|
+
docs?: PikkuDocs;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Workflow step options
|
|
91
|
+
*/
|
|
92
|
+
export interface WorkflowStepOptions {
|
|
93
|
+
/** Display name for logs/UI (optional, doesn't affect execution) */
|
|
94
|
+
description?: string;
|
|
95
|
+
/** Number of retry attempts for failed steps (only applies to local execution) */
|
|
96
|
+
retries?: number;
|
|
97
|
+
/** Delay between retry attempts (e.g., '1s', '2s', '2min') */
|
|
98
|
+
retryDelay?: string | number;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Type signature for workflow.do() RPC form - used by inspector
|
|
102
|
+
*/
|
|
103
|
+
export type WorkflowInteractionDoRPC = <TOutput = any, TInput = any>(stepName: string, rpcName: string, data: TInput, options?: WorkflowStepOptions) => Promise<TOutput>;
|
|
104
|
+
/**
|
|
105
|
+
* Type signature for workflow.do() inline form - used by inspector
|
|
106
|
+
*/
|
|
107
|
+
export type WorkflowInteractionDoInline = <T>(stepName: string, fn: () => Promise<T> | T, options?: WorkflowStepOptions) => Promise<T>;
|
|
108
|
+
/**
|
|
109
|
+
* Type signature for workflow.sleep() - used by inspector
|
|
110
|
+
*/
|
|
111
|
+
export type WorkflowInteractionSleep = (stepName: string, duration: string) => Promise<void>;
|
|
112
|
+
/**
|
|
113
|
+
* Workflow step metadata (extracted by inspector)
|
|
114
|
+
*/
|
|
115
|
+
export type WorkflowStepMeta = {
|
|
116
|
+
/** RPC form - generates queue worker */
|
|
117
|
+
type: 'rpc';
|
|
118
|
+
/** Cache key (stepName from workflow.do) */
|
|
119
|
+
stepName: string;
|
|
120
|
+
/** RPC to invoke */
|
|
121
|
+
rpcName: string;
|
|
122
|
+
/** Display name */
|
|
123
|
+
description?: string;
|
|
124
|
+
/** Step options */
|
|
125
|
+
options?: WorkflowStepOptions;
|
|
126
|
+
} | {
|
|
127
|
+
/** Inline form - local execution */
|
|
128
|
+
type: 'inline';
|
|
129
|
+
/** Cache key (stepName from workflow.do) */
|
|
130
|
+
stepName: string;
|
|
131
|
+
/** Display name */
|
|
132
|
+
description?: string;
|
|
133
|
+
/** Step options */
|
|
134
|
+
options?: WorkflowStepOptions;
|
|
135
|
+
} | {
|
|
136
|
+
/** Sleep step */
|
|
137
|
+
type: 'sleep';
|
|
138
|
+
/** Cache key (stepName from workflow.sleep) */
|
|
139
|
+
stepName: string;
|
|
140
|
+
/** Sleep duration */
|
|
141
|
+
duration: string | number;
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Workflow step interaction context for RPC functions
|
|
145
|
+
* Provides step-level metadata including retry attempt tracking
|
|
146
|
+
*/
|
|
147
|
+
export interface WorkflowStepInteraction {
|
|
148
|
+
/** The workflow run ID */
|
|
149
|
+
runId: string;
|
|
150
|
+
/** The unique step ID */
|
|
151
|
+
stepId: string;
|
|
152
|
+
/** Current attempt number (1-indexed, increments on retry) */
|
|
153
|
+
attemptCount: number;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Workflow interaction object for middleware
|
|
157
|
+
* Provides workflow-specific capabilities to function execution
|
|
158
|
+
*/
|
|
159
|
+
export interface PikkuWorkflowInteraction {
|
|
160
|
+
/** The workflow name */
|
|
161
|
+
workflowName: string;
|
|
162
|
+
/** The current run ID */
|
|
163
|
+
runId: string;
|
|
164
|
+
/** Get the current workflow run */
|
|
165
|
+
getRun: () => Promise<WorkflowRun>;
|
|
166
|
+
/** Execute a workflow step (overloaded - RPC or inline form) */
|
|
167
|
+
do: WorkflowInteractionDoRPC & WorkflowInteractionDoInline;
|
|
168
|
+
/** Sleep for a duration */
|
|
169
|
+
sleep: WorkflowInteractionSleep;
|
|
170
|
+
/** Cancel the current workflow run */
|
|
171
|
+
cancel: (reason?: string) => Promise<void>;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Workflow client interface
|
|
175
|
+
*/
|
|
176
|
+
export interface PikkuWorkflow {
|
|
177
|
+
/** Start a new workflow run */
|
|
178
|
+
start: <I>(input: I) => Promise<{
|
|
179
|
+
runId: string;
|
|
180
|
+
}>;
|
|
181
|
+
/** Get a workflow run by ID */
|
|
182
|
+
getRun: (runId: string) => Promise<WorkflowRun>;
|
|
183
|
+
/** Cancel a running workflow */
|
|
184
|
+
cancelRun: (runId: string) => Promise<void>;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Workflows metadata for inspector/CLI
|
|
188
|
+
*/
|
|
189
|
+
export type WorkflowsMeta = Record<string, {
|
|
190
|
+
pikkuFuncName: string;
|
|
191
|
+
workflowName: string;
|
|
192
|
+
description?: string;
|
|
193
|
+
session?: undefined;
|
|
194
|
+
docs?: PikkuDocs;
|
|
195
|
+
tags?: string[];
|
|
196
|
+
middleware?: MiddlewareMetadata[];
|
|
197
|
+
steps: WorkflowStepMeta[];
|
|
198
|
+
}>;
|
|
199
|
+
/**
|
|
200
|
+
* Interface for workflow orchestration
|
|
201
|
+
* Handles workflow execution, replay, orchestration logic, and run-level state
|
|
202
|
+
*/
|
|
203
|
+
export interface WorkflowService {
|
|
204
|
+
createRun(workflowName: string, input: any): Promise<string>;
|
|
205
|
+
getRun(id: string): Promise<WorkflowRun | null>;
|
|
206
|
+
getRunHistory(runId: string): Promise<Array<StepState & {
|
|
207
|
+
stepName: string;
|
|
208
|
+
}>>;
|
|
209
|
+
updateRunStatus(id: string, status: WorkflowStatus, output?: any, error?: SerializedError): Promise<void>;
|
|
210
|
+
withRunLock<T>(id: string, fn: () => Promise<T>): Promise<T>;
|
|
211
|
+
close(): Promise<void>;
|
|
212
|
+
resumeWorkflow(runId: string): Promise<void>;
|
|
213
|
+
setServices(singletonServices: CoreSingletonServices, createSessionServices: CreateSessionServices, config: CoreConfig): void;
|
|
214
|
+
startWorkflow<I>(name: string, input: I, rpcService: any): Promise<{
|
|
215
|
+
runId: string;
|
|
216
|
+
}>;
|
|
217
|
+
runWorkflowJob(runId: string, rpcService: any): Promise<void>;
|
|
218
|
+
orchestrateWorkflow(runId: string, rpcService: any): Promise<void>;
|
|
219
|
+
executeWorkflowSleep(runId: string, stepId: string): Promise<void>;
|
|
220
|
+
insertStepState(runId: string, stepName: string, rpcName: string, data: any, stepOptions?: {
|
|
221
|
+
retries?: number;
|
|
222
|
+
retryDelay?: string | number;
|
|
223
|
+
}): Promise<StepState>;
|
|
224
|
+
getStepState(runId: string, stepName: string): Promise<StepState>;
|
|
225
|
+
setStepRunning(stepId: string): Promise<void>;
|
|
226
|
+
setStepScheduled(stepId: string): Promise<void>;
|
|
227
|
+
setStepResult(stepId: string, result: any): Promise<void>;
|
|
228
|
+
setStepError(stepId: string, error: Error): Promise<void>;
|
|
229
|
+
createRetryAttempt(stepId: string, status: 'pending' | 'running'): Promise<StepState>;
|
|
230
|
+
executeWorkflowStep(runId: string, stepName: string, rpcName: string | null, data: any, rpcService: any): Promise<void>;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Worker input types for generated queue workers
|
|
234
|
+
*/
|
|
235
|
+
export type WorkflowStepInput = {
|
|
236
|
+
runId: string;
|
|
237
|
+
stepName: string;
|
|
238
|
+
rpcName: string;
|
|
239
|
+
data: any;
|
|
240
|
+
};
|
|
241
|
+
export type WorkflowOrchestratorInput = {
|
|
242
|
+
runId: string;
|
|
243
|
+
};
|
|
244
|
+
export type WorkflowSleeperInput = {
|
|
245
|
+
runId: string;
|
|
246
|
+
stepId: string;
|
|
247
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"author": "yasser.fadl@gmail.com",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"./middleware": "./dist/middleware/index.js",
|
|
21
21
|
"./function": "./dist/function/index.js",
|
|
22
22
|
"./channel": "./dist/wirings/channel/index.js",
|
|
23
|
+
"./workflow": "./dist/wirings/workflow/index.js",
|
|
23
24
|
"./channel/local": "./dist/wirings/channel/local/index.js",
|
|
24
25
|
"./channel/serverless": "./dist/wirings/channel/serverless/index.js",
|
|
25
26
|
"./http": "./dist/wirings/http/index.js",
|
|
@@ -36,8 +37,6 @@
|
|
|
36
37
|
"./types": "./dist/types/index.d.ts"
|
|
37
38
|
},
|
|
38
39
|
"dependencies": {
|
|
39
|
-
"@types/cookie": "^1.0.0",
|
|
40
|
-
"@types/uuid": "^11.0.0",
|
|
41
40
|
"cookie": "^1.0.2",
|
|
42
41
|
"path-to-regexp": "^8.3.0",
|
|
43
42
|
"picoquery": "^2.5.0"
|
|
@@ -91,7 +91,7 @@ export const runPikkuFunc = async <In = any, Out = any>(
|
|
|
91
91
|
|
|
92
92
|
// Helper function to run permissions and execute the function
|
|
93
93
|
const executeFunction = async () => {
|
|
94
|
-
const session = userSession?.get()
|
|
94
|
+
const session = await userSession?.get()
|
|
95
95
|
if (wiringAuth === true || funcConfig.auth === true) {
|
|
96
96
|
// This means it was explicitly enabled in either wiring or function and has to be respected
|
|
97
97
|
if (!session) {
|
|
@@ -101,7 +101,7 @@ export const runPikkuFunc = async <In = any, Out = any>(
|
|
|
101
101
|
if (wiringAuth === undefined && funcConfig.auth === undefined) {
|
|
102
102
|
// We always default to requiring auth unless explicitly disabled
|
|
103
103
|
if (!session) {
|
|
104
|
-
throw new ForbiddenError('Authentication required')
|
|
104
|
+
// throw new ForbiddenError('Authentication required')
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
package/src/index.ts
CHANGED
|
@@ -10,11 +10,13 @@ export * from './services/schema-service.js'
|
|
|
10
10
|
export * from './services/jwt-service.js'
|
|
11
11
|
export * from './services/secret-service.js'
|
|
12
12
|
export * from './services/user-session-service.js'
|
|
13
|
+
export * from './services/scheduler-service.js'
|
|
13
14
|
export * from './wirings/http/index.js'
|
|
14
15
|
export * from './wirings/channel/index.js'
|
|
15
16
|
export * from './wirings/scheduler/index.js'
|
|
16
17
|
export * from './wirings/rpc/index.js'
|
|
17
18
|
export * from './wirings/queue/index.js'
|
|
19
|
+
export * from './wirings/workflow/index.js'
|
|
18
20
|
export * from './wirings/mcp/index.js'
|
|
19
21
|
export * from './wirings/cli/index.js'
|
|
20
22
|
export * from './errors/index.js'
|
package/src/middleware-runner.ts
CHANGED
package/src/permissions.ts
CHANGED
package/src/pikku-state.ts
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
CorePikkuPermission,
|
|
22
22
|
} from './function/functions.types.js'
|
|
23
23
|
import {
|
|
24
|
-
|
|
24
|
+
QueueWorkersMeta,
|
|
25
25
|
CoreQueueWorker,
|
|
26
26
|
} from './wirings/queue/queue.types.js'
|
|
27
27
|
import {
|
|
@@ -33,6 +33,10 @@ import {
|
|
|
33
33
|
MCPPromptMeta,
|
|
34
34
|
} from './wirings/mcp/mcp.types.js'
|
|
35
35
|
import { CLIMeta, CLIProgramState } from './wirings/cli/cli.types.js'
|
|
36
|
+
import {
|
|
37
|
+
CoreWorkflow,
|
|
38
|
+
WorkflowsMeta,
|
|
39
|
+
} from './wirings/workflow/workflow.types.js'
|
|
36
40
|
|
|
37
41
|
interface PikkuState {
|
|
38
42
|
function: {
|
|
@@ -65,7 +69,11 @@ interface PikkuState {
|
|
|
65
69
|
}
|
|
66
70
|
queue: {
|
|
67
71
|
registrations: Map<string, CoreQueueWorker>
|
|
68
|
-
meta:
|
|
72
|
+
meta: QueueWorkersMeta
|
|
73
|
+
}
|
|
74
|
+
workflows: {
|
|
75
|
+
registrations: Map<string, CoreWorkflow>
|
|
76
|
+
meta: WorkflowsMeta
|
|
69
77
|
}
|
|
70
78
|
mcp: {
|
|
71
79
|
resources: Map<string, CoreMCPResource>
|
|
@@ -166,6 +174,10 @@ export const resetPikkuState = () => {
|
|
|
166
174
|
registrations: new Map(),
|
|
167
175
|
meta: {},
|
|
168
176
|
},
|
|
177
|
+
workflows: {
|
|
178
|
+
registrations: new Map(),
|
|
179
|
+
meta: {},
|
|
180
|
+
},
|
|
169
181
|
mcp: {
|
|
170
182
|
resources: new Map(),
|
|
171
183
|
resourcesMeta: {} as MCPResourceMeta,
|
package/src/services/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ export * from './secret-service.js'
|
|
|
10
10
|
export * from './variables-service.js'
|
|
11
11
|
export * from './schema-service.js'
|
|
12
12
|
export * from './user-session-service.js'
|
|
13
|
+
export * from './scheduler-service.js'
|
|
13
14
|
|
|
14
15
|
// Local implementations
|
|
15
16
|
export * from './local-secrets.js'
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { CoreUserSession } from '../types/core.types.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal metadata for listing scheduled tasks
|
|
5
|
+
*/
|
|
6
|
+
export interface ScheduledTaskSummary {
|
|
7
|
+
/** Unique task ID */
|
|
8
|
+
taskId: string
|
|
9
|
+
/** RPC function name to invoke */
|
|
10
|
+
rpcName: string
|
|
11
|
+
/** Scheduled execution time */
|
|
12
|
+
scheduledFor: Date
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Full metadata for a scheduled task retrieved from the queue
|
|
17
|
+
*/
|
|
18
|
+
export interface ScheduledTaskInfo extends ScheduledTaskSummary {
|
|
19
|
+
/** Data to pass to the RPC function */
|
|
20
|
+
data?: any
|
|
21
|
+
/** User session */
|
|
22
|
+
session?: CoreUserSession
|
|
23
|
+
/** Task status */
|
|
24
|
+
status?: 'scheduled' | 'active' | 'completed' | 'failed'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Abstract scheduler service for persistent scheduled task management
|
|
29
|
+
* Implementations use queue backends (pg-boss, BullMQ) for distributed scheduling
|
|
30
|
+
*/
|
|
31
|
+
export abstract class SchedulerService {
|
|
32
|
+
/**
|
|
33
|
+
* Initialize the scheduler service
|
|
34
|
+
*/
|
|
35
|
+
abstract init(): Promise<void>
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Schedule a one-off delayed RPC call
|
|
39
|
+
* @param delay - Delay before execution (number in milliseconds or duration string like "5h", "30m")
|
|
40
|
+
* @param rpcName - RPC function name to invoke
|
|
41
|
+
* @param data - Data to pass to the RPC function
|
|
42
|
+
* @param session - Optional user session
|
|
43
|
+
* @returns Task ID
|
|
44
|
+
*/
|
|
45
|
+
abstract scheduleRPC(
|
|
46
|
+
delay: number | string,
|
|
47
|
+
rpcName: string,
|
|
48
|
+
data?: any,
|
|
49
|
+
session?: CoreUserSession
|
|
50
|
+
): Promise<string>
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Unschedule a task by ID
|
|
54
|
+
* @param taskId - Task ID
|
|
55
|
+
* @returns Success status
|
|
56
|
+
*/
|
|
57
|
+
abstract unschedule(taskId: string): Promise<boolean>
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get a scheduled task by ID with full details
|
|
61
|
+
* @param taskId - Task ID
|
|
62
|
+
* @returns Task info or null if not found
|
|
63
|
+
*/
|
|
64
|
+
abstract getTask(taskId: string): Promise<ScheduledTaskInfo | null>
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get all scheduled tasks with minimal info
|
|
68
|
+
* @returns Array of task summaries with taskId, rpcName, and schedule
|
|
69
|
+
*/
|
|
70
|
+
abstract getAllTasks(): Promise<ScheduledTaskSummary[]>
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Close any open connections
|
|
74
|
+
*/
|
|
75
|
+
abstract close(): Promise<void>
|
|
76
|
+
}
|
package/src/time-utils.ts
CHANGED
|
@@ -30,3 +30,78 @@ export const getRelativeTimeOffsetFromNow = (
|
|
|
30
30
|
): Date => {
|
|
31
31
|
return new Date(Date.now() + getRelativeTimeOffset(relativeTime))
|
|
32
32
|
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get duration in milliseconds from a string or number
|
|
36
|
+
* @param duration
|
|
37
|
+
* @returns
|
|
38
|
+
*/
|
|
39
|
+
export const getDurationInMilliseconds = (
|
|
40
|
+
duration: string | number
|
|
41
|
+
): number => {
|
|
42
|
+
if (typeof duration === 'number') {
|
|
43
|
+
return duration
|
|
44
|
+
}
|
|
45
|
+
return parseDurationString(duration)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Parse a duration string to milliseconds
|
|
50
|
+
* Supports formats like: '2s', '5s', '5sec', '5seconds', '5m', '5min', '5minutes', '1h', '1hour', '2d', '2day', '1w', '1week'
|
|
51
|
+
*
|
|
52
|
+
* @param duration - Duration string (e.g., '2s', '5min', '2hours', '1day')
|
|
53
|
+
* @returns Duration in milliseconds
|
|
54
|
+
*/
|
|
55
|
+
export const parseDurationString = (duration: string): number => {
|
|
56
|
+
const match = duration.match(
|
|
57
|
+
/^(\d+)(ms|milliseconds?|s|sec|seconds?|m|min|minutes?|h|hour|hours?|d|day|days?|w|week|weeks?|y|year|years?)$/
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
if (!match) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`Invalid duration format: ${duration}. Use formats like '2s', '5s', '5min', '1hour', '2days', '1week'`
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const value = parseInt(match[1], 10)
|
|
67
|
+
const unitStr = match[2]
|
|
68
|
+
|
|
69
|
+
// Handle milliseconds specially
|
|
70
|
+
if (
|
|
71
|
+
unitStr === 'ms' ||
|
|
72
|
+
unitStr === 'millisecond' ||
|
|
73
|
+
unitStr === 'milliseconds'
|
|
74
|
+
) {
|
|
75
|
+
return value
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Map string variations to TimeUnit
|
|
79
|
+
let unit: TimeUnit
|
|
80
|
+
if (
|
|
81
|
+
unitStr === 's' ||
|
|
82
|
+
unitStr === 'sec' ||
|
|
83
|
+
unitStr === 'second' ||
|
|
84
|
+
unitStr === 'seconds'
|
|
85
|
+
) {
|
|
86
|
+
unit = 'second'
|
|
87
|
+
} else if (
|
|
88
|
+
unitStr === 'm' ||
|
|
89
|
+
unitStr === 'min' ||
|
|
90
|
+
unitStr === 'minute' ||
|
|
91
|
+
unitStr === 'minutes'
|
|
92
|
+
) {
|
|
93
|
+
unit = 'minute'
|
|
94
|
+
} else if (unitStr === 'h' || unitStr === 'hour' || unitStr === 'hours') {
|
|
95
|
+
unit = 'hour'
|
|
96
|
+
} else if (unitStr === 'd' || unitStr === 'day' || unitStr === 'days') {
|
|
97
|
+
unit = 'day'
|
|
98
|
+
} else if (unitStr === 'w' || unitStr === 'week' || unitStr === 'weeks') {
|
|
99
|
+
unit = 'week'
|
|
100
|
+
} else if (unitStr === 'y' || unitStr === 'year' || unitStr === 'years') {
|
|
101
|
+
unit = 'year'
|
|
102
|
+
} else {
|
|
103
|
+
throw new Error(`Unknown time unit: ${unitStr}`)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return getRelativeTimeOffset({ value, unit })
|
|
107
|
+
}
|
package/src/types/core.types.ts
CHANGED
|
@@ -8,8 +8,15 @@ import { PikkuChannel } from '../wirings/channel/channel.types.js'
|
|
|
8
8
|
import { PikkuRPC } from '../wirings/rpc/rpc-types.js'
|
|
9
9
|
import { PikkuMCP } from '../wirings/mcp/mcp.types.js'
|
|
10
10
|
import { PikkuScheduledTask } from '../wirings/scheduler/scheduler.types.js'
|
|
11
|
-
import { PikkuQueue } from '../wirings/queue/queue.types.js'
|
|
11
|
+
import { PikkuQueue, QueueService } from '../wirings/queue/queue.types.js'
|
|
12
12
|
import { PikkuCLI } from '../wirings/cli/cli.types.js'
|
|
13
|
+
import {
|
|
14
|
+
PikkuWorkflowInteraction,
|
|
15
|
+
WorkflowService,
|
|
16
|
+
WorkflowServiceConfig,
|
|
17
|
+
WorkflowStepInteraction,
|
|
18
|
+
} from '../wirings/workflow/workflow.types.js'
|
|
19
|
+
import { SchedulerService } from '../services/scheduler-service.js'
|
|
13
20
|
|
|
14
21
|
export enum PikkuWiringTypes {
|
|
15
22
|
http = 'http',
|
|
@@ -19,6 +26,7 @@ export enum PikkuWiringTypes {
|
|
|
19
26
|
queue = 'queue',
|
|
20
27
|
mcp = 'mcp',
|
|
21
28
|
cli = 'cli',
|
|
29
|
+
workflow = 'workflow',
|
|
22
30
|
}
|
|
23
31
|
|
|
24
32
|
export interface FunctionServicesMeta {
|
|
@@ -73,6 +81,7 @@ export type FunctionRuntimeMeta = {
|
|
|
73
81
|
inputSchemaName: string | null
|
|
74
82
|
outputSchemaName: string | null
|
|
75
83
|
expose?: boolean
|
|
84
|
+
internal?: boolean
|
|
76
85
|
}
|
|
77
86
|
|
|
78
87
|
export type FunctionMeta = FunctionRuntimeMeta &
|
|
@@ -137,6 +146,8 @@ export type CoreConfig<Config extends Record<string, unknown> = {}> = {
|
|
|
137
146
|
logLevel?: LogLevel
|
|
138
147
|
/** Secrets used by the application (optional). */
|
|
139
148
|
secrets?: {}
|
|
149
|
+
|
|
150
|
+
workflow?: WorkflowServiceConfig
|
|
140
151
|
} & Config
|
|
141
152
|
|
|
142
153
|
/**
|
|
@@ -158,6 +169,12 @@ export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
|
|
|
158
169
|
logger: Logger
|
|
159
170
|
/** The variable service to be used */
|
|
160
171
|
variables: VariablesService
|
|
172
|
+
/** The workflow orchestrator service */
|
|
173
|
+
workflowService?: WorkflowService
|
|
174
|
+
/** The queue service */
|
|
175
|
+
queueService?: QueueService
|
|
176
|
+
/** The scheduler service */
|
|
177
|
+
schedulerService?: SchedulerService
|
|
161
178
|
}
|
|
162
179
|
|
|
163
180
|
/**
|
|
@@ -171,6 +188,8 @@ export type PikkuInteraction<In = unknown, Out = unknown> = Partial<{
|
|
|
171
188
|
scheduledTask: PikkuScheduledTask
|
|
172
189
|
queue: PikkuQueue
|
|
173
190
|
cli: PikkuCLI
|
|
191
|
+
workflow: PikkuWorkflowInteraction
|
|
192
|
+
workflowStep: WorkflowStepInteraction
|
|
174
193
|
}>
|
|
175
194
|
|
|
176
195
|
/**
|
|
@@ -344,3 +363,13 @@ export type PikkuDocs = {
|
|
|
344
363
|
tags?: string[]
|
|
345
364
|
errors?: string[]
|
|
346
365
|
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Serialized error for storage
|
|
369
|
+
*/
|
|
370
|
+
export interface SerializedError {
|
|
371
|
+
message: string
|
|
372
|
+
stack?: string
|
|
373
|
+
code?: string
|
|
374
|
+
[key: string]: any
|
|
375
|
+
}
|