@pikku/core 0.11.0 → 0.11.1

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 (105) hide show
  1. package/CHANGELOG.md +11 -1
  2. package/dist/errors/errors.d.ts +1 -1
  3. package/dist/errors/errors.js +3 -3
  4. package/dist/function/function-runner.d.ts +6 -7
  5. package/dist/function/function-runner.js +35 -16
  6. package/dist/function/functions.types.d.ts +22 -30
  7. package/dist/function/functions.types.js +9 -6
  8. package/dist/handle-error.d.ts +1 -1
  9. package/dist/handle-error.js +1 -1
  10. package/dist/middleware/auth-apikey.js +3 -3
  11. package/dist/middleware/auth-bearer.js +3 -3
  12. package/dist/middleware/auth-cookie.js +5 -5
  13. package/dist/middleware/timeout.js +3 -2
  14. package/dist/middleware-runner.d.ts +3 -6
  15. package/dist/middleware-runner.js +11 -12
  16. package/dist/permissions.d.ts +6 -6
  17. package/dist/permissions.js +15 -16
  18. package/dist/services/user-session-service.d.ts +7 -3
  19. package/dist/services/user-session-service.js +8 -1
  20. package/dist/types/core.types.d.ts +29 -38
  21. package/dist/types/core.types.js +3 -14
  22. package/dist/utils.d.ts +1 -1
  23. package/dist/utils.js +3 -3
  24. package/dist/wirings/channel/channel-common.js +2 -3
  25. package/dist/wirings/channel/channel-handler.d.ts +3 -2
  26. package/dist/wirings/channel/channel-handler.js +6 -9
  27. package/dist/wirings/channel/channel-runner.d.ts +2 -2
  28. package/dist/wirings/channel/channel.types.d.ts +10 -5
  29. package/dist/wirings/channel/local/local-channel-runner.d.ts +1 -1
  30. package/dist/wirings/channel/local/local-channel-runner.js +19 -26
  31. package/dist/wirings/channel/serverless/serverless-channel-runner.d.ts +1 -1
  32. package/dist/wirings/channel/serverless/serverless-channel-runner.js +41 -46
  33. package/dist/wirings/cli/cli-runner.d.ts +5 -5
  34. package/dist/wirings/cli/cli-runner.js +10 -28
  35. package/dist/wirings/cli/cli.types.d.ts +15 -11
  36. package/dist/wirings/http/http-runner.d.ts +6 -6
  37. package/dist/wirings/http/http-runner.js +25 -39
  38. package/dist/wirings/http/http.types.d.ts +5 -10
  39. package/dist/wirings/mcp/mcp-runner.d.ts +2 -2
  40. package/dist/wirings/mcp/mcp-runner.js +12 -19
  41. package/dist/wirings/mcp/mcp.types.d.ts +6 -0
  42. package/dist/wirings/queue/queue-runner.d.ts +3 -3
  43. package/dist/wirings/queue/queue-runner.js +7 -17
  44. package/dist/wirings/queue/queue.types.d.ts +4 -11
  45. package/dist/wirings/rpc/rpc-runner.d.ts +10 -8
  46. package/dist/wirings/rpc/rpc-runner.js +36 -47
  47. package/dist/wirings/scheduler/scheduler-runner.d.ts +3 -3
  48. package/dist/wirings/scheduler/scheduler-runner.js +13 -24
  49. package/dist/wirings/scheduler/scheduler.types.d.ts +3 -8
  50. package/dist/wirings/workflow/index.d.ts +11 -2
  51. package/dist/wirings/workflow/index.js +10 -2
  52. package/dist/wirings/workflow/pikku-workflow-service.d.ts +12 -4
  53. package/dist/wirings/workflow/pikku-workflow-service.js +61 -79
  54. package/dist/wirings/workflow/workflow-runner.d.ts +3 -4
  55. package/dist/wirings/workflow/workflow-runner.js +11 -14
  56. package/dist/wirings/workflow/workflow.types.d.ts +114 -29
  57. package/package.json +2 -2
  58. package/src/errors/errors.ts +3 -3
  59. package/src/factory-functions.test.ts +9 -36
  60. package/src/function/function-runner.test.ts +57 -68
  61. package/src/function/function-runner.ts +51 -28
  62. package/src/function/function-runner.ts.bak +188 -0
  63. package/src/function/functions.types.ts +68 -51
  64. package/src/handle-error.ts +1 -1
  65. package/src/middleware/auth-apikey.test.ts +360 -0
  66. package/src/middleware/auth-apikey.ts +3 -7
  67. package/src/middleware/auth-bearer.test.ts +447 -0
  68. package/src/middleware/auth-bearer.ts +27 -33
  69. package/src/middleware/auth-cookie.test.ts +525 -0
  70. package/src/middleware/auth-cookie.ts +6 -6
  71. package/src/middleware/timeout.ts +4 -2
  72. package/src/middleware-runner.test.ts +58 -127
  73. package/src/middleware-runner.ts +13 -16
  74. package/src/permissions.test.ts +41 -45
  75. package/src/permissions.ts +21 -21
  76. package/src/services/user-session-service.ts +14 -4
  77. package/src/types/core.types.ts +67 -55
  78. package/src/utils.ts +4 -4
  79. package/src/wirings/channel/channel-common.ts +4 -7
  80. package/src/wirings/channel/channel-handler.ts +16 -22
  81. package/src/wirings/channel/channel-runner.ts +2 -2
  82. package/src/wirings/channel/channel.types.ts +14 -11
  83. package/src/wirings/channel/local/local-channel-runner.test.ts +5 -3
  84. package/src/wirings/channel/local/local-channel-runner.ts +23 -38
  85. package/src/wirings/channel/serverless/serverless-channel-runner.ts +51 -77
  86. package/src/wirings/cli/cli-runner.test.ts +7 -7
  87. package/src/wirings/cli/cli-runner.ts +24 -62
  88. package/src/wirings/cli/cli.types.ts +22 -16
  89. package/src/wirings/http/http-runner.test.ts +8 -8
  90. package/src/wirings/http/http-runner.ts +29 -56
  91. package/src/wirings/http/http.types.ts +5 -14
  92. package/src/wirings/mcp/mcp-runner.ts +23 -48
  93. package/src/wirings/mcp/mcp.types.ts +6 -0
  94. package/src/wirings/queue/queue-runner.test.ts +641 -0
  95. package/src/wirings/queue/queue-runner.ts +10 -32
  96. package/src/wirings/queue/queue.types.ts +4 -11
  97. package/src/wirings/rpc/rpc-runner.ts +41 -55
  98. package/src/wirings/scheduler/scheduler-runner.test.ts +627 -0
  99. package/src/wirings/scheduler/scheduler-runner.ts +24 -50
  100. package/src/wirings/scheduler/scheduler.types.ts +3 -9
  101. package/src/wirings/workflow/index.ts +24 -3
  102. package/src/wirings/workflow/pikku-workflow-service.ts +83 -93
  103. package/src/wirings/workflow/workflow-runner.ts +12 -25
  104. package/src/wirings/workflow/workflow.types.ts +144 -54
  105. package/tsconfig.tsbuildinfo +1 -1
@@ -1,4 +1,4 @@
1
- import { CoreConfig, CoreSingletonServices, CreateSessionServices, SerializedError } from '../../types/core.types.js';
1
+ import { CoreConfig, CoreSingletonServices, CreateWireServices, SerializedError } from '../../types/core.types.js';
2
2
  import type { StepState, WorkflowRun, WorkflowStatus, WorkflowService, WorkflowStepOptions } from './workflow.types.js';
3
3
  export declare class WorkflowServiceNotInitialized extends Error {
4
4
  }
@@ -13,9 +13,9 @@ export declare class WorkflowStepNameNotString extends Error {
13
13
  export declare abstract class PikkuWorkflowService implements WorkflowService {
14
14
  private config;
15
15
  private singletonServices;
16
- private createSessionServices;
16
+ private createWireServices;
17
17
  constructor();
18
- setServices(singletonServices: CoreSingletonServices, createSessionServices: CreateSessionServices, { workflow }: CoreConfig): void;
18
+ setServices(singletonServices: CoreSingletonServices, createWireServices: CreateWireServices, { workflow }: CoreConfig): void;
19
19
  /**
20
20
  * Create a new workflow run
21
21
  * @param name - Workflow name
@@ -103,6 +103,14 @@ export declare abstract class PikkuWorkflowService implements WorkflowService {
103
103
  * @returns Function result
104
104
  */
105
105
  abstract withRunLock<T>(id: string, fn: () => Promise<T>): Promise<T>;
106
+ /**
107
+ * Execute function within a step lock to prevent concurrent step execution
108
+ * @param runId - Run ID
109
+ * @param stepName - Step name
110
+ * @param fn - Function to execute
111
+ * @returns Function result
112
+ */
113
+ abstract withStepLock<T>(runId: string, stepName: string, fn: () => Promise<T>): Promise<T>;
106
114
  /**
107
115
  * Close any open connections
108
116
  */
@@ -146,7 +154,7 @@ export declare abstract class PikkuWorkflowService implements WorkflowService {
146
154
  private inlineStep;
147
155
  private sleepStep;
148
156
  private cancelWorkflow;
149
- private createWorkflowInteraction;
157
+ private createWorkflowWire;
150
158
  private verifyStepName;
151
159
  private getConfig;
152
160
  }
@@ -1,7 +1,6 @@
1
1
  import { runPikkuFunc } from '../../function/function-runner.js';
2
2
  import { pikkuState } from '../../pikku-state.js';
3
3
  import { getDurationInMilliseconds } from '../../time-utils.js';
4
- import { PikkuWiringTypes, } from '../../types/core.types.js';
5
4
  import { WorkflowAsyncException, WorkflowCancelledException, WorkflowNotFoundError, WorkflowRunNotFound, } from './workflow-runner.js';
6
5
  export class WorkflowServiceNotInitialized extends Error {
7
6
  }
@@ -18,11 +17,11 @@ export class WorkflowStepNameNotString extends Error {
18
17
  export class PikkuWorkflowService {
19
18
  config;
20
19
  singletonServices;
21
- createSessionServices;
20
+ createWireServices;
22
21
  constructor() { }
23
- setServices(singletonServices, createSessionServices, { workflow }) {
22
+ setServices(singletonServices, createWireServices, { workflow }) {
24
23
  this.singletonServices = singletonServices;
25
- this.createSessionServices = createSessionServices;
24
+ this.createWireServices = createWireServices;
26
25
  this.config = {
27
26
  retries: workflow?.retries ?? 0,
28
27
  retryDelay: workflow?.retryDelay ?? 0,
@@ -84,49 +83,30 @@ export class PikkuWorkflowService {
84
83
  return { runId };
85
84
  }
86
85
  async runWorkflowJob(runId, rpcService) {
87
- // Get the run
88
86
  const run = await this.getRun(runId);
89
87
  if (!run) {
90
88
  throw new WorkflowRunNotFound(runId);
91
89
  }
92
- // Get workflow registration
93
90
  const registrations = pikkuState('workflows', 'registrations');
94
91
  const workflow = registrations.get(run.workflow);
95
92
  if (!workflow) {
96
93
  throw new WorkflowNotFoundError(run.workflow);
97
94
  }
98
- // Use lock to prevent concurrent execution
99
95
  await this.withRunLock(runId, async () => {
100
- // Create workflow interaction object
101
- const workflowInteraction = this.createWorkflowInteraction(run.workflow, runId, rpcService);
102
- // Get function metadata
103
96
  const meta = pikkuState('workflows', 'meta');
104
97
  const workflowMeta = meta[run.workflow];
105
- // Execute workflow function with workflow interaction
98
+ const workflowWire = this.createWorkflowWire(run.workflow, runId, rpcService);
99
+ const wire = { workflow: workflowWire };
106
100
  try {
107
- const getAllServices = () => {
108
- let sessionServices = {};
109
- const interaction = { workflow: workflowInteraction };
110
- if (this.createSessionServices) {
111
- sessionServices = this.createSessionServices(this.singletonServices, interaction, undefined);
112
- }
113
- return {
114
- ...this.singletonServices,
115
- ...sessionServices,
116
- ...interaction,
117
- };
118
- };
119
- const result = await runPikkuFunc(PikkuWiringTypes.workflow, workflowMeta.workflowName, workflowMeta.pikkuFuncName, {
101
+ const result = await runPikkuFunc('workflow', workflowMeta.workflowName, workflowMeta.pikkuFuncName, {
120
102
  singletonServices: this.singletonServices,
121
- interaction: { workflow: workflowInteraction },
122
- getAllServices,
103
+ wire,
104
+ createWireServices: this.createWireServices,
123
105
  data: () => run.input,
124
106
  });
125
- // Workflow completed successfully
126
107
  await this.updateRunStatus(runId, 'completed', result);
127
108
  }
128
109
  catch (error) {
129
- // Check if it's a WorkflowAsyncException
130
110
  if (error instanceof WorkflowAsyncException) {
131
111
  // Normal - workflow paused for step execution
132
112
  throw error;
@@ -136,7 +116,6 @@ export class PikkuWorkflowService {
136
116
  // Workflow was cancelled - status already updated, just rethrow
137
117
  throw error;
138
118
  }
139
- // Real error - mark as failed
140
119
  await this.updateRunStatus(runId, 'failed', undefined, {
141
120
  message: error.message,
142
121
  stack: error.stack,
@@ -151,56 +130,59 @@ export class PikkuWorkflowService {
151
130
  * Handles idempotency, RPC execution, result storage, retry logic, and orchestrator triggering
152
131
  */
153
132
  async executeWorkflowStep(runId, stepName, rpcName, data, rpcService) {
154
- // Get step state
155
- let stepState = await this.getStepState(runId, stepName);
156
- // Idempotency - if already succeeded, resume orchestrator and return
157
- if (stepState.status === 'succeeded') {
158
- await this.resumeWorkflow(runId);
159
- return;
160
- }
161
- // Log warning if already running (race condition)
162
- if (stepState.status === 'running') {
163
- return;
164
- }
165
- // If status is 'failed', this is a retry - create new attempt history
166
- if (stepState.status === 'failed') {
167
- stepState = await this.createRetryAttempt(stepState.stepId, 'running');
168
- }
169
- if (stepState.status !== 'pending') {
170
- // Mark step as running (pending or failed status)
171
- await this.setStepRunning(stepState.stepId);
172
- }
173
- try {
174
- // Execute RPC with workflow step context
175
- const result = await rpcService.rpcWithInteraction(rpcName, data, {
176
- workflowStep: {
177
- runId,
178
- stepId: stepState.stepId,
179
- attemptCount: stepState.attemptCount,
180
- },
181
- });
182
- // Store result and mark succeeded
183
- await this.setStepResult(stepState.stepId, result);
184
- // Resume orchestrator to continue workflow
185
- try {
133
+ // Use step-level lock to prevent concurrent execution of same step
134
+ await this.withStepLock(runId, stepName, async () => {
135
+ // Get step state
136
+ let stepState = await this.getStepState(runId, stepName);
137
+ // Idempotency - if already succeeded, resume orchestrator and return
138
+ if (stepState.status === 'succeeded') {
186
139
  await this.resumeWorkflow(runId);
140
+ return;
187
141
  }
188
- catch (resumeError) {
189
- throw resumeError;
142
+ // Log warning if already running (race condition)
143
+ if (stepState.status === 'running') {
144
+ return;
190
145
  }
191
- }
192
- catch (error) {
193
- // Store error and mark failed
194
- await this.setStepError(stepState.stepId, error);
195
- const maxAttempts = (stepState.retries ?? 0) + 1;
196
- const retriesExhausted = stepState.attemptCount >= maxAttempts;
197
- if (retriesExhausted) {
198
- // No more retries - resume orchestrator to mark workflow as failed
199
- await this.resumeWorkflow(runId);
146
+ // If status is 'failed', this is a retry - create new attempt history
147
+ if (stepState.status === 'failed') {
148
+ stepState = await this.createRetryAttempt(stepState.stepId, 'running');
200
149
  }
201
- // Always throw so queue knows the job failed and can retry if needed
202
- throw error;
203
- }
150
+ if (stepState.status !== 'pending') {
151
+ // Mark step as running (pending or failed status)
152
+ await this.setStepRunning(stepState.stepId);
153
+ }
154
+ try {
155
+ // Execute RPC with workflow step context
156
+ const result = await rpcService.rpcWithWire(rpcName, data, {
157
+ workflowStep: {
158
+ runId,
159
+ stepId: stepState.stepId,
160
+ attemptCount: stepState.attemptCount,
161
+ },
162
+ });
163
+ // Store result and mark succeeded
164
+ await this.setStepResult(stepState.stepId, result);
165
+ // Resume orchestrator to continue workflow
166
+ try {
167
+ await this.resumeWorkflow(runId);
168
+ }
169
+ catch (resumeError) {
170
+ throw resumeError;
171
+ }
172
+ }
173
+ catch (error) {
174
+ // Store error and mark failed
175
+ await this.setStepError(stepState.stepId, error);
176
+ const maxAttempts = (stepState.retries ?? 0) + 1;
177
+ const retriesExhausted = stepState.attemptCount >= maxAttempts;
178
+ if (retriesExhausted) {
179
+ // No more retries - resume orchestrator to mark workflow as failed
180
+ await this.resumeWorkflow(runId);
181
+ }
182
+ // Always throw so queue knows the job failed and can retry if needed
183
+ throw error;
184
+ }
185
+ });
204
186
  }
205
187
  /**
206
188
  * Orchestrate workflow execution (called by orchestrator)
@@ -287,7 +269,7 @@ export class PikkuWorkflowService {
287
269
  const retryDelay = stepOptions?.retryDelay;
288
270
  const attemptCount = stepState.attemptCount;
289
271
  try {
290
- const result = await rpcService.rpcWithInteraction(rpcName, data, {
272
+ const result = await rpcService.rpcWithWire(rpcName, data, {
291
273
  workflowStep: {
292
274
  runId,
293
275
  stepId: stepState.stepId,
@@ -407,8 +389,8 @@ export class PikkuWorkflowService {
407
389
  // Throw cancellation exception to stop workflow execution
408
390
  throw new WorkflowCancelledException(runId, reason);
409
391
  }
410
- createWorkflowInteraction(workflowName, runId, rpcService) {
411
- const workflowInteraction = {
392
+ createWorkflowWire(workflowName, runId, rpcService) {
393
+ const workflowWire = {
412
394
  workflowName,
413
395
  runId,
414
396
  getRun: async () => (await this.getRun(runId)),
@@ -432,7 +414,7 @@ export class PikkuWorkflowService {
432
414
  await this.cancelWorkflow(runId, reason);
433
415
  },
434
416
  };
435
- return workflowInteraction;
417
+ return workflowWire;
436
418
  }
437
419
  verifyStepName(stepName) {
438
420
  if (typeof stepName !== 'string') {
@@ -1,5 +1,3 @@
1
- import type { CoreWorkflow } from './workflow.types.js';
2
- import type { CorePikkuFunctionConfig } from '../../function/functions.types.js';
3
1
  import { PikkuError } from '../../errors/error-handler.js';
4
2
  /**
5
3
  * Exception thrown when workflow needs to pause for async step
@@ -30,6 +28,7 @@ export declare class WorkflowRunNotFound extends PikkuError {
30
28
  constructor(runId: string);
31
29
  }
32
30
  /**
33
- * Register a workflow with the system
31
+ * Add a workflow to the system
32
+ * This is called by the generated workflow wirings
34
33
  */
35
- export declare const wireWorkflow: <PikkuFunctionConfig extends CorePikkuFunctionConfig<any, any, any> = CorePikkuFunctionConfig<any, any, any>>(workflow: CoreWorkflow<PikkuFunctionConfig>) => void;
34
+ export declare const addWorkflow: (workflowName: string, workflowFunc: any) => void;
@@ -44,25 +44,22 @@ export class WorkflowRunNotFound extends PikkuError {
44
44
  }
45
45
  }
46
46
  /**
47
- * Register a workflow with the system
47
+ * Add a workflow to the system
48
+ * This is called by the generated workflow wirings
48
49
  */
49
- export const wireWorkflow = (workflow) => {
50
+ export const addWorkflow = (workflowName, workflowFunc) => {
50
51
  // Get workflow metadata from inspector
51
52
  const meta = pikkuState('workflows', 'meta');
52
- const workflowMeta = meta[workflow.name];
53
+ const workflowMeta = meta[workflowName];
53
54
  if (!workflowMeta) {
54
- throw new Error(`Workflow metadata not found for '${workflow.name}'. Make sure to run the CLI to generate metadata.`);
55
+ throw new Error(`Workflow metadata not found for '${workflowName}'. Make sure to run the CLI to generate metadata.`);
55
56
  }
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
57
  // Store workflow definition in state
66
58
  const registrations = pikkuState('workflows', 'registrations');
67
- registrations.set(workflow.name, workflow);
59
+ registrations.set(workflowName, {
60
+ name: workflowName,
61
+ func: workflowFunc,
62
+ });
63
+ // Register the function with pikku
64
+ addFunction(workflowMeta.pikkuFuncName, workflowFunc);
68
65
  };
@@ -1,4 +1,4 @@
1
- import { PikkuDocs, MiddlewareMetadata, SerializedError, CoreSingletonServices, CreateSessionServices, CoreConfig } from '../../types/core.types.js';
1
+ import { SerializedError, CoreSingletonServices, CreateWireServices, CoreConfig, CommonWireMeta } from '../../types/core.types.js';
2
2
  import { CorePikkuFunctionConfig } from '../../function/functions.types.js';
3
3
  export interface WorkflowServiceConfig {
4
4
  retries: number;
@@ -73,8 +73,6 @@ export interface StepState {
73
73
  export type CoreWorkflow<PikkuFunctionConfig extends CorePikkuFunctionConfig<any, any, any> = CorePikkuFunctionConfig<any, any, any>> = {
74
74
  /** Unique workflow name */
75
75
  name: string;
76
- /** Description of the workflow */
77
- description?: string;
78
76
  /** The workflow function */
79
77
  func: PikkuFunctionConfig;
80
78
  /** Middleware chain for this workflow */
@@ -83,8 +81,6 @@ export type CoreWorkflow<PikkuFunctionConfig extends CorePikkuFunctionConfig<any
83
81
  permissions?: PikkuFunctionConfig['permissions'];
84
82
  /** Tags for organization and filtering */
85
83
  tags?: string[];
86
- /** Documentation metadata */
87
- docs?: PikkuDocs;
88
84
  };
89
85
  /**
90
86
  * Workflow step options
@@ -100,30 +96,108 @@ export interface WorkflowStepOptions {
100
96
  /**
101
97
  * Type signature for workflow.do() RPC form - used by inspector
102
98
  */
103
- export type WorkflowInteractionDoRPC = <TOutput = any, TInput = any>(stepName: string, rpcName: string, data: TInput, options?: WorkflowStepOptions) => Promise<TOutput>;
99
+ export type WorkflowWireDoRPC = <TOutput = any, TInput = any>(stepName: string, rpcName: string, data: TInput, options?: WorkflowStepOptions) => Promise<TOutput>;
104
100
  /**
105
101
  * Type signature for workflow.do() inline form - used by inspector
106
102
  */
107
- export type WorkflowInteractionDoInline = <T>(stepName: string, fn: () => Promise<T> | T, options?: WorkflowStepOptions) => Promise<T>;
103
+ export type WorkflowWireDoInline = <T>(stepName: string, fn: () => Promise<T> | T, options?: WorkflowStepOptions) => Promise<T>;
108
104
  /**
109
105
  * Type signature for workflow.sleep() - used by inspector
110
106
  */
111
- export type WorkflowInteractionSleep = (stepName: string, duration: string) => Promise<void>;
107
+ export type WorkflowWireSleep = (stepName: string, duration: string) => Promise<void>;
112
108
  /**
113
- * Workflow step metadata (extracted by inspector)
109
+ * Input source for step arguments in simple workflows
110
+ */
111
+ export type InputSource = {
112
+ from: 'input';
113
+ path: string;
114
+ } | {
115
+ from: 'outputVar';
116
+ name: string;
117
+ path?: string;
118
+ } | {
119
+ from: 'item';
120
+ path: string;
121
+ } | {
122
+ from: 'literal';
123
+ value: unknown;
124
+ };
125
+ /**
126
+ * Output binding for return statements in simple workflows
127
+ */
128
+ export interface OutputBinding {
129
+ from: 'outputVar' | 'input';
130
+ name?: string;
131
+ path?: string;
132
+ }
133
+ /**
134
+ * RPC step metadata (base form)
114
135
  */
115
- export type WorkflowStepMeta = {
136
+ export interface RpcStepMeta {
116
137
  /** RPC form - generates queue worker */
117
138
  type: 'rpc';
118
139
  /** Cache key (stepName from workflow.do) */
119
140
  stepName: string;
120
141
  /** RPC to invoke */
121
142
  rpcName: string;
122
- /** Display name */
123
- description?: string;
143
+ /** Output variable name (if assigned) */
144
+ outputVar?: string;
145
+ /** Input source mappings */
146
+ inputs?: Record<string, InputSource>;
124
147
  /** Step options */
125
148
  options?: WorkflowStepOptions;
126
- } | {
149
+ }
150
+ /**
151
+ * Branch step metadata (if/else control flow)
152
+ */
153
+ export interface BranchStepMeta {
154
+ type: 'branch';
155
+ /** Condition expression (as source string) */
156
+ condition: string;
157
+ /** Branch paths */
158
+ branches: {
159
+ then: WorkflowStepMeta[];
160
+ else?: WorkflowStepMeta[];
161
+ };
162
+ }
163
+ /**
164
+ * Parallel group step metadata (Promise.all with multiple steps)
165
+ */
166
+ export interface ParallelGroupStepMeta {
167
+ type: 'parallel';
168
+ /** Child steps to execute in parallel */
169
+ children: RpcStepMeta[];
170
+ }
171
+ /**
172
+ * Fanout step metadata (parallel or sequential iteration)
173
+ */
174
+ export interface FanoutStepMeta {
175
+ type: 'fanout';
176
+ /** Step name for this fanout */
177
+ stepName: string;
178
+ /** Source array variable name */
179
+ sourceVar: string;
180
+ /** Iterator variable name */
181
+ itemVar: string;
182
+ /** Execution mode */
183
+ mode: 'parallel' | 'sequential';
184
+ /** Child step to execute per iteration */
185
+ child: RpcStepMeta;
186
+ /** Time between iterations (sequential mode only) */
187
+ timeBetween?: string;
188
+ }
189
+ /**
190
+ * Return step metadata (workflow output)
191
+ */
192
+ export interface ReturnStepMeta {
193
+ type: 'return';
194
+ /** Output bindings */
195
+ outputs: Record<string, OutputBinding>;
196
+ }
197
+ /**
198
+ * Inline step metadata (legacy support)
199
+ */
200
+ export interface InlineStepMeta {
127
201
  /** Inline form - local execution */
128
202
  type: 'inline';
129
203
  /** Cache key (stepName from workflow.do) */
@@ -132,19 +206,34 @@ export type WorkflowStepMeta = {
132
206
  description?: string;
133
207
  /** Step options */
134
208
  options?: WorkflowStepOptions;
135
- } | {
209
+ }
210
+ /**
211
+ * Sleep step metadata
212
+ */
213
+ export interface SleepStepMeta {
136
214
  /** Sleep step */
137
215
  type: 'sleep';
138
216
  /** Cache key (stepName from workflow.sleep) */
139
217
  stepName: string;
140
218
  /** Sleep duration */
141
219
  duration: string | number;
142
- };
220
+ }
221
+ /**
222
+ * Cancel step metadata
223
+ */
224
+ export interface CancelStepMeta {
225
+ /** Cancel step */
226
+ type: 'cancel';
227
+ }
143
228
  /**
144
- * Workflow step interaction context for RPC functions
229
+ * Workflow step metadata (extracted by inspector)
230
+ */
231
+ export type WorkflowStepMeta = RpcStepMeta | BranchStepMeta | ParallelGroupStepMeta | FanoutStepMeta | ReturnStepMeta | InlineStepMeta | SleepStepMeta | CancelStepMeta;
232
+ /**
233
+ * Workflow step wire context for RPC functions
145
234
  * Provides step-level metadata including retry attempt tracking
146
235
  */
147
- export interface WorkflowStepInteraction {
236
+ export interface WorkflowStepWire {
148
237
  /** The workflow run ID */
149
238
  runId: string;
150
239
  /** The unique step ID */
@@ -153,10 +242,10 @@ export interface WorkflowStepInteraction {
153
242
  attemptCount: number;
154
243
  }
155
244
  /**
156
- * Workflow interaction object for middleware
245
+ * Workflow wire object for middleware
157
246
  * Provides workflow-specific capabilities to function execution
158
247
  */
159
- export interface PikkuWorkflowInteraction {
248
+ export interface PikkuWorkflowWire {
160
249
  /** The workflow name */
161
250
  workflowName: string;
162
251
  /** The current run ID */
@@ -164,9 +253,9 @@ export interface PikkuWorkflowInteraction {
164
253
  /** Get the current workflow run */
165
254
  getRun: () => Promise<WorkflowRun>;
166
255
  /** Execute a workflow step (overloaded - RPC or inline form) */
167
- do: WorkflowInteractionDoRPC & WorkflowInteractionDoInline;
256
+ do: WorkflowWireDoRPC & WorkflowWireDoInline;
168
257
  /** Sleep for a duration */
169
- sleep: WorkflowInteractionSleep;
258
+ sleep: WorkflowWireSleep;
170
259
  /** Cancel the current workflow run */
171
260
  cancel: (reason?: string) => Promise<void>;
172
261
  }
@@ -186,15 +275,11 @@ export interface PikkuWorkflow {
186
275
  /**
187
276
  * Workflows metadata for inspector/CLI
188
277
  */
189
- export type WorkflowsMeta = Record<string, {
190
- pikkuFuncName: string;
278
+ export type WorkflowsMeta = Record<string, CommonWireMeta & {
191
279
  workflowName: string;
192
- description?: string;
193
- session?: undefined;
194
- docs?: PikkuDocs;
195
- tags?: string[];
196
- middleware?: MiddlewareMetadata[];
197
280
  steps: WorkflowStepMeta[];
281
+ /** Whether this workflow conforms to simple workflow DSL */
282
+ simple?: boolean;
198
283
  }>;
199
284
  /**
200
285
  * Interface for workflow orchestration
@@ -210,7 +295,7 @@ export interface WorkflowService {
210
295
  withRunLock<T>(id: string, fn: () => Promise<T>): Promise<T>;
211
296
  close(): Promise<void>;
212
297
  resumeWorkflow(runId: string): Promise<void>;
213
- setServices(singletonServices: CoreSingletonServices, createSessionServices: CreateSessionServices, config: CoreConfig): void;
298
+ setServices(singletonServices: CoreSingletonServices, createWireServices: CreateWireServices, config: CoreConfig): void;
214
299
  startWorkflow<I>(name: string, input: I, rpcService: any): Promise<{
215
300
  runId: string;
216
301
  }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/core",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "module": "dist/index.js",
@@ -42,7 +42,7 @@
42
42
  "picoquery": "^2.5.0"
43
43
  },
44
44
  "devDependencies": {
45
- "@types/node": "^24.9.1",
45
+ "@types/node": "^24.10.1",
46
46
  "tsx": "^4.20.6",
47
47
  "typescript": "^5.9"
48
48
  },
@@ -3,10 +3,10 @@
3
3
  */
4
4
  import { addError, PikkuError } from './error-handler.js'
5
5
 
6
- export class InvalidMiddlewareInteractionError extends PikkuError {}
7
- addError(InvalidMiddlewareInteractionError, {
6
+ export class InvalidMiddlewareWireError extends PikkuError {}
7
+ addError(InvalidMiddlewareWireError, {
8
8
  status: 500,
9
- message: 'The middleware interaction is invalid for the current wiring type.',
9
+ message: 'The middleware wire is invalid for the current wiring type.',
10
10
  })
11
11
 
12
12
  /**
@@ -7,44 +7,22 @@ import {
7
7
  pikkuMiddlewareFactory,
8
8
  } from './index.js'
9
9
 
10
- test('pikkuPermission factory function', async () => {
11
- const permission = pikkuPermission(async ({ logger }, data, session) => {
12
- return true
13
- })
14
-
15
- assert.strictEqual(typeof permission, 'function')
16
- assert.strictEqual(permission.length, 3) // services, data, session
17
- })
18
-
19
- test('pikkuMiddleware factory function', async () => {
20
- const middleware = pikkuMiddleware(async ({ logger }, interactions, next) => {
21
- await next()
22
- })
23
-
24
- assert.strictEqual(typeof middleware, 'function')
25
- assert.strictEqual(middleware.length, 3) // services, interactions, next
26
- })
27
-
28
10
  test('pikkuPermission returns the same function', async () => {
29
- const originalFn = async ({ logger }, data, session) => true
11
+ const originalFn = async () => true
30
12
  const wrappedFn = pikkuPermission(originalFn)
31
-
32
13
  assert.strictEqual(wrappedFn, originalFn)
33
14
  })
34
15
 
35
16
  test('pikkuMiddleware returns the same function', async () => {
36
- const originalFn = async ({ logger }, interactions, next) => {
37
- await next()
38
- }
17
+ const originalFn = async ({ logger }, wires, next) => next
39
18
  const wrappedFn = pikkuMiddleware(originalFn)
40
-
41
19
  assert.strictEqual(wrappedFn, originalFn)
42
20
  })
43
21
 
44
22
  test('pikkuPermissionFactory creates a factory function', async () => {
45
23
  const factory = pikkuPermissionFactory<{ role: string }>(({ role }) => {
46
- return pikkuPermission(async ({ logger }, data, session) => {
47
- return session?.role === role
24
+ return pikkuPermission(async ({ logger }, data, { initialSession }) => {
25
+ return (initialSession as any)?.role === role
48
26
  })
49
27
  })
50
28
 
@@ -59,7 +37,7 @@ test('pikkuPermissionFactory creates a factory function', async () => {
59
37
 
60
38
  test('pikkuMiddlewareFactory creates a factory function', async () => {
61
39
  const factory = pikkuMiddlewareFactory<{ message: string }>(({ message }) => {
62
- return pikkuMiddleware(async ({ logger }, interactions, next) => {
40
+ return pikkuMiddleware(async ({ logger }, wires, next) => {
63
41
  logger.info(message)
64
42
  await next()
65
43
  })
@@ -71,26 +49,21 @@ test('pikkuMiddlewareFactory creates a factory function', async () => {
71
49
  // Call the factory to get middleware
72
50
  const middleware = factory({ message: 'test' })
73
51
  assert.strictEqual(typeof middleware, 'function')
74
- assert.strictEqual(middleware.length, 3) // services, interactions, next
52
+ assert.strictEqual(middleware.length, 3) // services, wires, next
75
53
  })
76
54
 
77
55
  test('pikkuPermissionFactory returns the same factory', async () => {
78
- const originalFactory = ({ role }) =>
79
- pikkuPermission(async ({ logger }, data, session) => {
80
- return session?.role === role
81
- })
82
- const wrappedFactory = pikkuPermissionFactory(originalFactory)
83
-
56
+ const originalFactory = () => pikkuPermission(async () => true)
57
+ const wrappedFactory = pikkuPermissionFactory(originalFactory as any)
84
58
  assert.strictEqual(wrappedFactory, originalFactory)
85
59
  })
86
60
 
87
61
  test('pikkuMiddlewareFactory returns the same factory', async () => {
88
62
  const originalFactory = ({ message }) =>
89
- pikkuMiddleware(async ({ logger }, interactions, next) => {
63
+ pikkuMiddleware(async ({ logger }, _wires, next) => {
90
64
  logger.info(message)
91
65
  await next()
92
66
  })
93
67
  const wrappedFactory = pikkuMiddlewareFactory(originalFactory)
94
-
95
68
  assert.strictEqual(wrappedFactory, originalFactory)
96
69
  })