@pikku/core 0.12.51 → 0.12.53
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 +74 -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 +5 -2
- package/dist/services/index.js +4 -1
- package/dist/services/istanbul-coverage-service.d.ts +12 -0
- package/dist/services/istanbul-coverage-service.js +41 -0
- 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 +21 -0
- package/dist/services/scenario-actors-service.js +1 -0
- package/dist/services/stub-tracker.d.ts +43 -0
- package/dist/services/stub-tracker.js +146 -0
- package/dist/services/user-flow-actors-service.d.ts +11 -1
- package/dist/services/v8-coverage-service.d.ts +41 -0
- package/dist/services/v8-coverage-service.js +63 -0
- package/dist/types/core.types.d.ts +13 -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 +22 -6
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +4 -2
- package/dist/wirings/workflow/pikku-workflow-service.js +92 -2
- package/dist/wirings/workflow/workflow.types.d.ts +7 -7
- 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 +27 -8
- package/src/services/istanbul-coverage-service.test.ts +66 -0
- package/src/services/istanbul-coverage-service.ts +65 -0
- package/src/services/meta-service.ts +9 -9
- package/src/services/scenario-actors-service.ts +27 -0
- package/src/services/stub-tracker.test.ts +104 -0
- package/src/services/stub-tracker.ts +185 -0
- package/src/services/v8-coverage-service.test.ts +69 -0
- package/src/services/v8-coverage-service.ts +121 -0
- package/src/types/core.types.ts +13 -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 +35 -6
- package/src/wirings/workflow/pikku-workflow-service.ts +144 -5
- package/src/wirings/workflow/{user-flow-step.test.ts → scenario-step.test.ts} +19 -14
- package/src/wirings/workflow/workflow.types.ts +8 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/src/services/http-user-flow-actors.ts +0 -132
- package/src/services/user-flow-actors-service.ts +0 -31
|
@@ -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
|
|
@@ -59,6 +60,8 @@ import type {
|
|
|
59
60
|
WorkflowServiceConfig,
|
|
60
61
|
WorkflowStepOptions,
|
|
61
62
|
WorkflowExpectEventuallyOptions,
|
|
63
|
+
WorkflowExpectErrorOptions,
|
|
64
|
+
WorkflowExpectServiceOptions,
|
|
62
65
|
} from './workflow.types.js'
|
|
63
66
|
import {
|
|
64
67
|
continueGraph,
|
|
@@ -67,7 +70,7 @@ import {
|
|
|
67
70
|
runFromMeta,
|
|
68
71
|
} from './graph/graph-runner.js'
|
|
69
72
|
import type { WorkflowService } from '../../services/workflow-service.js'
|
|
70
|
-
import type {
|
|
73
|
+
import type { ScenarioActors } from '../../services/scenario-actors-service.js'
|
|
71
74
|
import {
|
|
72
75
|
PikkuError,
|
|
73
76
|
addError,
|
|
@@ -222,7 +225,7 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
222
225
|
private inlineRuns = new Set<string>()
|
|
223
226
|
// User-flow actors per run: live authenticated clients (cookie jars) are
|
|
224
227
|
// process-local by nature, so they ride this map, never the persisted wire.
|
|
225
|
-
private runActors = new Map<string,
|
|
228
|
+
private runActors = new Map<string, ScenarioActors>()
|
|
226
229
|
|
|
227
230
|
protected get logger() {
|
|
228
231
|
return getSingletonServices()?.logger
|
|
@@ -1065,6 +1068,39 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1065
1068
|
return true
|
|
1066
1069
|
}
|
|
1067
1070
|
|
|
1071
|
+
/** Build HTTP scenario actors for a run started without them; undefined when SCENARIO_ACTOR_SECRET or the API URL is missing */
|
|
1072
|
+
private async resolveScenarioActors(): Promise<ScenarioActors | undefined> {
|
|
1073
|
+
const services = getSingletonServices()
|
|
1074
|
+
const variables = services?.variables
|
|
1075
|
+
const metaService = services?.metaService
|
|
1076
|
+
if (!variables || !metaService) {
|
|
1077
|
+
return undefined
|
|
1078
|
+
}
|
|
1079
|
+
const secret = await variables.get('SCENARIO_ACTOR_SECRET')
|
|
1080
|
+
const apiUrl = await variables.get('API_URL')
|
|
1081
|
+
if (!secret || !apiUrl) {
|
|
1082
|
+
services?.logger?.warn(
|
|
1083
|
+
'A scenario was started without actors but SCENARIO_ACTOR_SECRET / API_URL is not configured — running without actors.'
|
|
1084
|
+
)
|
|
1085
|
+
return undefined
|
|
1086
|
+
}
|
|
1087
|
+
const actorsConfig = await metaService.getScenarioActorsMeta()
|
|
1088
|
+
if (!actorsConfig || Object.keys(actorsConfig).length === 0) {
|
|
1089
|
+
return undefined
|
|
1090
|
+
}
|
|
1091
|
+
const signInPath =
|
|
1092
|
+
(await variables.get('SCENARIO_SIGN_IN_PATH')) ??
|
|
1093
|
+
'/api/auth/sign-in/actor'
|
|
1094
|
+
const rpcPath = (await variables.get('SCENARIO_RPC_PATH')) ?? '/rpc'
|
|
1095
|
+
return createHttpScenarioActors({
|
|
1096
|
+
apiUrl,
|
|
1097
|
+
secret,
|
|
1098
|
+
actors: actorsConfig,
|
|
1099
|
+
signInPath,
|
|
1100
|
+
rpcPath,
|
|
1101
|
+
})
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1068
1104
|
/**
|
|
1069
1105
|
* Start a new workflow run
|
|
1070
1106
|
* Automatically detects workflow type (DSL or graph) from meta and executes accordingly
|
|
@@ -1076,7 +1112,7 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1076
1112
|
input: I,
|
|
1077
1113
|
wire: WorkflowRunWire,
|
|
1078
1114
|
rpcService: any,
|
|
1079
|
-
options?: { inline?: boolean; startNode?: string; actors?:
|
|
1115
|
+
options?: { inline?: boolean; startNode?: string; actors?: ScenarioActors }
|
|
1080
1116
|
): Promise<{ runId: string }> {
|
|
1081
1117
|
// Resolve workflow from static meta (root or addon namespace), then dynamic DB
|
|
1082
1118
|
const resolved = resolveWorkflowMeta(name)
|
|
@@ -1140,8 +1176,13 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
1140
1176
|
}
|
|
1141
1177
|
)
|
|
1142
1178
|
|
|
1143
|
-
|
|
1144
|
-
|
|
1179
|
+
const actors =
|
|
1180
|
+
options?.actors ??
|
|
1181
|
+
(workflowMeta.source === 'scenario'
|
|
1182
|
+
? await this.resolveScenarioActors()
|
|
1183
|
+
: undefined)
|
|
1184
|
+
if (actors) {
|
|
1185
|
+
this.runActors.set(runId, actors)
|
|
1145
1186
|
}
|
|
1146
1187
|
|
|
1147
1188
|
if (shouldInline) {
|
|
@@ -2184,6 +2225,104 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
2184
2225
|
)
|
|
2185
2226
|
},
|
|
2186
2227
|
|
|
2228
|
+
expectError: async (
|
|
2229
|
+
stepName: string,
|
|
2230
|
+
rpcName: string,
|
|
2231
|
+
data: any,
|
|
2232
|
+
options?: WorkflowExpectErrorOptions
|
|
2233
|
+
) => {
|
|
2234
|
+
this.verifyStepName(stepName)
|
|
2235
|
+
const resolvedRpcName =
|
|
2236
|
+
addonNamespace && !rpcName.includes(':')
|
|
2237
|
+
? `${addonNamespace}:${rpcName}`
|
|
2238
|
+
: rpcName
|
|
2239
|
+
return await this.inlineStep(
|
|
2240
|
+
runId,
|
|
2241
|
+
stepName,
|
|
2242
|
+
async () => {
|
|
2243
|
+
let result: any
|
|
2244
|
+
try {
|
|
2245
|
+
result = options?.actor
|
|
2246
|
+
? await options.actor.invoke(resolvedRpcName, data)
|
|
2247
|
+
: await rpcService.rpcWithWire(resolvedRpcName, data, {})
|
|
2248
|
+
} catch (e: any) {
|
|
2249
|
+
const message = e?.message ?? String(e)
|
|
2250
|
+
if (options?.matches) {
|
|
2251
|
+
const matched =
|
|
2252
|
+
typeof options.matches === 'string'
|
|
2253
|
+
? message.includes(options.matches)
|
|
2254
|
+
: options.matches.test(message)
|
|
2255
|
+
if (!matched) {
|
|
2256
|
+
throw new Error(
|
|
2257
|
+
`[workflow] expectError '${stepName}' ('${resolvedRpcName}') threw, but the message did not match ${options.matches}: ${message}`
|
|
2258
|
+
)
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
return message
|
|
2262
|
+
}
|
|
2263
|
+
throw new Error(
|
|
2264
|
+
`[workflow] expectError '${stepName}' ('${resolvedRpcName}') expected an error but the call succeeded: ${JSON.stringify(result)?.slice(0, 300)}`
|
|
2265
|
+
)
|
|
2266
|
+
},
|
|
2267
|
+
options
|
|
2268
|
+
)
|
|
2269
|
+
},
|
|
2270
|
+
|
|
2271
|
+
expectService: async (
|
|
2272
|
+
stepName: string,
|
|
2273
|
+
serviceMethod: string,
|
|
2274
|
+
options?: WorkflowExpectServiceOptions
|
|
2275
|
+
) => {
|
|
2276
|
+
this.verifyStepName(stepName)
|
|
2277
|
+
const [service, method] = serviceMethod.split('.')
|
|
2278
|
+
if (!service || !method) {
|
|
2279
|
+
throw new Error(
|
|
2280
|
+
`[workflow] expectService '${stepName}' needs 'service.method', got '${serviceMethod}'`
|
|
2281
|
+
)
|
|
2282
|
+
}
|
|
2283
|
+
await this.inlineStep(
|
|
2284
|
+
runId,
|
|
2285
|
+
stepName,
|
|
2286
|
+
async () => {
|
|
2287
|
+
const rpcName = 'console:getStubCalls'
|
|
2288
|
+
const calls: Array<{
|
|
2289
|
+
service: string
|
|
2290
|
+
method: string
|
|
2291
|
+
args: unknown[]
|
|
2292
|
+
}> = options?.actor
|
|
2293
|
+
? await options.actor.invoke(rpcName, { service })
|
|
2294
|
+
: await rpcService.rpcWithWire(rpcName, { service }, {})
|
|
2295
|
+
const matching = (calls ?? []).filter(
|
|
2296
|
+
(c) =>
|
|
2297
|
+
c.service === service &&
|
|
2298
|
+
c.method === method &&
|
|
2299
|
+
(options?.calledWith === undefined ||
|
|
2300
|
+
JSON.stringify(c.args?.[0]) ===
|
|
2301
|
+
JSON.stringify(options.calledWith))
|
|
2302
|
+
)
|
|
2303
|
+
const expected = options?.times
|
|
2304
|
+
const ok =
|
|
2305
|
+
expected === undefined
|
|
2306
|
+
? matching.length > 0
|
|
2307
|
+
: matching.length === expected
|
|
2308
|
+
if (!ok) {
|
|
2309
|
+
const seen =
|
|
2310
|
+
(calls ?? [])
|
|
2311
|
+
.map(
|
|
2312
|
+
(c) =>
|
|
2313
|
+
`${c.service}.${c.method}(${JSON.stringify(c.args?.[0])?.slice(0, 120) ?? ''})`
|
|
2314
|
+
)
|
|
2315
|
+
.join('\n ') || '(none)'
|
|
2316
|
+
throw new Error(
|
|
2317
|
+
`[workflow] expectService '${stepName}' expected ${expected ?? 'at least one'} call(s) to '${serviceMethod}'` +
|
|
2318
|
+
`${options?.calledWith !== undefined ? ` with ${JSON.stringify(options.calledWith)}` : ''}, found ${matching.length}. Recorded:\n ${seen}`
|
|
2319
|
+
)
|
|
2320
|
+
}
|
|
2321
|
+
},
|
|
2322
|
+
options
|
|
2323
|
+
)
|
|
2324
|
+
},
|
|
2325
|
+
|
|
2187
2326
|
// Implement workflow.sleep()
|
|
2188
2327
|
sleep: async (stepName: string, duration: string | number) => {
|
|
2189
2328
|
this.verifyStepName(stepName)
|
|
@@ -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',
|
|
@@ -8,6 +8,8 @@ export type { WorkflowService } from '../../services/workflow-service.js'
|
|
|
8
8
|
export type {
|
|
9
9
|
WorkflowStepOptions,
|
|
10
10
|
WorkflowExpectEventuallyOptions,
|
|
11
|
+
WorkflowExpectErrorOptions,
|
|
12
|
+
WorkflowExpectServiceOptions,
|
|
11
13
|
WorkflowWireDoRPC,
|
|
12
14
|
WorkflowWireDoInline,
|
|
13
15
|
WorkflowWireSleep,
|
|
@@ -325,9 +327,9 @@ export type WorkflowsMeta = Record<
|
|
|
325
327
|
context?: WorkflowContext
|
|
326
328
|
dsl?: boolean
|
|
327
329
|
expose?: boolean
|
|
328
|
-
/** True for
|
|
329
|
-
|
|
330
|
-
/** Actor names a
|
|
330
|
+
/** True for pikkuScenario workflows (complex + actor steps). */
|
|
331
|
+
scenario?: boolean
|
|
332
|
+
/** Actor names a scenario declares (personas it runs steps as). */
|
|
331
333
|
actors?: string[]
|
|
332
334
|
}
|
|
333
335
|
>
|
|
@@ -342,13 +344,13 @@ export interface WorkflowRuntimeMeta {
|
|
|
342
344
|
name: string
|
|
343
345
|
/** Pikku function name (for execution) */
|
|
344
346
|
pikkuFuncId: string
|
|
345
|
-
/** Source type: 'dsl' (serializable), 'complex' (has inline steps), 'graph', '
|
|
346
|
-
source: 'dsl' | 'complex' | 'graph' | 'dynamic-workflow' | '
|
|
347
|
+
/** Source type: 'dsl' (serializable), 'complex' (has inline steps), 'graph', 'scenario' (complex + actor steps) */
|
|
348
|
+
source: 'dsl' | 'complex' | 'graph' | 'dynamic-workflow' | 'scenario'
|
|
347
349
|
/** Optional description */
|
|
348
350
|
description?: string
|
|
349
351
|
/** Tags for organization */
|
|
350
352
|
tags?: string[]
|
|
351
|
-
/** Actor names a
|
|
353
|
+
/** Actor names a scenario declares (personas it runs steps as). */
|
|
352
354
|
actors?: string[]
|
|
353
355
|
/** Serialized nodes */
|
|
354
356
|
nodes?: Record<string, any>
|