@pikku/core 0.12.51 → 0.12.52
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 +59 -0
- package/dist/services/http-scenario-actors.d.ts +67 -0
- package/dist/services/http-scenario-actors.js +193 -0
- package/dist/services/http-user-flow-actors.d.ts +20 -0
- package/dist/services/http-user-flow-actors.js +100 -0
- package/dist/services/index.d.ts +2 -2
- package/dist/services/index.js +1 -1
- package/dist/services/meta-service.d.ts +4 -4
- package/dist/services/meta-service.js +8 -8
- package/dist/services/scenario-actors-service.d.ts +39 -0
- package/dist/services/scenario-actors-service.js +1 -0
- package/dist/services/user-flow-actors-service.d.ts +11 -1
- package/dist/types/core.types.d.ts +4 -4
- package/dist/wirings/actor-flow/actor-flow.types.d.ts +68 -0
- package/dist/wirings/actor-flow/actor-flow.types.js +1 -0
- package/dist/wirings/actor-flow/index.d.ts +11 -0
- package/dist/wirings/actor-flow/index.js +1 -0
- package/dist/wirings/actor-flow/run-conversation.d.ts +36 -0
- package/dist/wirings/actor-flow/run-conversation.js +181 -0
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +6 -6
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +10 -2
- package/dist/wirings/workflow/pikku-workflow-service.js +42 -2
- package/dist/wirings/workflow/workflow.types.d.ts +6 -6
- package/package.json +2 -1
- package/src/services/http-scenario-actors-converse.test.ts +222 -0
- package/src/services/{http-user-flow-actors.test.ts → http-scenario-actors.test.ts} +17 -7
- package/src/services/http-scenario-actors.ts +269 -0
- package/src/services/index.ts +8 -8
- package/src/services/meta-service.ts +9 -9
- package/src/services/{user-flow-actors-service.ts → scenario-actors-service.ts} +18 -4
- package/src/types/core.types.ts +4 -4
- package/src/wirings/actor-flow/actor-flow.types.ts +73 -0
- package/src/wirings/actor-flow/index.ts +22 -0
- package/src/wirings/actor-flow/run-conversation.test.ts +176 -0
- package/src/wirings/actor-flow/run-conversation.ts +285 -0
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +6 -6
- package/src/wirings/workflow/pikku-workflow-service.ts +50 -5
- package/src/wirings/workflow/{user-flow-step.test.ts → scenario-step.test.ts} +19 -14
- package/src/wirings/workflow/workflow.types.ts +6 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/src/services/http-user-flow-actors.ts +0 -132
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
pikkuState,
|
|
12
12
|
} from '../../pikku-state.js'
|
|
13
13
|
import { getDurationInMilliseconds } from '../../time-utils.js'
|
|
14
|
+
import { createHttpScenarioActors } from '../../services/http-scenario-actors.js'
|
|
14
15
|
|
|
15
16
|
const resolveWorkflowMeta = (
|
|
16
17
|
name: string
|
|
@@ -67,7 +68,7 @@ import {
|
|
|
67
68
|
runFromMeta,
|
|
68
69
|
} from './graph/graph-runner.js'
|
|
69
70
|
import type { WorkflowService } from '../../services/workflow-service.js'
|
|
70
|
-
import type {
|
|
71
|
+
import type { ScenarioActors } from '../../services/scenario-actors-service.js'
|
|
71
72
|
import {
|
|
72
73
|
PikkuError,
|
|
73
74
|
addError,
|
|
@@ -222,7 +223,7 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
222
223
|
private inlineRuns = new Set<string>()
|
|
223
224
|
// User-flow actors per run: live authenticated clients (cookie jars) are
|
|
224
225
|
// process-local by nature, so they ride this map, never the persisted wire.
|
|
225
|
-
private runActors = new Map<string,
|
|
226
|
+
private runActors = new Map<string, ScenarioActors>()
|
|
226
227
|
|
|
227
228
|
protected get logger() {
|
|
228
229
|
return getSingletonServices()?.logger
|
|
@@ -1065,6 +1066,45 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1065
1066
|
return true
|
|
1066
1067
|
}
|
|
1067
1068
|
|
|
1069
|
+
/** Build HTTP scenario actors for a run started without them; undefined when SCENARIO_ACTOR_SECRET or the API URL is missing */
|
|
1070
|
+
private async resolveScenarioActors(): Promise<ScenarioActors | undefined> {
|
|
1071
|
+
const services = getSingletonServices()
|
|
1072
|
+
const variables = services?.variables
|
|
1073
|
+
const metaService = services?.metaService
|
|
1074
|
+
if (!variables || !metaService) {
|
|
1075
|
+
return undefined
|
|
1076
|
+
}
|
|
1077
|
+
const secret = await variables.get('SCENARIO_ACTOR_SECRET')
|
|
1078
|
+
const apiUrl = await variables.get('API_URL')
|
|
1079
|
+
if (!secret || !apiUrl) {
|
|
1080
|
+
services?.logger?.warn(
|
|
1081
|
+
'A scenario was started without actors but SCENARIO_ACTOR_SECRET / API_URL is not configured — running without actors.'
|
|
1082
|
+
)
|
|
1083
|
+
return undefined
|
|
1084
|
+
}
|
|
1085
|
+
const actorsConfig = await metaService.getScenarioActorsMeta()
|
|
1086
|
+
if (!actorsConfig || Object.keys(actorsConfig).length === 0) {
|
|
1087
|
+
return undefined
|
|
1088
|
+
}
|
|
1089
|
+
const signInPath =
|
|
1090
|
+
(await variables.get('SCENARIO_SIGN_IN_PATH')) ??
|
|
1091
|
+
'/api/auth/sign-in/actor'
|
|
1092
|
+
const rpcPath = (await variables.get('SCENARIO_RPC_PATH')) ?? '/rpc'
|
|
1093
|
+
return createHttpScenarioActors({
|
|
1094
|
+
apiUrl,
|
|
1095
|
+
secret,
|
|
1096
|
+
actors: actorsConfig,
|
|
1097
|
+
signInPath,
|
|
1098
|
+
rpcPath,
|
|
1099
|
+
})
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
/**
|
|
1103
|
+
* Start a new workflow run
|
|
1104
|
+
* Automatically detects workflow type (DSL or graph) from meta and executes accordingly
|
|
1105
|
+
* @param options.inline - If true, execute workflow directly without queue service
|
|
1106
|
+
* @param options.startNode - Starting node ID for graph workflows (from wire config)
|
|
1107
|
+
*/
|
|
1068
1108
|
/**
|
|
1069
1109
|
* Start a new workflow run
|
|
1070
1110
|
* Automatically detects workflow type (DSL or graph) from meta and executes accordingly
|
|
@@ -1076,7 +1116,7 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1076
1116
|
input: I,
|
|
1077
1117
|
wire: WorkflowRunWire,
|
|
1078
1118
|
rpcService: any,
|
|
1079
|
-
options?: { inline?: boolean; startNode?: string; actors?:
|
|
1119
|
+
options?: { inline?: boolean; startNode?: string; actors?: ScenarioActors }
|
|
1080
1120
|
): Promise<{ runId: string }> {
|
|
1081
1121
|
// Resolve workflow from static meta (root or addon namespace), then dynamic DB
|
|
1082
1122
|
const resolved = resolveWorkflowMeta(name)
|
|
@@ -1140,8 +1180,13 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1140
1180
|
}
|
|
1141
1181
|
)
|
|
1142
1182
|
|
|
1143
|
-
|
|
1144
|
-
|
|
1183
|
+
const actors =
|
|
1184
|
+
options?.actors ??
|
|
1185
|
+
(workflowMeta.source === 'scenario'
|
|
1186
|
+
? await this.resolveScenarioActors()
|
|
1187
|
+
: undefined)
|
|
1188
|
+
if (actors) {
|
|
1189
|
+
this.runActors.set(runId, actors)
|
|
1145
1190
|
}
|
|
1146
1191
|
|
|
1147
1192
|
if (shouldInline) {
|
|
@@ -3,14 +3,14 @@ import assert from 'node:assert/strict'
|
|
|
3
3
|
|
|
4
4
|
import { InMemoryWorkflowService } from '../../services/in-memory-workflow-service.js'
|
|
5
5
|
import { pikkuState, resetPikkuState } from '../../pikku-state.js'
|
|
6
|
-
import type {
|
|
6
|
+
import type { ScenarioActor } from '../../services/scenario-actors-service.js'
|
|
7
7
|
|
|
8
8
|
const noopLogger = { error() {}, info() {}, warn() {}, debug() {} }
|
|
9
9
|
|
|
10
10
|
const fakeActor = (
|
|
11
11
|
name: string,
|
|
12
12
|
handler: (rpcName: string, data: unknown) => Promise<unknown>
|
|
13
|
-
):
|
|
13
|
+
): ScenarioActor & { calls: Array<{ rpcName: string; data: unknown }> } => {
|
|
14
14
|
const calls: Array<{ rpcName: string; data: unknown }> = []
|
|
15
15
|
return {
|
|
16
16
|
name,
|
|
@@ -31,14 +31,14 @@ const setup = async (
|
|
|
31
31
|
logger: noopLogger,
|
|
32
32
|
...services,
|
|
33
33
|
} as any)
|
|
34
|
-
const runId = await ws.createRun('
|
|
34
|
+
const runId = await ws.createRun('scenarioTest', {}, true, 'hash', {
|
|
35
35
|
type: 'test',
|
|
36
36
|
} as any)
|
|
37
37
|
ws.registerInlineRun(runId)
|
|
38
38
|
return runId
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
describe('
|
|
41
|
+
describe('scenario actor steps (workflow.do with `actor`)', () => {
|
|
42
42
|
beforeEach(() => resetPikkuState())
|
|
43
43
|
|
|
44
44
|
test('routes through the actor over the real transport, never internal rpc', async () => {
|
|
@@ -54,7 +54,7 @@ describe('user-flow actor steps (workflow.do with `actor`)', () => {
|
|
|
54
54
|
},
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
const wire = ws.createWorkflowWire('
|
|
57
|
+
const wire = ws.createWorkflowWire('scenarioTest', runId, rpc)
|
|
58
58
|
const result = await wire.do(
|
|
59
59
|
'customer creates todo',
|
|
60
60
|
'createTodo',
|
|
@@ -74,7 +74,7 @@ describe('user-flow actor steps (workflow.do with `actor`)', () => {
|
|
|
74
74
|
let invocations = 0
|
|
75
75
|
const yasser = fakeActor('yasser', async () => ({ n: ++invocations }))
|
|
76
76
|
const runId = await setup(ws)
|
|
77
|
-
const wire = ws.createWorkflowWire('
|
|
77
|
+
const wire = ws.createWorkflowWire('scenarioTest', runId, {})
|
|
78
78
|
|
|
79
79
|
const first = await wire.do('step', 'someRpc', {}, { actor: yasser })
|
|
80
80
|
assert.deepEqual(first, { n: 1 })
|
|
@@ -82,8 +82,13 @@ describe('user-flow actor steps (workflow.do with `actor`)', () => {
|
|
|
82
82
|
// Simulate replay: reset per-run ordinals so the same logical step name
|
|
83
83
|
// resolves to the same durable step key.
|
|
84
84
|
;(ws as any).resetStepOrdinals(runId)
|
|
85
|
-
const replayWire = ws.createWorkflowWire('
|
|
86
|
-
const replayed = await replayWire.do(
|
|
85
|
+
const replayWire = ws.createWorkflowWire('scenarioTest', runId, {})
|
|
86
|
+
const replayed = await replayWire.do(
|
|
87
|
+
'step',
|
|
88
|
+
'someRpc',
|
|
89
|
+
{},
|
|
90
|
+
{ actor: yasser }
|
|
91
|
+
)
|
|
87
92
|
|
|
88
93
|
assert.deepEqual(replayed, { n: 1 }, 'replay must return the cached result')
|
|
89
94
|
assert.equal(invocations, 1, 'the actor must not be re-invoked on replay')
|
|
@@ -106,7 +111,7 @@ describe('user-flow actor steps (workflow.do with `actor`)', () => {
|
|
|
106
111
|
workflowQueued: true,
|
|
107
112
|
} as any
|
|
108
113
|
|
|
109
|
-
const wire = ws.createWorkflowWire('
|
|
114
|
+
const wire = ws.createWorkflowWire('scenarioTest', runId, {})
|
|
110
115
|
await wire.do('step', 'queuedRpc', {}, { actor: customer })
|
|
111
116
|
|
|
112
117
|
assert.equal(queued, 0, 'actor steps are outbound HTTP — never queued')
|
|
@@ -116,10 +121,10 @@ describe('user-flow actor steps (workflow.do with `actor`)', () => {
|
|
|
116
121
|
test('actor step failure surfaces the actor error and fails after retries', async () => {
|
|
117
122
|
const ws = new InMemoryWorkflowService()
|
|
118
123
|
const broken = fakeActor('broken', async () => {
|
|
119
|
-
throw new Error("[
|
|
124
|
+
throw new Error("[scenario] 'createTodo' as 'broken' returned 403: nope")
|
|
120
125
|
})
|
|
121
126
|
const runId = await setup(ws)
|
|
122
|
-
const wire = ws.createWorkflowWire('
|
|
127
|
+
const wire = ws.createWorkflowWire('scenarioTest', runId, {})
|
|
123
128
|
|
|
124
129
|
await assert.rejects(
|
|
125
130
|
wire.do('step', 'createTodo', {}, { actor: broken, retries: 2 }),
|
|
@@ -139,7 +144,7 @@ describe('workflow.expectEventually', () => {
|
|
|
139
144
|
notifications: ++polls >= 3 ? ['ping'] : [],
|
|
140
145
|
}))
|
|
141
146
|
const runId = await setup(ws)
|
|
142
|
-
const wire = ws.createWorkflowWire('
|
|
147
|
+
const wire = ws.createWorkflowWire('scenarioTest', runId, {})
|
|
143
148
|
|
|
144
149
|
const result = await wire.expectEventually(
|
|
145
150
|
'sarah sees the notification',
|
|
@@ -157,7 +162,7 @@ describe('workflow.expectEventually', () => {
|
|
|
157
162
|
const ws = new InMemoryWorkflowService()
|
|
158
163
|
const sarah = fakeActor('sarah', async () => ({ notifications: [] }))
|
|
159
164
|
const runId = await setup(ws)
|
|
160
|
-
const wire = ws.createWorkflowWire('
|
|
165
|
+
const wire = ws.createWorkflowWire('scenarioTest', runId, {})
|
|
161
166
|
|
|
162
167
|
await assert.rejects(
|
|
163
168
|
wire.expectEventually(
|
|
@@ -178,7 +183,7 @@ describe('workflow.expectEventually', () => {
|
|
|
178
183
|
const rpc = {
|
|
179
184
|
rpcWithWire: async () => ({ ready: ++polls >= 2 }),
|
|
180
185
|
}
|
|
181
|
-
const wire = ws.createWorkflowWire('
|
|
186
|
+
const wire = ws.createWorkflowWire('scenarioTest', runId, rpc)
|
|
182
187
|
|
|
183
188
|
const result = await wire.expectEventually(
|
|
184
189
|
'job finishes',
|
|
@@ -325,9 +325,9 @@ export type WorkflowsMeta = Record<
|
|
|
325
325
|
context?: WorkflowContext
|
|
326
326
|
dsl?: boolean
|
|
327
327
|
expose?: boolean
|
|
328
|
-
/** True for
|
|
329
|
-
|
|
330
|
-
/** Actor names a
|
|
328
|
+
/** True for pikkuScenario workflows (complex + actor steps). */
|
|
329
|
+
scenario?: boolean
|
|
330
|
+
/** Actor names a scenario declares (personas it runs steps as). */
|
|
331
331
|
actors?: string[]
|
|
332
332
|
}
|
|
333
333
|
>
|
|
@@ -342,13 +342,13 @@ export interface WorkflowRuntimeMeta {
|
|
|
342
342
|
name: string
|
|
343
343
|
/** Pikku function name (for execution) */
|
|
344
344
|
pikkuFuncId: string
|
|
345
|
-
/** Source type: 'dsl' (serializable), 'complex' (has inline steps), 'graph', '
|
|
346
|
-
source: 'dsl' | 'complex' | 'graph' | 'dynamic-workflow' | '
|
|
345
|
+
/** Source type: 'dsl' (serializable), 'complex' (has inline steps), 'graph', 'scenario' (complex + actor steps) */
|
|
346
|
+
source: 'dsl' | 'complex' | 'graph' | 'dynamic-workflow' | 'scenario'
|
|
347
347
|
/** Optional description */
|
|
348
348
|
description?: string
|
|
349
349
|
/** Tags for organization */
|
|
350
350
|
tags?: string[]
|
|
351
|
-
/** Actor names a
|
|
351
|
+
/** Actor names a scenario declares (personas it runs steps as). */
|
|
352
352
|
actors?: string[]
|
|
353
353
|
/** Serialized nodes */
|
|
354
354
|
nodes?: Record<string, any>
|