@pikku/inspector 0.12.31 → 0.12.34

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.
@@ -21,6 +21,7 @@ export enum ErrorCode {
21
21
  MISSING_QUEUE_NAME = 'PKU384',
22
22
  MISSING_CHANNEL_NAME = 'PKU400',
23
23
  CLI_CLIENTSIDE_RENDERER_HAS_SERVICES = 'PKU672',
24
+ USER_FLOW_HAS_SERVICES = 'PKU673',
24
25
  DYNAMIC_STEP_NAME = 'PKU529',
25
26
  WORKFLOW_ORCHESTRATOR_NOT_CONFIGURED = 'PKU600',
26
27
  INVALID_DSL_WORKFLOW = 'PKU641',
@@ -21,6 +21,8 @@ import type {
21
21
  } from '@pikku/core/workflow'
22
22
  import {
23
23
  isWorkflowDoCall,
24
+ isWorkflowExpectEventuallyCall,
25
+ extractActorFromOptions,
24
26
  isWorkflowSleepCall,
25
27
  isWorkflowSuspendCall,
26
28
  isThrowCancelException,
@@ -553,10 +555,15 @@ function extractRpcStep(
553
555
  const inputs =
554
556
  args.length >= 3 ? extractInputSources(args[2], context) : undefined
555
557
 
556
- // Extract options from fourth argument
558
+ // do(step, rpc, data, options?) vs expectEventually(step, rpc, data, predicate, options?)
559
+ const expectEventually = isWorkflowExpectEventuallyCall(call)
560
+ const optionsIndex = expectEventually ? 4 : 3
561
+ const optionsArg =
562
+ args.length > optionsIndex ? args[optionsIndex] : undefined
563
+
557
564
  const options =
558
- args.length >= 4 && ts.isObjectLiteralExpression(args[3])
559
- ? extractStepOptions(args[3], context)
565
+ optionsArg && ts.isObjectLiteralExpression(optionsArg)
566
+ ? extractStepOptions(optionsArg, context)
560
567
  : undefined
561
568
 
562
569
  return {
@@ -566,6 +573,8 @@ function extractRpcStep(
566
573
  outputVar,
567
574
  inputs,
568
575
  options,
576
+ actor: extractActorFromOptions(optionsArg),
577
+ expectEventually: expectEventually || undefined,
569
578
  }
570
579
  } catch (error) {
571
580
  context.errors.push({
@@ -5,7 +5,8 @@ import * as ts from 'typescript'
5
5
  */
6
6
 
7
7
  /**
8
- * Check if a call expression is workflow.do()
8
+ * Check if a call expression is workflow.do() or workflow.expectEventually()
9
+ * (both are RPC steps; expectEventually is the polling variant used by user flows)
9
10
  */
10
11
  export function isWorkflowDoCall(
11
12
  node: ts.CallExpression,
@@ -17,12 +18,57 @@ export function isWorkflowDoCall(
17
18
 
18
19
  const propAccess = node.expression
19
20
  return (
20
- propAccess.name.text === 'do' &&
21
+ (propAccess.name.text === 'do' ||
22
+ propAccess.name.text === 'expectEventually') &&
21
23
  ts.isIdentifier(propAccess.expression) &&
22
24
  propAccess.expression.text === 'workflow'
23
25
  )
24
26
  }
25
27
 
28
+ /**
29
+ * Check if a call expression is workflow.expectEventually()
30
+ */
31
+ export function isWorkflowExpectEventuallyCall(
32
+ node: ts.CallExpression
33
+ ): boolean {
34
+ return (
35
+ ts.isPropertyAccessExpression(node.expression) &&
36
+ node.expression.name.text === 'expectEventually' &&
37
+ ts.isIdentifier(node.expression.expression) &&
38
+ node.expression.expression.text === 'workflow'
39
+ )
40
+ }
41
+
42
+ /**
43
+ * Extract the actor NAME from a step options object literal:
44
+ * `{ actor: actors.x }` → 'x' (also `actors['x']`). Undefined when absent
45
+ * or not statically resolvable.
46
+ */
47
+ export function extractActorFromOptions(
48
+ optionsArg: ts.Expression | undefined
49
+ ): string | undefined {
50
+ if (!optionsArg || !ts.isObjectLiteralExpression(optionsArg)) return
51
+ for (const prop of optionsArg.properties) {
52
+ if (
53
+ ts.isPropertyAssignment(prop) &&
54
+ ts.isIdentifier(prop.name) &&
55
+ prop.name.text === 'actor'
56
+ ) {
57
+ const init = prop.initializer
58
+ if (ts.isPropertyAccessExpression(init)) {
59
+ return init.name.text
60
+ }
61
+ if (
62
+ ts.isElementAccessExpression(init) &&
63
+ ts.isStringLiteral(init.argumentExpression)
64
+ ) {
65
+ return init.argumentExpression.text
66
+ }
67
+ }
68
+ }
69
+ return
70
+ }
71
+
26
72
  /**
27
73
  * Check if a call expression is workflow.sleep()
28
74
  */
@@ -93,6 +93,12 @@ function convertStepToNode(
93
93
  rpcName: step.rpcName,
94
94
  next: nextNodeId,
95
95
  }
96
+ if (step.actor) {
97
+ node.actor = step.actor
98
+ }
99
+ if (step.expectEventually) {
100
+ node.expectEventually = true
101
+ }
96
102
  if (step.inputs) {
97
103
  if (step.inputs === 'passthrough') {
98
104
  // Entire data is passed through - store as reference to trigger
@@ -389,17 +395,24 @@ export function convertDslToGraph(
389
395
 
390
396
  const entryNodeIds = nodes.length > 0 ? [nodes[0].nodeId] : []
391
397
 
392
- // Determine source type based on dsl flag:
398
+ // Determine source type:
399
+ // - userFlow: complex workflow whose steps run as actors (user flow)
393
400
  // - dsl === true: pure DSL workflow, can be serialized
394
401
  // - dsl === false: complex workflow with inline steps, not serializable
395
- const source = meta.dsl === false ? 'complex' : 'dsl'
402
+ const source = meta.userFlow
403
+ ? 'user-flow'
404
+ : meta.dsl === false
405
+ ? 'complex'
406
+ : 'dsl'
396
407
 
397
408
  return {
398
409
  name: workflowName,
399
410
  pikkuFuncId: meta.pikkuFuncId,
400
411
  source,
412
+ title: meta.title,
401
413
  description: meta.description,
402
414
  tags: meta.tags,
415
+ actors: meta.actors,
403
416
  context: meta.context,
404
417
  nodes: nodesRecord,
405
418
  entryNodeIds,
@@ -142,6 +142,10 @@ export interface FunctionNode extends BaseNode {
142
142
  outputVar?: string
143
143
  /** Hash of nodeId + RPC input/output schemas for version detection */
144
144
  stepHash?: string
145
+ /** User-flow actor name this step runs as (workflow.do {actor: actors.x}) */
146
+ actor?: string
147
+ /** True for workflow.expectEventually polling steps (user flows) */
148
+ expectEventually?: boolean
145
149
  }
146
150
 
147
151
  /**
@@ -177,8 +181,9 @@ export const isFlowNode = (node: SerializedGraphNode): node is FlowNode =>
177
181
  * - 'dsl': Pure DSL workflow (pikkuWorkflowFunc) - can be round-tripped to code
178
182
  * - 'complex': Complex workflow (pikkuWorkflowComplexFunc) - contains inline steps, not serializable
179
183
  * - 'graph': Graph-based workflow (pikkuWorkflowGraph)
184
+ * - 'user-flow': User flow (pikkuUserFlow) - complex workflow whose steps run as actors
180
185
  */
181
- export type WorkflowSourceType = 'dsl' | 'complex' | 'graph'
186
+ export type WorkflowSourceType = 'dsl' | 'complex' | 'graph' | 'user-flow'
182
187
 
183
188
  /**
184
189
  * Serialized workflow graph - the canonical JSON format
@@ -190,10 +195,14 @@ export interface SerializedWorkflowGraph {
190
195
  pikkuFuncId: string
191
196
  /** Source type: 'dsl' for pikkuWorkflowFunc, 'graph' for pikkuWorkflowGraph */
192
197
  source: WorkflowSourceType
198
+ /** Optional short display name */
199
+ title?: string
193
200
  /** Optional description */
194
201
  description?: string
195
202
  /** Tags for organization */
196
203
  tags?: string[]
204
+ /** Actor names a user flow's steps run as */
205
+ actors?: string[]
197
206
  /** If true, workflow always executes inline without queues */
198
207
  inline?: boolean
199
208
  /** Workflow context/state variables (from Zod schema) */