@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.
Files changed (81) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/function/function-runner.js +2 -2
  3. package/dist/function/functions.types.d.ts +1 -0
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.js +2 -0
  6. package/dist/middleware/auth-apikey.d.ts +1 -0
  7. package/dist/middleware/auth-bearer.d.ts +1 -0
  8. package/dist/middleware/auth-cookie.d.ts +1 -0
  9. package/dist/middleware/timeout.d.ts +1 -0
  10. package/dist/middleware-runner.js +1 -0
  11. package/dist/permissions.js +1 -0
  12. package/dist/pikku-state.d.ts +7 -2
  13. package/dist/pikku-state.js +4 -0
  14. package/dist/services/index.d.ts +1 -0
  15. package/dist/services/index.js +1 -0
  16. package/dist/services/scheduler-service.d.ts +63 -0
  17. package/dist/services/scheduler-service.js +6 -0
  18. package/dist/time-utils.d.ts +14 -0
  19. package/dist/time-utils.js +62 -0
  20. package/dist/types/core.types.d.ts +24 -2
  21. package/dist/types/core.types.js +1 -0
  22. package/dist/wirings/channel/channel-common.d.ts +28 -0
  23. package/dist/wirings/channel/channel-common.js +42 -0
  24. package/dist/wirings/channel/channel-handler.js +37 -11
  25. package/dist/wirings/channel/channel-runner.js +9 -4
  26. package/dist/wirings/channel/channel.types.d.ts +8 -2
  27. package/dist/wirings/channel/local/local-channel-handler.js +3 -0
  28. package/dist/wirings/channel/local/local-channel-runner.js +47 -14
  29. package/dist/wirings/channel/serverless/serverless-channel-runner.js +90 -41
  30. package/dist/wirings/cli/channel/cli-channel-runner.js +10 -1
  31. package/dist/wirings/cli/cli-runner.d.ts +1 -1
  32. package/dist/wirings/cli/cli-runner.js +1 -1
  33. package/dist/wirings/http/http-runner.js +0 -1
  34. package/dist/wirings/mcp/mcp-runner.js +3 -2
  35. package/dist/wirings/queue/queue-runner.js +6 -3
  36. package/dist/wirings/queue/queue.types.d.ts +1 -3
  37. package/dist/wirings/rpc/index.d.ts +1 -1
  38. package/dist/wirings/rpc/index.js +1 -1
  39. package/dist/wirings/rpc/rpc-runner.d.ts +15 -0
  40. package/dist/wirings/rpc/rpc-runner.js +38 -3
  41. package/dist/wirings/rpc/rpc-types.d.ts +1 -0
  42. package/dist/wirings/workflow/index.d.ts +6 -0
  43. package/dist/wirings/workflow/index.js +6 -0
  44. package/dist/wirings/workflow/pikku-workflow-service.d.ts +152 -0
  45. package/dist/wirings/workflow/pikku-workflow-service.js +448 -0
  46. package/dist/wirings/workflow/workflow-runner.d.ts +35 -0
  47. package/dist/wirings/workflow/workflow-runner.js +68 -0
  48. package/dist/wirings/workflow/workflow.types.d.ts +247 -0
  49. package/dist/wirings/workflow/workflow.types.js +1 -0
  50. package/package.json +2 -3
  51. package/src/function/function-runner.ts +2 -2
  52. package/src/function/functions.types.ts +1 -0
  53. package/src/index.ts +2 -0
  54. package/src/middleware-runner.ts +1 -0
  55. package/src/permissions.ts +1 -0
  56. package/src/pikku-state.ts +14 -2
  57. package/src/services/index.ts +1 -0
  58. package/src/services/scheduler-service.ts +76 -0
  59. package/src/time-utils.ts +75 -0
  60. package/src/types/core.types.ts +30 -1
  61. package/src/wirings/channel/channel-common.ts +80 -0
  62. package/src/wirings/channel/channel-handler.ts +48 -18
  63. package/src/wirings/channel/channel-runner.ts +15 -4
  64. package/src/wirings/channel/channel.types.ts +12 -2
  65. package/src/wirings/channel/local/local-channel-handler.ts +3 -0
  66. package/src/wirings/channel/local/local-channel-runner.ts +48 -36
  67. package/src/wirings/channel/serverless/serverless-channel-runner.ts +134 -66
  68. package/src/wirings/cli/channel/cli-channel-runner.ts +13 -1
  69. package/src/wirings/cli/cli-runner.ts +2 -2
  70. package/src/wirings/http/http-runner.ts +0 -2
  71. package/src/wirings/mcp/mcp-runner.ts +5 -2
  72. package/src/wirings/queue/queue-runner.ts +14 -6
  73. package/src/wirings/queue/queue.types.ts +1 -4
  74. package/src/wirings/rpc/index.ts +1 -1
  75. package/src/wirings/rpc/rpc-runner.ts +57 -4
  76. package/src/wirings/rpc/rpc-types.ts +1 -0
  77. package/src/wirings/workflow/index.ts +22 -0
  78. package/src/wirings/workflow/pikku-workflow-service.ts +756 -0
  79. package/src/wirings/workflow/workflow-runner.ts +85 -0
  80. package/src/wirings/workflow/workflow.types.ts +332 -0
  81. 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.10.1",
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
 
@@ -187,6 +187,7 @@ export type CorePikkuFunctionConfig<
187
187
  > = {
188
188
  name?: string
189
189
  expose?: boolean
190
+ internal?: boolean
190
191
  func: PikkuFunction
191
192
  auth?: boolean
192
193
  permissions?: CorePermissionGroup<PikkuPermission>
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'
@@ -122,6 +122,7 @@ const middlewareCache: Record<
122
122
  [PikkuWiringTypes.scheduler]: {},
123
123
  [PikkuWiringTypes.mcp]: {},
124
124
  [PikkuWiringTypes.cli]: {},
125
+ [PikkuWiringTypes.workflow]: {},
125
126
  }
126
127
 
127
128
  /**
@@ -197,6 +197,7 @@ const combinedPermissionsCache: Record<
197
197
  [PikkuWiringTypes.scheduler]: {},
198
198
  [PikkuWiringTypes.mcp]: {},
199
199
  [PikkuWiringTypes.cli]: {},
200
+ [PikkuWiringTypes.workflow]: {},
200
201
  }
201
202
 
202
203
  /**
@@ -21,7 +21,7 @@ import {
21
21
  CorePikkuPermission,
22
22
  } from './function/functions.types.js'
23
23
  import {
24
- queueWorkersMeta,
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: queueWorkersMeta
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,
@@ -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
+ }
@@ -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
+ }