@pikku/inspector 0.12.34 → 0.12.36
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 +56 -0
- package/dist/add/add-functions.js +2 -2
- package/dist/add/add-http-route.js +4 -1
- package/dist/add/add-rpc-invocations.js +7 -0
- package/dist/add/add-workflow.d.ts +1 -1
- package/dist/add/add-workflow.js +39 -11
- package/dist/error-codes.d.ts +2 -1
- package/dist/error-codes.js +2 -1
- package/dist/inspector.js +1 -0
- package/dist/types.d.ts +1 -0
- package/dist/utils/filter-inspector-state.js +43 -0
- package/dist/utils/load-addon-functions-meta.js +20 -14
- package/dist/utils/post-process.js +38 -13
- package/dist/utils/serialize-inspector-state.d.ts +1 -0
- package/dist/utils/serialize-inspector-state.js +2 -0
- package/dist/utils/workflow/dsl/patterns.d.ts +1 -1
- package/dist/utils/workflow/dsl/patterns.js +1 -1
- package/dist/utils/workflow/graph/convert-dsl-to-graph.js +3 -3
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +5 -5
- package/package.json +2 -2
- package/run-tests.sh +4 -4
- package/src/add/add-functions.ts +11 -5
- package/src/add/add-http-route.ts +4 -1
- package/src/add/add-rpc-invocations.test.ts +113 -0
- package/src/add/add-rpc-invocations.ts +8 -0
- package/src/add/add-workflow.ts +53 -14
- package/src/add/expect-eventually-scenario-only.test.ts +108 -0
- package/src/error-codes.ts +2 -1
- package/src/inspector.ts +1 -0
- package/src/types.ts +1 -0
- package/src/utils/filter-inspector-state.test.ts +231 -0
- package/src/utils/filter-inspector-state.ts +56 -0
- package/src/utils/load-addon-functions-meta.ts +26 -16
- package/src/utils/post-process.test.ts +254 -0
- package/src/utils/post-process.ts +41 -14
- package/src/utils/serialize-inspector-state.ts +11 -0
- package/src/utils/workflow/dsl/patterns.ts +1 -1
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +3 -3
- package/src/utils/workflow/graph/workflow-graph.types.ts +5 -5
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -468,6 +468,11 @@ export function filterInspectorState(
|
|
|
468
468
|
filteredState.serviceAggregation.usedFunctions.add(
|
|
469
469
|
routeMeta.pikkuFuncId
|
|
470
470
|
)
|
|
471
|
+
if (routeMeta.refTarget) {
|
|
472
|
+
filteredState.serviceAggregation.usedFunctions.add(
|
|
473
|
+
routeMeta.refTarget
|
|
474
|
+
)
|
|
475
|
+
}
|
|
471
476
|
// For workflow/agent routes, also add the base name
|
|
472
477
|
// so the workflow/agent definition survives pruning
|
|
473
478
|
const colonIdx = routeMeta.pikkuFuncId.indexOf(':')
|
|
@@ -1105,6 +1110,57 @@ export function filterInspectorState(
|
|
|
1105
1110
|
filteredState.serviceAggregation.requiredServices.add('queueService')
|
|
1106
1111
|
}
|
|
1107
1112
|
|
|
1113
|
+
if ((filteredState.rpc.wireAddonDeclarations?.size ?? 0) > 0) {
|
|
1114
|
+
const referencedIds = new Set<string>(
|
|
1115
|
+
filteredState.serviceAggregation.usedFunctions
|
|
1116
|
+
)
|
|
1117
|
+
const keptFiles = new Set<string>()
|
|
1118
|
+
for (const funcId of filteredState.serviceAggregation.usedFunctions) {
|
|
1119
|
+
const file = filteredState.functions.files.get(funcId)
|
|
1120
|
+
if (file?.path) keptFiles.add(file.path)
|
|
1121
|
+
}
|
|
1122
|
+
for (const [file, invoked] of state.rpc.invokedFunctionsByFile ??
|
|
1123
|
+
new Map<string, Set<string>>()) {
|
|
1124
|
+
if (!keptFiles.has(file)) continue
|
|
1125
|
+
for (const id of invoked) {
|
|
1126
|
+
referencedIds.add(id)
|
|
1127
|
+
if (id.includes(':')) {
|
|
1128
|
+
filteredState.serviceAggregation.usedFunctions.add(id)
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
for (const toolMeta of Object.values(
|
|
1133
|
+
filteredState.mcpEndpoints?.toolsMeta ?? {}
|
|
1134
|
+
) as Array<{ pikkuFuncId?: string }>) {
|
|
1135
|
+
if (toolMeta.pikkuFuncId) referencedIds.add(toolMeta.pikkuFuncId)
|
|
1136
|
+
}
|
|
1137
|
+
for (const agentMeta of Object.values(
|
|
1138
|
+
filteredState.agents?.agentsMeta ?? {}
|
|
1139
|
+
) as Array<{ tools?: string[] }>) {
|
|
1140
|
+
for (const tool of agentMeta.tools ?? []) referencedIds.add(tool)
|
|
1141
|
+
}
|
|
1142
|
+
const keptNamespaces = new Set<string>()
|
|
1143
|
+
for (const namespace of filteredState.rpc.wireAddonDeclarations.keys()) {
|
|
1144
|
+
const prefix = `${namespace}:`
|
|
1145
|
+
for (const id of referencedIds) {
|
|
1146
|
+
if (id.startsWith(prefix)) {
|
|
1147
|
+
keptNamespaces.add(namespace)
|
|
1148
|
+
break
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
filteredState.rpc.wireAddonDeclarations = new Map(
|
|
1153
|
+
[...filteredState.rpc.wireAddonDeclarations].filter(([namespace]) =>
|
|
1154
|
+
keptNamespaces.has(namespace)
|
|
1155
|
+
)
|
|
1156
|
+
)
|
|
1157
|
+
filteredState.rpc.usedAddons = new Set(
|
|
1158
|
+
[...filteredState.rpc.usedAddons].filter((namespace) =>
|
|
1159
|
+
keptNamespaces.has(namespace)
|
|
1160
|
+
)
|
|
1161
|
+
)
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1108
1164
|
// Recalculate requiredServices based on filtered functions/middleware/permissions
|
|
1109
1165
|
// Need to cast to InspectorState temporarily for aggregateRequiredServices
|
|
1110
1166
|
const stateForAggregation = filteredState as InspectorState
|
|
@@ -200,6 +200,7 @@ export async function loadAddonFunctionsMeta(
|
|
|
200
200
|
// No variables meta — that's fine
|
|
201
201
|
}
|
|
202
202
|
// Load addon serverlessIncompatible service names from pikku-addon-meta.gen.json
|
|
203
|
+
let loadedParentServices = false
|
|
203
204
|
try {
|
|
204
205
|
const addonMetaPath = require.resolve(
|
|
205
206
|
`${decl.package}/.pikku/console/pikku-addon-meta.gen.json`
|
|
@@ -218,29 +219,38 @@ export async function loadAddonFunctionsMeta(
|
|
|
218
219
|
`Addon '${namespace}' marks [${addonMeta.serverlessIncompatible.join(', ')}] as serverless-incompatible`
|
|
219
220
|
)
|
|
220
221
|
}
|
|
221
|
-
} catch {
|
|
222
|
-
// No addon meta or no serverlessIncompatible declared — that's fine
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Load addon required parent services from pikku-services.gen
|
|
226
|
-
try {
|
|
227
|
-
const servicesGenPath = require.resolve(
|
|
228
|
-
`${decl.package}/.pikku/pikku-services.gen.js`
|
|
229
|
-
)
|
|
230
|
-
const servicesModule = await import(servicesGenPath)
|
|
231
222
|
if (
|
|
232
|
-
|
|
233
|
-
|
|
223
|
+
Array.isArray(addonMeta.requiredParentServices) &&
|
|
224
|
+
addonMeta.requiredParentServices.length > 0
|
|
234
225
|
) {
|
|
235
|
-
for (const service of
|
|
226
|
+
for (const service of addonMeta.requiredParentServices) {
|
|
236
227
|
state.addonRequiredParentServices.push(service)
|
|
237
228
|
}
|
|
229
|
+
loadedParentServices = true
|
|
238
230
|
logger.debug(
|
|
239
|
-
`Loaded ${
|
|
231
|
+
`Loaded ${addonMeta.requiredParentServices.length} required parent services for '${namespace}' from addon meta`
|
|
240
232
|
)
|
|
241
233
|
}
|
|
242
|
-
} catch {
|
|
243
|
-
|
|
234
|
+
} catch {}
|
|
235
|
+
|
|
236
|
+
if (!loadedParentServices) {
|
|
237
|
+
try {
|
|
238
|
+
const servicesGenPath = require.resolve(
|
|
239
|
+
`${decl.package}/.pikku/pikku-services.gen.js`
|
|
240
|
+
)
|
|
241
|
+
const servicesModule = await import(servicesGenPath)
|
|
242
|
+
if (
|
|
243
|
+
servicesModule.requiredParentServices &&
|
|
244
|
+
Array.isArray(servicesModule.requiredParentServices)
|
|
245
|
+
) {
|
|
246
|
+
for (const service of servicesModule.requiredParentServices) {
|
|
247
|
+
state.addonRequiredParentServices.push(service)
|
|
248
|
+
}
|
|
249
|
+
logger.debug(
|
|
250
|
+
`Loaded ${servicesModule.requiredParentServices.length} required parent services for '${namespace}' from ${decl.package}`
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
} catch {}
|
|
244
254
|
}
|
|
245
255
|
|
|
246
256
|
try {
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { strict as assert } from 'assert'
|
|
2
|
+
import { describe, test } from 'node:test'
|
|
3
|
+
import { aggregateRequiredServices } from './post-process.js'
|
|
4
|
+
import type { InspectorState } from '../types.js'
|
|
5
|
+
|
|
6
|
+
function makeState(
|
|
7
|
+
overrides: {
|
|
8
|
+
usedFunctions?: string[]
|
|
9
|
+
functionsMeta?: Record<string, any>
|
|
10
|
+
addonFunctions?: Record<string, Record<string, any>>
|
|
11
|
+
addonRequiredParentServices?: string[]
|
|
12
|
+
} = {}
|
|
13
|
+
): Omit<InspectorState, 'typesLookup'> {
|
|
14
|
+
return {
|
|
15
|
+
serviceAggregation: {
|
|
16
|
+
requiredServices: new Set<string>(),
|
|
17
|
+
usedFunctions: new Set(overrides.usedFunctions ?? []),
|
|
18
|
+
usedMiddleware: new Set<string>(),
|
|
19
|
+
usedPermissions: new Set<string>(),
|
|
20
|
+
allSingletonServices: [],
|
|
21
|
+
allWireServices: [],
|
|
22
|
+
},
|
|
23
|
+
functions: { meta: overrides.functionsMeta ?? {} },
|
|
24
|
+
middleware: { definitions: {}, tagMiddleware: new Map() },
|
|
25
|
+
permissions: { definitions: {}, tagPermissions: new Map() },
|
|
26
|
+
http: {
|
|
27
|
+
meta: {
|
|
28
|
+
get: {},
|
|
29
|
+
post: {},
|
|
30
|
+
put: {},
|
|
31
|
+
patch: {},
|
|
32
|
+
delete: {},
|
|
33
|
+
head: {},
|
|
34
|
+
options: {},
|
|
35
|
+
},
|
|
36
|
+
routeMiddleware: new Map(),
|
|
37
|
+
routePermissions: new Map(),
|
|
38
|
+
},
|
|
39
|
+
channels: { meta: {} },
|
|
40
|
+
queueWorkers: { meta: {} },
|
|
41
|
+
scheduledTasks: { meta: {} },
|
|
42
|
+
mcpEndpoints: { toolsMeta: {}, promptsMeta: {}, resourcesMeta: {} },
|
|
43
|
+
agents: { agentsMeta: {} },
|
|
44
|
+
workflows: { meta: {}, graphMeta: {} },
|
|
45
|
+
wireServicesMeta: new Map(),
|
|
46
|
+
rpc: { internalMeta: {}, exposedMeta: {} },
|
|
47
|
+
addonFunctions: overrides.addonFunctions ?? {},
|
|
48
|
+
addonRequiredParentServices: overrides.addonRequiredParentServices ?? [],
|
|
49
|
+
} as unknown as Omit<InspectorState, 'typesLookup'>
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const CONSOLE_PARENT_SERVICES = [
|
|
53
|
+
'metaService',
|
|
54
|
+
'aiAgentRunner',
|
|
55
|
+
'deploymentService',
|
|
56
|
+
'credentialService',
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
describe('aggregateRequiredServices — per-function addon services', () => {
|
|
60
|
+
test('a used addon function adds only its own parent services', () => {
|
|
61
|
+
const state = makeState({
|
|
62
|
+
usedFunctions: ['console:getSchema'],
|
|
63
|
+
addonFunctions: {
|
|
64
|
+
console: {
|
|
65
|
+
getSchema: {
|
|
66
|
+
services: { optimized: true, services: ['metaService'] },
|
|
67
|
+
},
|
|
68
|
+
runAgent: {
|
|
69
|
+
services: {
|
|
70
|
+
optimized: true,
|
|
71
|
+
services: ['aiAgentRunner', 'deploymentService'],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
addonRequiredParentServices: CONSOLE_PARENT_SERVICES,
|
|
77
|
+
})
|
|
78
|
+
aggregateRequiredServices(state)
|
|
79
|
+
const required = state.serviceAggregation.requiredServices
|
|
80
|
+
assert.ok(required.has('metaService'))
|
|
81
|
+
assert.ok(!required.has('aiAgentRunner'))
|
|
82
|
+
assert.ok(!required.has('deploymentService'))
|
|
83
|
+
assert.ok(!required.has('credentialService'))
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test('an addon-created service falls back to the full parent blanket', () => {
|
|
87
|
+
const state = makeState({
|
|
88
|
+
usedFunctions: ['console:editCode'],
|
|
89
|
+
addonFunctions: {
|
|
90
|
+
console: {
|
|
91
|
+
editCode: {
|
|
92
|
+
services: {
|
|
93
|
+
optimized: true,
|
|
94
|
+
services: ['codeEditService', 'metaService'],
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
addonRequiredParentServices: CONSOLE_PARENT_SERVICES,
|
|
100
|
+
})
|
|
101
|
+
aggregateRequiredServices(state)
|
|
102
|
+
const required = state.serviceAggregation.requiredServices
|
|
103
|
+
for (const service of CONSOLE_PARENT_SERVICES) {
|
|
104
|
+
assert.ok(required.has(service), `expected blanket to add ${service}`)
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test('missing services meta (old addon build) falls back to the blanket', () => {
|
|
109
|
+
const state = makeState({
|
|
110
|
+
usedFunctions: ['console:getSchema'],
|
|
111
|
+
addonFunctions: { console: { getSchema: {} } },
|
|
112
|
+
addonRequiredParentServices: CONSOLE_PARENT_SERVICES,
|
|
113
|
+
})
|
|
114
|
+
aggregateRequiredServices(state)
|
|
115
|
+
const required = state.serviceAggregation.requiredServices
|
|
116
|
+
for (const service of CONSOLE_PARENT_SERVICES) {
|
|
117
|
+
assert.ok(required.has(service), `expected blanket to add ${service}`)
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('a bare project function colliding with an addon function name adds nothing', () => {
|
|
122
|
+
const state = makeState({
|
|
123
|
+
usedFunctions: ['getAgentThreads'],
|
|
124
|
+
functionsMeta: {
|
|
125
|
+
getAgentThreads: {
|
|
126
|
+
services: { optimized: true, services: ['kysely'] },
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
addonFunctions: {
|
|
130
|
+
console: {
|
|
131
|
+
getAgentThreads: {
|
|
132
|
+
services: { optimized: true, services: ['metaService'] },
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
addonRequiredParentServices: CONSOLE_PARENT_SERVICES,
|
|
137
|
+
})
|
|
138
|
+
aggregateRequiredServices(state)
|
|
139
|
+
const required = state.serviceAggregation.requiredServices
|
|
140
|
+
assert.ok(required.has('kysely'))
|
|
141
|
+
assert.ok(!required.has('metaService'))
|
|
142
|
+
assert.ok(!required.has('aiAgentRunner'))
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test('no used addon functions adds no parent services', () => {
|
|
146
|
+
const state = makeState({
|
|
147
|
+
usedFunctions: ['listTasks'],
|
|
148
|
+
functionsMeta: {
|
|
149
|
+
listTasks: { services: { optimized: true, services: ['kysely'] } },
|
|
150
|
+
},
|
|
151
|
+
addonFunctions: {
|
|
152
|
+
console: {
|
|
153
|
+
getSchema: {
|
|
154
|
+
services: { optimized: true, services: ['metaService'] },
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
addonRequiredParentServices: CONSOLE_PARENT_SERVICES,
|
|
159
|
+
})
|
|
160
|
+
aggregateRequiredServices(state)
|
|
161
|
+
const required = state.serviceAggregation.requiredServices
|
|
162
|
+
assert.ok(!required.has('metaService'))
|
|
163
|
+
assert.ok(!required.has('aiAgentRunner'))
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
test('internal services in addon function meta never force the blanket', () => {
|
|
167
|
+
const state = makeState({
|
|
168
|
+
usedFunctions: ['console:getSchema'],
|
|
169
|
+
addonFunctions: {
|
|
170
|
+
console: {
|
|
171
|
+
getSchema: {
|
|
172
|
+
services: {
|
|
173
|
+
optimized: true,
|
|
174
|
+
services: ['metaService', 'rpc', 'channel'],
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
addonRequiredParentServices: CONSOLE_PARENT_SERVICES,
|
|
180
|
+
})
|
|
181
|
+
aggregateRequiredServices(state)
|
|
182
|
+
const required = state.serviceAggregation.requiredServices
|
|
183
|
+
assert.ok(required.has('metaService'))
|
|
184
|
+
assert.ok(!required.has('aiAgentRunner'))
|
|
185
|
+
assert.ok(!required.has('rpc'))
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
test('default framework services in addon function meta never force the blanket', () => {
|
|
189
|
+
const state = makeState({
|
|
190
|
+
usedFunctions: ['ext:goodbye'],
|
|
191
|
+
addonFunctions: {
|
|
192
|
+
ext: {
|
|
193
|
+
goodbye: {
|
|
194
|
+
services: { optimized: true, services: ['logger', 'config'] },
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
addonRequiredParentServices: ['greetingStore', 'auditSink'],
|
|
199
|
+
})
|
|
200
|
+
aggregateRequiredServices(state)
|
|
201
|
+
const required = state.serviceAggregation.requiredServices
|
|
202
|
+
assert.ok(!required.has('greetingStore'))
|
|
203
|
+
assert.ok(!required.has('auditSink'))
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
test('a ref()-wired route (inline id + namespaced target) aggregates the target services', () => {
|
|
207
|
+
const state = makeState({
|
|
208
|
+
usedFunctions: [
|
|
209
|
+
'http:get:/function-tests/stream',
|
|
210
|
+
'console:streamFunctionTests',
|
|
211
|
+
],
|
|
212
|
+
functionsMeta: {
|
|
213
|
+
'http:get:/function-tests/stream': {
|
|
214
|
+
services: { optimized: false, services: [] },
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
addonFunctions: {
|
|
218
|
+
console: {
|
|
219
|
+
streamFunctionTests: {
|
|
220
|
+
services: { optimized: true, services: ['metaService'] },
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
addonRequiredParentServices: CONSOLE_PARENT_SERVICES,
|
|
225
|
+
})
|
|
226
|
+
aggregateRequiredServices(state)
|
|
227
|
+
const required = state.serviceAggregation.requiredServices
|
|
228
|
+
assert.ok(required.has('metaService'))
|
|
229
|
+
assert.ok(!required.has('aiAgentRunner'))
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
test('multiple used addon functions union their parent services', () => {
|
|
233
|
+
const state = makeState({
|
|
234
|
+
usedFunctions: ['console:getSchema', 'console:runAgent'],
|
|
235
|
+
addonFunctions: {
|
|
236
|
+
console: {
|
|
237
|
+
getSchema: {
|
|
238
|
+
services: { optimized: true, services: ['metaService'] },
|
|
239
|
+
},
|
|
240
|
+
runAgent: {
|
|
241
|
+
services: { optimized: true, services: ['aiAgentRunner'] },
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
addonRequiredParentServices: CONSOLE_PARENT_SERVICES,
|
|
246
|
+
})
|
|
247
|
+
aggregateRequiredServices(state)
|
|
248
|
+
const required = state.serviceAggregation.requiredServices
|
|
249
|
+
assert.ok(required.has('metaService'))
|
|
250
|
+
assert.ok(required.has('aiAgentRunner'))
|
|
251
|
+
assert.ok(!required.has('deploymentService'))
|
|
252
|
+
assert.ok(!required.has('credentialService'))
|
|
253
|
+
})
|
|
254
|
+
})
|
|
@@ -325,20 +325,47 @@ export function aggregateRequiredServices(
|
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
// 7. Services that consumed addons need from the parent project.
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
328
|
+
const addonFnServices = new Map<string, string[] | undefined>()
|
|
329
|
+
for (const [namespace, fns] of Object.entries(state.addonFunctions ?? {})) {
|
|
330
|
+
for (const [id, meta] of Object.entries(fns)) {
|
|
331
|
+
addonFnServices.set(
|
|
332
|
+
`${namespace}:${id}`,
|
|
333
|
+
(meta as { services?: FunctionServicesMeta })?.services?.services
|
|
334
|
+
)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const parentDeclared = state.addonRequiredParentServices ?? []
|
|
338
|
+
const parentDeclaredSet = new Set(parentDeclared)
|
|
339
|
+
const defaultServices = new Set([
|
|
340
|
+
'config',
|
|
341
|
+
'logger',
|
|
342
|
+
'variables',
|
|
343
|
+
'schema',
|
|
344
|
+
'secrets',
|
|
345
|
+
])
|
|
346
|
+
let usesAddonFn = false
|
|
347
|
+
let addonFactoryNeeded = false
|
|
348
|
+
for (const funcId of usedFunctions) {
|
|
349
|
+
if (!addonFnServices.has(funcId)) continue
|
|
350
|
+
usesAddonFn = true
|
|
351
|
+
const services = addonFnServices.get(funcId)
|
|
352
|
+
if (!services) {
|
|
353
|
+
addonFactoryNeeded = true
|
|
354
|
+
continue
|
|
355
|
+
}
|
|
356
|
+
for (const service of services) {
|
|
357
|
+
if (parentDeclaredSet.has(service)) {
|
|
358
|
+
requiredServices.add(service)
|
|
359
|
+
} else if (
|
|
360
|
+
!internalServices.has(service) &&
|
|
361
|
+
!defaultServices.has(service)
|
|
362
|
+
) {
|
|
363
|
+
addonFactoryNeeded = true
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (usesAddonFn && addonFactoryNeeded) {
|
|
368
|
+
for (const service of parentDeclared) {
|
|
342
369
|
requiredServices.add(service)
|
|
343
370
|
}
|
|
344
371
|
}
|
|
@@ -155,6 +155,7 @@ export interface SerializableInspectorState {
|
|
|
155
155
|
exposedMeta: InspectorState['rpc']['exposedMeta']
|
|
156
156
|
exposedFiles: Array<[string, { path: string; exportedName: string }]>
|
|
157
157
|
invokedFunctions: string[]
|
|
158
|
+
invokedFunctionsByFile?: Array<[string, string[]]>
|
|
158
159
|
usedAddons: string[]
|
|
159
160
|
wireAddonDeclarations: Array<
|
|
160
161
|
[
|
|
@@ -379,6 +380,11 @@ export function serializeInspectorState(
|
|
|
379
380
|
exposedMeta: state.rpc.exposedMeta,
|
|
380
381
|
exposedFiles: Array.from(state.rpc.exposedFiles.entries()),
|
|
381
382
|
invokedFunctions: Array.from(state.rpc.invokedFunctions),
|
|
383
|
+
invokedFunctionsByFile: Array.from(
|
|
384
|
+
(
|
|
385
|
+
state.rpc.invokedFunctionsByFile ?? new Map<string, Set<string>>()
|
|
386
|
+
).entries()
|
|
387
|
+
).map(([file, fns]): [string, string[]] => [file, Array.from(fns)]),
|
|
382
388
|
usedAddons: Array.from(state.rpc.usedAddons),
|
|
383
389
|
wireAddonDeclarations: Array.from(
|
|
384
390
|
state.rpc.wireAddonDeclarations.entries()
|
|
@@ -565,6 +571,11 @@ export function deserializeInspectorState(
|
|
|
565
571
|
exposedMeta: data.rpc.exposedMeta,
|
|
566
572
|
exposedFiles: new Map(data.rpc.exposedFiles),
|
|
567
573
|
invokedFunctions: new Set(data.rpc.invokedFunctions),
|
|
574
|
+
invokedFunctionsByFile: new Map(
|
|
575
|
+
(data.rpc.invokedFunctionsByFile || []).map(
|
|
576
|
+
([file, fns]): [string, Set<string>] => [file, new Set(fns)]
|
|
577
|
+
)
|
|
578
|
+
),
|
|
568
579
|
usedAddons: new Set(data.rpc.usedAddons || []),
|
|
569
580
|
wireAddonDeclarations: new Map(data.rpc.wireAddonDeclarations || []),
|
|
570
581
|
wireAddonFiles: new Set(data.rpc.wireAddonFiles || []),
|
|
@@ -6,7 +6,7 @@ import * as ts from 'typescript'
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Check if a call expression is workflow.do() or workflow.expectEventually()
|
|
9
|
-
* (both are RPC steps; expectEventually is the polling variant used by
|
|
9
|
+
* (both are RPC steps; expectEventually is the polling variant used by scenarios)
|
|
10
10
|
*/
|
|
11
11
|
export function isWorkflowDoCall(
|
|
12
12
|
node: ts.CallExpression,
|
|
@@ -396,11 +396,11 @@ export function convertDslToGraph(
|
|
|
396
396
|
const entryNodeIds = nodes.length > 0 ? [nodes[0].nodeId] : []
|
|
397
397
|
|
|
398
398
|
// Determine source type:
|
|
399
|
-
// -
|
|
399
|
+
// - scenario: complex workflow whose steps run as actors (scenario)
|
|
400
400
|
// - dsl === true: pure DSL workflow, can be serialized
|
|
401
401
|
// - dsl === false: complex workflow with inline steps, not serializable
|
|
402
|
-
const source = meta.
|
|
403
|
-
? '
|
|
402
|
+
const source = meta.scenario
|
|
403
|
+
? 'scenario'
|
|
404
404
|
: meta.dsl === false
|
|
405
405
|
? 'complex'
|
|
406
406
|
: 'dsl'
|
|
@@ -142,9 +142,9 @@ 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
|
-
/**
|
|
145
|
+
/** Scenario actor name this step runs as (workflow.do {actor: actors.x}) */
|
|
146
146
|
actor?: string
|
|
147
|
-
/** True for workflow.expectEventually polling steps (
|
|
147
|
+
/** True for workflow.expectEventually polling steps (scenarios) */
|
|
148
148
|
expectEventually?: boolean
|
|
149
149
|
}
|
|
150
150
|
|
|
@@ -181,9 +181,9 @@ export const isFlowNode = (node: SerializedGraphNode): node is FlowNode =>
|
|
|
181
181
|
* - 'dsl': Pure DSL workflow (pikkuWorkflowFunc) - can be round-tripped to code
|
|
182
182
|
* - 'complex': Complex workflow (pikkuWorkflowComplexFunc) - contains inline steps, not serializable
|
|
183
183
|
* - 'graph': Graph-based workflow (pikkuWorkflowGraph)
|
|
184
|
-
* - '
|
|
184
|
+
* - 'scenario': Scenario (pikkuScenario) - complex workflow whose steps run as actors
|
|
185
185
|
*/
|
|
186
|
-
export type WorkflowSourceType = 'dsl' | 'complex' | 'graph' | '
|
|
186
|
+
export type WorkflowSourceType = 'dsl' | 'complex' | 'graph' | 'scenario'
|
|
187
187
|
|
|
188
188
|
/**
|
|
189
189
|
* Serialized workflow graph - the canonical JSON format
|
|
@@ -201,7 +201,7 @@ export interface SerializedWorkflowGraph {
|
|
|
201
201
|
description?: string
|
|
202
202
|
/** Tags for organization */
|
|
203
203
|
tags?: string[]
|
|
204
|
-
/** Actor names a
|
|
204
|
+
/** Actor names a scenario's steps run as */
|
|
205
205
|
actors?: string[]
|
|
206
206
|
/** If true, workflow always executes inline without queues */
|
|
207
207
|
inline?: boolean
|