@pikku/inspector 0.12.74 → 0.12.81
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 +347 -0
- package/dist/add/add-analytics.d.ts +9 -0
- package/dist/add/add-analytics.js +178 -0
- package/dist/add/add-auth.js +0 -54
- package/dist/add/add-feature-flag.d.ts +2 -0
- package/dist/add/add-feature-flag.js +162 -0
- package/dist/add/add-wire-addon.js +1 -0
- package/dist/add/add-wire-remote-addon.js +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/inspector.js +4 -0
- package/dist/surface/collect-surface.d.ts +58 -0
- package/dist/surface/collect-surface.js +613 -0
- package/dist/types.d.ts +20 -11
- package/dist/utils/filter-inspector-state.js +23 -13
- package/dist/utils/post-process.js +15 -0
- package/dist/utils/read-module-specifiers.d.ts +8 -0
- package/dist/utils/read-module-specifiers.js +23 -0
- package/dist/utils/read-tsconfig-out-dir.d.ts +5 -0
- package/dist/utils/read-tsconfig-out-dir.js +12 -0
- package/dist/utils/resolve-deploy-target.d.ts +7 -0
- package/dist/utils/resolve-deploy-target.js +15 -7
- package/dist/utils/serialize-inspector-state.d.ts +5 -0
- package/dist/utils/serialize-inspector-state.js +8 -0
- package/dist/visit.js +4 -0
- package/package.json +16 -6
- package/run-tests.sh +21 -6
- package/src/add/add-addon-bans.ts +0 -85
- package/src/add/add-agent-scorer.test.ts +0 -197
- package/src/add/add-agent-scorer.ts +0 -83
- package/src/add/add-agent.test.ts +0 -87
- package/src/add/add-agent.ts +0 -638
- package/src/add/add-approval-description.ts +0 -76
- package/src/add/add-auth.test.ts +0 -791
- package/src/add/add-auth.ts +0 -424
- package/src/add/add-channel.ts +0 -778
- package/src/add/add-cli-renderers.test.ts +0 -74
- package/src/add/add-cli.ts +0 -1006
- package/src/add/add-credential.ts +0 -186
- package/src/add/add-feature.test.ts +0 -306
- package/src/add/add-feature.ts +0 -253
- package/src/add/add-file-extends-core-type.ts +0 -66
- package/src/add/add-file-with-config.ts +0 -45
- package/src/add/add-file-with-factory.test.ts +0 -134
- package/src/add/add-file-with-factory.ts +0 -288
- package/src/add/add-functions.test.ts +0 -743
- package/src/add/add-functions.ts +0 -1548
- package/src/add/add-gateway.ts +0 -143
- package/src/add/add-http-route-ref.test.ts +0 -149
- package/src/add/add-http-route.ts +0 -530
- package/src/add/add-http-routes.ts +0 -471
- package/src/add/add-keyed-wiring.ts +0 -180
- package/src/add/add-mcp-prompt.ts +0 -157
- package/src/add/add-mcp-resource.ts +0 -175
- package/src/add/add-middleware.test.ts +0 -196
- package/src/add/add-middleware.ts +0 -879
- package/src/add/add-permission-auth.test.ts +0 -59
- package/src/add/add-permission.ts +0 -311
- package/src/add/add-personas.test.ts +0 -406
- package/src/add/add-personas.ts +0 -287
- package/src/add/add-queue-worker.ts +0 -117
- package/src/add/add-rpc-invocations.test.ts +0 -287
- package/src/add/add-rpc-invocations.ts +0 -221
- package/src/add/add-scenario-step.test.ts +0 -509
- package/src/add/add-schedule.ts +0 -120
- package/src/add/add-scope.test.ts +0 -419
- package/src/add/add-scope.ts +0 -221
- package/src/add/add-secret.ts +0 -10
- package/src/add/add-subject-scenario-steps.test.ts +0 -161
- package/src/add/add-system-role.test.ts +0 -297
- package/src/add/add-system-role.ts +0 -162
- package/src/add/add-trigger.ts +0 -165
- package/src/add/add-variable.ts +0 -10
- package/src/add/add-wire-addon.test.ts +0 -185
- package/src/add/add-wire-addon.ts +0 -146
- package/src/add/add-wire-remote-addon.ts +0 -80
- package/src/add/add-workflow-edge-cases.test.ts +0 -201
- package/src/add/add-workflow-fanout.test.ts +0 -166
- package/src/add/add-workflow-graph-foreach.test.ts +0 -174
- package/src/add/add-workflow-graph-input.test.ts +0 -94
- package/src/add/add-workflow-graph-notes.test.ts +0 -74
- package/src/add/add-workflow-graph.ts +0 -644
- package/src/add/add-workflow-on-error.test.ts +0 -118
- package/src/add/add-workflow-return-switch.test.ts +0 -121
- package/src/add/add-workflow-silent-drops.test.ts +0 -261
- package/src/add/add-workflow-sleep-expression.test.ts +0 -96
- package/src/add/add-workflow.test.ts +0 -155
- package/src/add/add-workflow.ts +0 -482
- package/src/add/addon-bans.test.ts +0 -121
- package/src/add/addon-contracts.test.ts +0 -221
- package/src/add/allow-escape-hatches.test.ts +0 -144
- package/src/add/audit-meta.test.ts +0 -75
- package/src/add/classified-sink-check.test.ts +0 -347
- package/src/add/config-value-metadata.test.ts +0 -263
- package/src/add/dynamic-import-check.test.ts +0 -119
- package/src/add/expect-eventually-scenario-only.test.ts +0 -108
- package/src/add/function-body-span.test.ts +0 -96
- package/src/add/inline-wiring-function.test.ts +0 -104
- package/src/add/pii-check.test.ts +0 -258
- package/src/add/rpc-type-cast.test.ts +0 -123
- package/src/add/scenario-instrumentation.test.ts +0 -89
- package/src/add/scenario-instrumentation.ts +0 -42
- package/src/add/scenario-marker.test.ts +0 -89
- package/src/add/scenario-not-extractable.test.ts +0 -159
- package/src/add/scenario-services.test.ts +0 -103
- package/src/add/scenario-skip.test.ts +0 -83
- package/src/add/validate-workflow-graph-addons.test.ts +0 -102
- package/src/add/wire-name-literal.test.ts +0 -117
- package/src/error-codes-emitted.test.ts +0 -76
- package/src/error-codes.ts +0 -145
- package/src/index.ts +0 -66
- package/src/inspector.ts +0 -575
- package/src/schema-generator.ts +0 -1
- package/src/source-file-cache.ts +0 -73
- package/src/tsconfig-paths.test.ts +0 -90
- package/src/types-map.ts +0 -141
- package/src/types.ts +0 -775
- package/src/utils/annotate-http-route-auth.test.ts +0 -161
- package/src/utils/annotate-http-route-auth.ts +0 -53
- package/src/utils/check-classified-sinks.ts +0 -215
- package/src/utils/check-pii-output.ts +0 -120
- package/src/utils/compute-diagnostics.test.ts +0 -106
- package/src/utils/compute-required-schemas.ts +0 -48
- package/src/utils/contract-hashes.test.ts +0 -553
- package/src/utils/contract-hashes.ts +0 -387
- package/src/utils/custom-types-generator.test.ts +0 -124
- package/src/utils/custom-types-generator.ts +0 -150
- package/src/utils/detect-schema-vendor.test.ts +0 -123
- package/src/utils/detect-schema-vendor.ts +0 -120
- package/src/utils/does-type-extend-core-type.ts +0 -53
- package/src/utils/ensure-function-metadata.ts +0 -385
- package/src/utils/extract-function-name.ts +0 -612
- package/src/utils/extract-function-node.ts +0 -70
- package/src/utils/extract-node-value.test.ts +0 -117
- package/src/utils/extract-node-value.ts +0 -172
- package/src/utils/extract-secret-usage.test.ts +0 -81
- package/src/utils/extract-secret-usage.ts +0 -73
- package/src/utils/extract-services.ts +0 -70
- package/src/utils/extract-surface-usage.test.ts +0 -147
- package/src/utils/extract-surface-usage.ts +0 -121
- package/src/utils/filter-inspector-state.test.ts +0 -2207
- package/src/utils/filter-inspector-state.ts +0 -1302
- package/src/utils/filter-utils.test.ts +0 -843
- package/src/utils/filter-utils.ts +0 -161
- package/src/utils/find-root-dir.ts +0 -68
- package/src/utils/find-zod-transform.test.ts +0 -70
- package/src/utils/find-zod-transform.ts +0 -103
- package/src/utils/get-exported-variable-name.ts +0 -48
- package/src/utils/get-files-and-methods.ts +0 -157
- package/src/utils/get-property-value.test.ts +0 -141
- package/src/utils/get-property-value.ts +0 -331
- package/src/utils/hash.ts +0 -26
- package/src/utils/literal-properties.ts +0 -97
- package/src/utils/load-addon-functions-meta.test.ts +0 -157
- package/src/utils/load-addon-functions-meta.ts +0 -508
- package/src/utils/load-addon-scopes.test.ts +0 -138
- package/src/utils/middleware.test.ts +0 -204
- package/src/utils/middleware.ts +0 -303
- package/src/utils/nested-project-filter.test.ts +0 -53
- package/src/utils/nested-project-filter.ts +0 -28
- package/src/utils/permissions-shorthand.test.ts +0 -79
- package/src/utils/permissions.test.ts +0 -125
- package/src/utils/permissions.ts +0 -129
- package/src/utils/post-process.test.ts +0 -1094
- package/src/utils/post-process.ts +0 -1604
- package/src/utils/resolve-addon-package.ts +0 -106
- package/src/utils/resolve-core-type.test.ts +0 -142
- package/src/utils/resolve-core-type.ts +0 -35
- package/src/utils/resolve-deploy-target.test.ts +0 -135
- package/src/utils/resolve-deploy-target.ts +0 -65
- package/src/utils/resolve-function-meta.ts +0 -25
- package/src/utils/resolve-function-types.ts +0 -42
- package/src/utils/resolve-identifier.ts +0 -46
- package/src/utils/resolve-ref-contract.ts +0 -71
- package/src/utils/resolve-versions.test.ts +0 -499
- package/src/utils/resolve-versions.ts +0 -228
- package/src/utils/schema-cache.test.ts +0 -124
- package/src/utils/schema-generator.test.ts +0 -272
- package/src/utils/schema-generator.ts +0 -780
- package/src/utils/secret-alias-services.test.ts +0 -146
- package/src/utils/secret-alias-services.ts +0 -58
- package/src/utils/serialize-inspector-state.ts +0 -742
- package/src/utils/serialize-mcp-json.ts +0 -150
- package/src/utils/serialize-middleware-groups-meta.ts +0 -36
- package/src/utils/serialize-openapi-json.ts +0 -277
- package/src/utils/serialize-permissions-groups-meta.ts +0 -7
- package/src/utils/single-declaration.ts +0 -57
- package/src/utils/type-utils.ts +0 -145
- package/src/utils/validate-auth-sessionless.ts +0 -30
- package/src/utils/validate-exposed-functions-gated.test.ts +0 -230
- package/src/utils/validate-exposed-functions-gated.ts +0 -103
- package/src/utils/validate-tags-resolve-to-middleware.test.ts +0 -108
- package/src/utils/validate-tags-resolve-to-middleware.ts +0 -48
- package/src/utils/workflow/derive-workflow-plan.test.ts +0 -178
- package/src/utils/workflow/derive-workflow-plan.ts +0 -117
- package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +0 -1465
- package/src/utils/workflow/dsl/deserialize-fanout-body.test.ts +0 -71
- package/src/utils/workflow/dsl/deserialize-fanout-fidelity.test.ts +0 -99
- package/src/utils/workflow/dsl/deserialize-graph-next.test.ts +0 -161
- package/src/utils/workflow/dsl/deserialize-roundtrip-bindings.test.ts +0 -121
- package/src/utils/workflow/dsl/deserialize-roundtrip-values.test.ts +0 -116
- package/src/utils/workflow/dsl/deserialize-roundtrip.test.ts +0 -145
- package/src/utils/workflow/dsl/extract-approval-policy.test.ts +0 -71
- package/src/utils/workflow/dsl/extract-dsl-workflow.ts +0 -2254
- package/src/utils/workflow/dsl/index.ts +0 -11
- package/src/utils/workflow/dsl/patterns.ts +0 -440
- package/src/utils/workflow/dsl/validation.ts +0 -203
- package/src/utils/workflow/graph/convert-dsl-to-graph-skip.test.ts +0 -35
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +0 -543
- package/src/utils/workflow/graph/fanout-node-identity.test.ts +0 -105
- package/src/utils/workflow/graph/finalize-workflow-wires.ts +0 -310
- package/src/utils/workflow/graph/finalize-workflows.test.ts +0 -50
- package/src/utils/workflow/graph/finalize-workflows.ts +0 -130
- package/src/utils/workflow/graph/index.ts +0 -28
- package/src/utils/workflow/graph/serialize-workflow-graph.ts +0 -240
- package/src/utils/workflow/graph/workflow-graph.types.ts +0 -281
- package/src/visit.ts +0 -181
- package/tsconfig.json +0 -19
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
import { strict as assert } from 'assert'
|
|
2
|
-
import { describe, test } from 'node:test'
|
|
3
|
-
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
|
|
4
|
-
import { tmpdir } from 'node:os'
|
|
5
|
-
import { join } from 'node:path'
|
|
6
|
-
import { inspect } from '../inspector.js'
|
|
7
|
-
import type { InspectorLogger } from '../types.js'
|
|
8
|
-
import { deserializeDslWorkflow } from '../utils/workflow/dsl/deserialize-dsl-workflow.js'
|
|
9
|
-
|
|
10
|
-
const STEP_FILE = [
|
|
11
|
-
"import { pikkuSessionlessFunc } from '@pikku/core'",
|
|
12
|
-
'export const chargeCard = pikkuSessionlessFunc({',
|
|
13
|
-
" func: async () => ({ id: 'c' }),",
|
|
14
|
-
'})',
|
|
15
|
-
'export const refundOrder = pikkuSessionlessFunc({',
|
|
16
|
-
" func: async () => ({ id: 'r' }),",
|
|
17
|
-
'})',
|
|
18
|
-
'export const notify = pikkuSessionlessFunc({',
|
|
19
|
-
" func: async () => ({ id: 'n' }),",
|
|
20
|
-
'})',
|
|
21
|
-
'export const audit = pikkuSessionlessFunc({',
|
|
22
|
-
" func: async () => ({ id: 'a' }),",
|
|
23
|
-
'})',
|
|
24
|
-
].join('\n')
|
|
25
|
-
|
|
26
|
-
async function run(body: string): Promise<{
|
|
27
|
-
nodes: Record<string, any>
|
|
28
|
-
diags: string[]
|
|
29
|
-
code: string
|
|
30
|
-
}> {
|
|
31
|
-
const diags: string[] = []
|
|
32
|
-
const logger = {
|
|
33
|
-
debug: () => {},
|
|
34
|
-
info: () => {},
|
|
35
|
-
warn: () => {},
|
|
36
|
-
error: () => {},
|
|
37
|
-
diagnostic: (...a: any[]) => diags.push(a.map(String).join(' ')),
|
|
38
|
-
critical: (...a: any[]) => diags.push(a.map(String).join(' ')),
|
|
39
|
-
hasCriticalErrors: () => false,
|
|
40
|
-
} as unknown as InspectorLogger
|
|
41
|
-
|
|
42
|
-
const rootDir = await mkdtemp(join(tmpdir(), 'pikku-edge-'))
|
|
43
|
-
const stepFile = join(rootDir, 'w.steps.ts')
|
|
44
|
-
const wfFile = join(rootDir, 'w.workflow.ts')
|
|
45
|
-
await writeFile(stepFile, STEP_FILE)
|
|
46
|
-
await writeFile(
|
|
47
|
-
wfFile,
|
|
48
|
-
[
|
|
49
|
-
"import { pikkuWorkflowFunc } from '@pikku/core/workflow'",
|
|
50
|
-
'export const wf = pikkuWorkflowFunc(async (_, data: any, { workflow }) => {',
|
|
51
|
-
body,
|
|
52
|
-
'})',
|
|
53
|
-
].join('\n')
|
|
54
|
-
)
|
|
55
|
-
try {
|
|
56
|
-
const state: any = await inspect(logger, [stepFile, wfFile], { rootDir })
|
|
57
|
-
const gm = state.workflows.graphMeta
|
|
58
|
-
const graph = (
|
|
59
|
-
gm instanceof Map ? [...gm.values()] : Object.values(gm)
|
|
60
|
-
)[0] as any
|
|
61
|
-
return {
|
|
62
|
-
nodes: graph?.nodes ?? {},
|
|
63
|
-
diags,
|
|
64
|
-
code: graph ? deserializeDslWorkflow(graph) : '',
|
|
65
|
-
}
|
|
66
|
-
} finally {
|
|
67
|
-
await rm(rootDir, { recursive: true, force: true })
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
describe('onError — edge cases', () => {
|
|
72
|
-
test('the handler is not also emitted as a step that always runs', async () => {
|
|
73
|
-
const { code } = await run(
|
|
74
|
-
[
|
|
75
|
-
" await workflow.do('Charge', 'chargeCard', {}, { onError: 'refundOrder' })",
|
|
76
|
-
' return { ok: true }',
|
|
77
|
-
].join('\n')
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
const refundCalls = code.match(/'refundOrder'/g) ?? []
|
|
81
|
-
assert.equal(
|
|
82
|
-
refundCalls.length,
|
|
83
|
-
1,
|
|
84
|
-
`the handler must appear only as onError, never as its own sequential step, got:\n${code}`
|
|
85
|
-
)
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
test('a non-literal onError is reported, not silently ignored', async () => {
|
|
89
|
-
const { diags } = await run(
|
|
90
|
-
[
|
|
91
|
-
' const target = data.handler',
|
|
92
|
-
" await workflow.do('Charge', 'chargeCard', {}, { onError: target })",
|
|
93
|
-
' return { ok: true }',
|
|
94
|
-
].join('\n')
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
assert.ok(
|
|
98
|
-
diags.some((d) => d.includes('onError')),
|
|
99
|
-
`a non-literal onError must be diagnosed, got: ${JSON.stringify(diags)}`
|
|
100
|
-
)
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
test('onError inside a branch still resolves', async () => {
|
|
104
|
-
const { nodes } = await run(
|
|
105
|
-
[
|
|
106
|
-
' if (data.paid) {',
|
|
107
|
-
" await workflow.do('Charge', 'chargeCard', {}, { onError: 'refundOrder' })",
|
|
108
|
-
' }',
|
|
109
|
-
' return { ok: true }',
|
|
110
|
-
].join('\n')
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
const charge = Object.values(nodes).find(
|
|
114
|
-
(n: any) => n.rpcName === 'chargeCard'
|
|
115
|
-
) as any
|
|
116
|
-
assert.ok(
|
|
117
|
-
charge?.onError,
|
|
118
|
-
'a branch step should still carry its error route'
|
|
119
|
-
)
|
|
120
|
-
assert.equal((nodes[charge.onError] as any)?.rpcName, 'refundOrder')
|
|
121
|
-
})
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
describe('switch — fall-through to default', () => {
|
|
125
|
-
test('a trailing empty case routes to the default body', async () => {
|
|
126
|
-
const { nodes } = await run(
|
|
127
|
-
[
|
|
128
|
-
' switch (data.kind) {',
|
|
129
|
-
" case 'a':",
|
|
130
|
-
" await workflow.do('Notify', 'notify', {})",
|
|
131
|
-
' break',
|
|
132
|
-
" case 'b':",
|
|
133
|
-
' default:',
|
|
134
|
-
" await workflow.do('Audit', 'audit', {})",
|
|
135
|
-
' }',
|
|
136
|
-
' return { ok: true }',
|
|
137
|
-
].join('\n')
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
const sw = Object.values(nodes).find((n: any) => n.flow === 'switch') as any
|
|
141
|
-
assert.ok(sw, 'a switch node should exist')
|
|
142
|
-
const caseB = (sw.cases ?? []).find((c: any) => c.value === 'b')
|
|
143
|
-
assert.ok(
|
|
144
|
-
caseB,
|
|
145
|
-
`a case falling through to default must not be dropped, got: ${JSON.stringify(sw.cases)}`
|
|
146
|
-
)
|
|
147
|
-
assert.equal(
|
|
148
|
-
caseB.entry,
|
|
149
|
-
sw.defaultEntry,
|
|
150
|
-
'it should route to the default body it falls into'
|
|
151
|
-
)
|
|
152
|
-
})
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
describe('options — numeric values keep their type', () => {
|
|
156
|
-
test('a numeric retryDelay is not quoted', async () => {
|
|
157
|
-
const { code } = await run(
|
|
158
|
-
[
|
|
159
|
-
" await workflow.do('Charge', 'chargeCard', {}, { retries: 2, retryDelay: 500 })",
|
|
160
|
-
' return { ok: true }',
|
|
161
|
-
].join('\n')
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
assert.ok(
|
|
165
|
-
code.includes('retryDelay: 500'),
|
|
166
|
-
`a numeric retryDelay must stay numeric, got:\n${code}`
|
|
167
|
-
)
|
|
168
|
-
})
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
describe('sequential fanout — for..of bodies', () => {
|
|
172
|
-
test('a multi-step for..of body keeps every step', async () => {
|
|
173
|
-
const { nodes } = await run(
|
|
174
|
-
[
|
|
175
|
-
' for (const u of data.users) {',
|
|
176
|
-
" const c = await workflow.do('Charge', 'chargeCard', { id: u.id })",
|
|
177
|
-
" await workflow.do('Notify', 'notify', { id: c.id })",
|
|
178
|
-
' }',
|
|
179
|
-
' return { ok: true }',
|
|
180
|
-
].join('\n')
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
const fanout = Object.values(nodes).find(
|
|
184
|
-
(n: any) => n.flow === 'fanout'
|
|
185
|
-
) as any
|
|
186
|
-
assert.ok(fanout, 'a for..of should produce a fanout node')
|
|
187
|
-
assert.notEqual(
|
|
188
|
-
fanout.nodeId,
|
|
189
|
-
fanout.childEntry,
|
|
190
|
-
'the loop and its body entry must not share an id'
|
|
191
|
-
)
|
|
192
|
-
|
|
193
|
-
const rpcs = Object.values(nodes)
|
|
194
|
-
.filter((n: any) => n.rpcName)
|
|
195
|
-
.map((n: any) => n.rpcName)
|
|
196
|
-
assert.ok(
|
|
197
|
-
rpcs.includes('chargeCard') && rpcs.includes('notify'),
|
|
198
|
-
`both body steps must survive, got: ${JSON.stringify(rpcs)}`
|
|
199
|
-
)
|
|
200
|
-
})
|
|
201
|
-
})
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import { strict as assert } from 'assert'
|
|
2
|
-
import { describe, test } from 'node:test'
|
|
3
|
-
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
|
|
4
|
-
import { tmpdir } from 'node:os'
|
|
5
|
-
import { join } from 'node:path'
|
|
6
|
-
import { inspect } from '../inspector.js'
|
|
7
|
-
import type { InspectorLogger } from '../types.js'
|
|
8
|
-
|
|
9
|
-
function makeLogger(
|
|
10
|
-
criticals: Array<{ code: string; message: string }>
|
|
11
|
-
): InspectorLogger {
|
|
12
|
-
return {
|
|
13
|
-
debug: () => {},
|
|
14
|
-
info: () => {},
|
|
15
|
-
warn: () => {},
|
|
16
|
-
error: () => {},
|
|
17
|
-
diagnostic: ({ code, message }) => {
|
|
18
|
-
criticals.push({ code, message })
|
|
19
|
-
},
|
|
20
|
-
critical: (code: any, message: string) => {
|
|
21
|
-
criticals.push({ code, message })
|
|
22
|
-
},
|
|
23
|
-
hasCriticalErrors: () => criticals.length > 0,
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const STEP_FILE = [
|
|
28
|
-
"import { pikkuSessionlessFunc } from '@pikku/core'",
|
|
29
|
-
'export const processEventLeadsStep = pikkuSessionlessFunc({',
|
|
30
|
-
' func: async ({ logger }) => ({ persistedCount: 1 }),',
|
|
31
|
-
'})',
|
|
32
|
-
].join('\n')
|
|
33
|
-
|
|
34
|
-
describe('addWorkflow — Promise.all fanout RPC detection', () => {
|
|
35
|
-
test('registers fanout RPC when captured with `const x = await Promise.all(map(...))`', async () => {
|
|
36
|
-
const rootDir = await mkdtemp(join(tmpdir(), 'pikku-fanout-const-'))
|
|
37
|
-
const wfFile = join(rootDir, 'leads.workflow.ts')
|
|
38
|
-
const stepFile = join(rootDir, 'leads.steps.ts')
|
|
39
|
-
|
|
40
|
-
await writeFile(stepFile, STEP_FILE)
|
|
41
|
-
await writeFile(
|
|
42
|
-
wfFile,
|
|
43
|
-
[
|
|
44
|
-
"import { pikkuWorkflowFunc } from '@pikku/core/workflow'",
|
|
45
|
-
'export const extractLeadsWorkflow = pikkuWorkflowFunc(async (_, data, { workflow }) => {',
|
|
46
|
-
' const events = [{ id: "a", name: "x" }]',
|
|
47
|
-
' const processed = await Promise.all(',
|
|
48
|
-
' events.map((event) =>',
|
|
49
|
-
" workflow.do(`Enrich event ${event.id ?? event.name}`, 'processEventLeadsStep', { event })",
|
|
50
|
-
' )',
|
|
51
|
-
' )',
|
|
52
|
-
' return { count: processed.length }',
|
|
53
|
-
'})',
|
|
54
|
-
].join('\n')
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
const criticals: Array<{ code: string; message: string }> = []
|
|
58
|
-
try {
|
|
59
|
-
const state = await inspect(makeLogger(criticals), [stepFile, wfFile], {
|
|
60
|
-
rootDir,
|
|
61
|
-
})
|
|
62
|
-
assert.ok(
|
|
63
|
-
state.rpc.invokedFunctions.has('processEventLeadsStep'),
|
|
64
|
-
'processEventLeadsStep should be registered when fanout is captured with const'
|
|
65
|
-
)
|
|
66
|
-
} finally {
|
|
67
|
-
await rm(rootDir, { recursive: true, force: true })
|
|
68
|
-
}
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
test('registers fanout RPC with string-concatenation (`+`) step name, same as template literal', async () => {
|
|
72
|
-
const rootDir = await mkdtemp(join(tmpdir(), 'pikku-fanout-concat-'))
|
|
73
|
-
const wfFile = join(rootDir, 'leads.workflow.ts')
|
|
74
|
-
const stepFile = join(rootDir, 'leads.steps.ts')
|
|
75
|
-
|
|
76
|
-
await writeFile(stepFile, STEP_FILE)
|
|
77
|
-
await writeFile(
|
|
78
|
-
wfFile,
|
|
79
|
-
[
|
|
80
|
-
"import { pikkuWorkflowFunc } from '@pikku/core/workflow'",
|
|
81
|
-
'export const extractLeadsWorkflow = pikkuWorkflowFunc(async (_, data, { workflow }) => {',
|
|
82
|
-
' const events = [{ id: "a", name: "x" }]',
|
|
83
|
-
' await Promise.all(',
|
|
84
|
-
' events.map((event) =>',
|
|
85
|
-
" workflow.do('Enrich event ' + (event.id ?? event.name), 'processEventLeadsStep', { event })",
|
|
86
|
-
' )',
|
|
87
|
-
' )',
|
|
88
|
-
' return { ok: true }',
|
|
89
|
-
'})',
|
|
90
|
-
].join('\n')
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
const criticals: Array<{ code: string; message: string }> = []
|
|
94
|
-
try {
|
|
95
|
-
const state = await inspect(makeLogger(criticals), [stepFile, wfFile], {
|
|
96
|
-
rootDir,
|
|
97
|
-
})
|
|
98
|
-
assert.ok(
|
|
99
|
-
state.rpc.invokedFunctions.has('processEventLeadsStep'),
|
|
100
|
-
'processEventLeadsStep should be registered even when the step name uses `+` concatenation with a non-static operand'
|
|
101
|
-
)
|
|
102
|
-
} finally {
|
|
103
|
-
await rm(rootDir, { recursive: true, force: true })
|
|
104
|
-
}
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
test('registers every RPC in a multi-step map body, including a `const`-captured step feeding the next', async () => {
|
|
108
|
-
const rootDir = await mkdtemp(join(tmpdir(), 'pikku-fanout-multi-'))
|
|
109
|
-
const wfFile = join(rootDir, 'digest.workflow.ts')
|
|
110
|
-
const stepFile = join(rootDir, 'digest.steps.ts')
|
|
111
|
-
|
|
112
|
-
await writeFile(
|
|
113
|
-
stepFile,
|
|
114
|
-
[
|
|
115
|
-
"import { pikkuSessionlessFunc } from '@pikku/core'",
|
|
116
|
-
'export const getDigestData = pikkuSessionlessFunc({',
|
|
117
|
-
' func: async ({ logger }) => ({ dealsByStage: {}, totalDealValue: 0 }),',
|
|
118
|
-
'})',
|
|
119
|
-
'export const sendDigestEmail = pikkuSessionlessFunc({',
|
|
120
|
-
' func: async ({ logger }) => ({ sent: true }),',
|
|
121
|
-
'})',
|
|
122
|
-
].join('\n')
|
|
123
|
-
)
|
|
124
|
-
await writeFile(
|
|
125
|
-
wfFile,
|
|
126
|
-
[
|
|
127
|
-
"import { pikkuWorkflowFunc } from '@pikku/core/workflow'",
|
|
128
|
-
'export const dailyDigestWorkflow = pikkuWorkflowFunc(async (_, data, { workflow }) => {',
|
|
129
|
-
' const users = [{ id: "1", email: "a@b.c" }]',
|
|
130
|
-
' await Promise.all(',
|
|
131
|
-
' users.map(async (u) => {',
|
|
132
|
-
' const digestData = await workflow.do(',
|
|
133
|
-
' `Get pipeline for ${u.email}`,',
|
|
134
|
-
" 'getDigestData',",
|
|
135
|
-
' { userId: u.id },',
|
|
136
|
-
' )',
|
|
137
|
-
" await workflow.do(`Send digest to ${u.email}`, 'sendDigestEmail', {",
|
|
138
|
-
' email: u.email,',
|
|
139
|
-
' dealsByStage: digestData.dealsByStage,',
|
|
140
|
-
' totalDealValue: digestData.totalDealValue,',
|
|
141
|
-
' })',
|
|
142
|
-
' }),',
|
|
143
|
-
' )',
|
|
144
|
-
' return { sent: users.length }',
|
|
145
|
-
'})',
|
|
146
|
-
].join('\n')
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
const criticals: Array<{ code: string; message: string }> = []
|
|
150
|
-
try {
|
|
151
|
-
const state = await inspect(makeLogger(criticals), [stepFile, wfFile], {
|
|
152
|
-
rootDir,
|
|
153
|
-
})
|
|
154
|
-
assert.ok(
|
|
155
|
-
state.rpc.invokedFunctions.has('sendDigestEmail'),
|
|
156
|
-
'sendDigestEmail should be registered'
|
|
157
|
-
)
|
|
158
|
-
assert.ok(
|
|
159
|
-
state.rpc.invokedFunctions.has('getDigestData'),
|
|
160
|
-
'getDigestData should be registered — a `const`-captured workflow.do inside the map body must not be dropped'
|
|
161
|
-
)
|
|
162
|
-
} finally {
|
|
163
|
-
await rm(rootDir, { recursive: true, force: true })
|
|
164
|
-
}
|
|
165
|
-
})
|
|
166
|
-
})
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import { strict as assert } from 'assert'
|
|
2
|
-
import { describe, test } from 'node:test'
|
|
3
|
-
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
|
|
4
|
-
import { tmpdir } from 'node:os'
|
|
5
|
-
import { join } from 'node:path'
|
|
6
|
-
import { inspect } from '../inspector.js'
|
|
7
|
-
import type { InspectorLogger } from '../types.js'
|
|
8
|
-
|
|
9
|
-
function makeLogger(): InspectorLogger {
|
|
10
|
-
return {
|
|
11
|
-
debug: () => {},
|
|
12
|
-
info: () => {},
|
|
13
|
-
warn: () => {},
|
|
14
|
-
error: () => {},
|
|
15
|
-
diagnostic: () => {},
|
|
16
|
-
critical: () => {},
|
|
17
|
-
hasCriticalErrors: () => false,
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async function inspectGraph(graphSource: string[], graphName: string) {
|
|
22
|
-
const rootDir = await mkdtemp(join(tmpdir(), 'pikku-graph-foreach-'))
|
|
23
|
-
const stepFile = join(rootDir, 'my.steps.ts')
|
|
24
|
-
const graphFile = join(rootDir, 'my.graph.ts')
|
|
25
|
-
|
|
26
|
-
await writeFile(
|
|
27
|
-
stepFile,
|
|
28
|
-
[
|
|
29
|
-
"import { pikkuSessionlessFunc } from '@pikku/core'",
|
|
30
|
-
'export const listRows = pikkuSessionlessFunc({ func: async () => ({ rows: [] }) })',
|
|
31
|
-
'export const postVideo = pikkuSessionlessFunc({ func: async () => ({ ok: true }) })',
|
|
32
|
-
].join('\n')
|
|
33
|
-
)
|
|
34
|
-
await writeFile(graphFile, graphSource.join('\n'))
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
const state = await inspect(makeLogger(), [stepFile, graphFile], {
|
|
38
|
-
rootDir,
|
|
39
|
-
})
|
|
40
|
-
const graph = state.workflows.graphMeta[graphName]
|
|
41
|
-
assert.ok(graph, `graph meta '${graphName}' should be registered`)
|
|
42
|
-
return graph
|
|
43
|
-
} finally {
|
|
44
|
-
await rm(rootDir, { recursive: true, force: true })
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
describe('addWorkflowGraph — forEach extraction', () => {
|
|
49
|
-
test('forEach node id and $item() lower into the serialized meta', async () => {
|
|
50
|
-
const graph = await inspectGraph(
|
|
51
|
-
[
|
|
52
|
-
"import { pikkuWorkflowGraph } from '@pikku/core/workflow'",
|
|
53
|
-
'export const myGraph = pikkuWorkflowGraph({',
|
|
54
|
-
" name: 'foreach-graph',",
|
|
55
|
-
" nodes: { listRows: 'listRows', postVideo: 'postVideo' },",
|
|
56
|
-
' config: {',
|
|
57
|
-
" listRows: { next: 'postVideo' },",
|
|
58
|
-
' postVideo: {',
|
|
59
|
-
" forEach: 'listRows',",
|
|
60
|
-
' input: (ref, template, $item) => ({',
|
|
61
|
-
" url: $item('URL VIDEO'),",
|
|
62
|
-
' whole: $item(),',
|
|
63
|
-
" listed: ref('listRows'),",
|
|
64
|
-
' }),',
|
|
65
|
-
' },',
|
|
66
|
-
' },',
|
|
67
|
-
'})',
|
|
68
|
-
],
|
|
69
|
-
'foreach-graph'
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
const node = graph.nodes['postVideo'] as any
|
|
73
|
-
assert.deepEqual(node.forEach, { $ref: 'listRows' })
|
|
74
|
-
assert.equal(node.mode, undefined)
|
|
75
|
-
assert.deepEqual(node.input, {
|
|
76
|
-
url: { $ref: '$item', path: 'URL VIDEO' },
|
|
77
|
-
whole: { $ref: '$item', path: undefined },
|
|
78
|
-
listed: { $ref: 'listRows', path: undefined },
|
|
79
|
-
})
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
test('forEach accepts a (ref) callback with a path, plus a sequential mode', async () => {
|
|
83
|
-
const graph = await inspectGraph(
|
|
84
|
-
[
|
|
85
|
-
"import { pikkuWorkflowGraph } from '@pikku/core/workflow'",
|
|
86
|
-
'export const myGraph = pikkuWorkflowGraph({',
|
|
87
|
-
" name: 'foreach-callback-graph',",
|
|
88
|
-
" nodes: { listRows: 'listRows', postVideo: 'postVideo' },",
|
|
89
|
-
' config: {',
|
|
90
|
-
" listRows: { next: 'postVideo' },",
|
|
91
|
-
' postVideo: {',
|
|
92
|
-
" forEach: (ref) => ref('listRows', 'rows'),",
|
|
93
|
-
" mode: 'sequential',",
|
|
94
|
-
" input: (ref, template, $item) => ({ url: $item('URL') }),",
|
|
95
|
-
' },',
|
|
96
|
-
' },',
|
|
97
|
-
'})',
|
|
98
|
-
],
|
|
99
|
-
'foreach-callback-graph'
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
const node = graph.nodes['postVideo'] as any
|
|
103
|
-
assert.deepEqual(node.forEach, { $ref: 'listRows', path: 'rows' })
|
|
104
|
-
assert.equal(node.mode, 'sequential')
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
test('an existing (ref) => ... node keeps its exact input and gains no forEach', async () => {
|
|
108
|
-
const graph = await inspectGraph(
|
|
109
|
-
[
|
|
110
|
-
"import { pikkuWorkflowGraph } from '@pikku/core/workflow'",
|
|
111
|
-
"import { template } from '@pikku/core/workflow'",
|
|
112
|
-
'export const myGraph = pikkuWorkflowGraph({',
|
|
113
|
-
" name: 'plain-graph',",
|
|
114
|
-
" nodes: { listRows: 'listRows', postVideo: 'postVideo' },",
|
|
115
|
-
' config: {',
|
|
116
|
-
" listRows: { next: 'postVideo' },",
|
|
117
|
-
' postVideo: {',
|
|
118
|
-
' input: (ref) => ({',
|
|
119
|
-
" rows: ref('listRows', 'rows'),",
|
|
120
|
-
" label: template('rows: $0', [ref('listRows', 'rows')]),",
|
|
121
|
-
' }),',
|
|
122
|
-
' },',
|
|
123
|
-
' },',
|
|
124
|
-
'})',
|
|
125
|
-
],
|
|
126
|
-
'plain-graph'
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
const node = graph.nodes['postVideo'] as any
|
|
130
|
-
assert.equal(node.forEach, undefined)
|
|
131
|
-
assert.equal(node.mode, undefined)
|
|
132
|
-
assert.deepEqual(node.input, {
|
|
133
|
-
rows: { $ref: 'listRows', path: 'rows' },
|
|
134
|
-
label: {
|
|
135
|
-
$template: {
|
|
136
|
-
parts: ['rows: ', ''],
|
|
137
|
-
expressions: [{ $ref: 'listRows', path: 'rows' }],
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
})
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
test('an existing (ref, template) => ... node still resolves template from the 2nd param', async () => {
|
|
144
|
-
const graph = await inspectGraph(
|
|
145
|
-
[
|
|
146
|
-
"import { pikkuWorkflowGraph } from '@pikku/core/workflow'",
|
|
147
|
-
'export const myGraph = pikkuWorkflowGraph({',
|
|
148
|
-
" name: 'two-param-graph',",
|
|
149
|
-
" nodes: { listRows: 'listRows', postVideo: 'postVideo' },",
|
|
150
|
-
' config: {',
|
|
151
|
-
" listRows: { next: 'postVideo' },",
|
|
152
|
-
' postVideo: {',
|
|
153
|
-
' input: (ref, template) => ({',
|
|
154
|
-
" label: template('rows: $0', [ref('listRows', 'rows')]),",
|
|
155
|
-
' }),',
|
|
156
|
-
' },',
|
|
157
|
-
' },',
|
|
158
|
-
'})',
|
|
159
|
-
],
|
|
160
|
-
'two-param-graph'
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
const node = graph.nodes['postVideo'] as any
|
|
164
|
-
assert.equal(node.forEach, undefined)
|
|
165
|
-
assert.deepEqual(node.input, {
|
|
166
|
-
label: {
|
|
167
|
-
$template: {
|
|
168
|
-
parts: ['rows: ', ''],
|
|
169
|
-
expressions: [{ $ref: 'listRows', path: 'rows' }],
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
})
|
|
173
|
-
})
|
|
174
|
-
})
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { strict as assert } from 'assert'
|
|
2
|
-
import { describe, test } from 'node:test'
|
|
3
|
-
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
|
|
4
|
-
import { tmpdir } from 'node:os'
|
|
5
|
-
import { join } from 'node:path'
|
|
6
|
-
import { inspect } from '../inspector.js'
|
|
7
|
-
import type { InspectorLogger } from '../types.js'
|
|
8
|
-
|
|
9
|
-
function makeLogger(): InspectorLogger {
|
|
10
|
-
return {
|
|
11
|
-
debug: () => {},
|
|
12
|
-
info: () => {},
|
|
13
|
-
warn: () => {},
|
|
14
|
-
error: () => {},
|
|
15
|
-
diagnostic: () => {},
|
|
16
|
-
critical: () => {},
|
|
17
|
-
hasCriticalErrors: () => false,
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
describe('addWorkflowGraph — node input extraction', () => {
|
|
22
|
-
test('`as const` and imported template() values survive into the serialized input', async () => {
|
|
23
|
-
const rootDir = await mkdtemp(join(tmpdir(), 'pikku-graph-input-'))
|
|
24
|
-
const stepFile = join(rootDir, 'my.steps.ts')
|
|
25
|
-
const graphFile = join(rootDir, 'my.graph.ts')
|
|
26
|
-
|
|
27
|
-
await writeFile(
|
|
28
|
-
stepFile,
|
|
29
|
-
[
|
|
30
|
-
"import { pikkuSessionlessFunc } from '@pikku/core'",
|
|
31
|
-
'export const edit = pikkuSessionlessFunc({ func: async () => ({ ok: true }) })',
|
|
32
|
-
].join('\n')
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
await writeFile(
|
|
36
|
-
graphFile,
|
|
37
|
-
[
|
|
38
|
-
"import { pikkuWorkflowGraph } from '@pikku/core/workflow'",
|
|
39
|
-
"import { template } from '@pikku/core/workflow'",
|
|
40
|
-
'export const myGraph = pikkuWorkflowGraph({',
|
|
41
|
-
" name: 'input-graph',",
|
|
42
|
-
" nodes: { edit: 'edit' },",
|
|
43
|
-
' config: {',
|
|
44
|
-
' edit: {',
|
|
45
|
-
' input: (ref) => ({',
|
|
46
|
-
" method: 'GET' as const,",
|
|
47
|
-
' operations: [',
|
|
48
|
-
" { field: 'product', operation: 'set' as const, value: 'widget' },",
|
|
49
|
-
" { field: 'email', operation: 'set' as const, value: ref('trigger', 'body.email') },",
|
|
50
|
-
" { field: 'greeting', operation: 'set' as const, value: template('Hi $0', [ref('trigger', 'body.name')]) },",
|
|
51
|
-
' ],',
|
|
52
|
-
' }),',
|
|
53
|
-
' },',
|
|
54
|
-
' },',
|
|
55
|
-
'})',
|
|
56
|
-
].join('\n')
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
const state = await inspect(makeLogger(), [stepFile, graphFile], {
|
|
61
|
-
rootDir,
|
|
62
|
-
})
|
|
63
|
-
const graph = state.workflows.graphMeta['input-graph']
|
|
64
|
-
assert.ok(graph, 'graph meta should be registered')
|
|
65
|
-
const input = (graph.nodes['edit'] as { input?: any }).input
|
|
66
|
-
assert.ok(input, 'node input should be extracted')
|
|
67
|
-
|
|
68
|
-
// Bug 1 — top-level `X as const` must not be dropped.
|
|
69
|
-
assert.equal(input.method, 'GET', 'top-level `as const` value preserved')
|
|
70
|
-
|
|
71
|
-
// Bug 1 — nested `as const` inside an array element must not be dropped.
|
|
72
|
-
assert.equal(input.operations[0].operation, 'set')
|
|
73
|
-
assert.equal(input.operations[0].value, 'widget')
|
|
74
|
-
assert.equal(input.operations[1].operation, 'set')
|
|
75
|
-
|
|
76
|
-
// ref() still works alongside the fix.
|
|
77
|
-
assert.deepEqual(input.operations[1].value, {
|
|
78
|
-
$ref: 'trigger',
|
|
79
|
-
path: 'body.email',
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
// Bug 2 — imported template() (arrow has only `ref`, no 2nd param) preserved.
|
|
83
|
-
assert.equal(input.operations[2].operation, 'set')
|
|
84
|
-
assert.deepEqual(input.operations[2].value, {
|
|
85
|
-
$template: {
|
|
86
|
-
parts: ['Hi ', ''],
|
|
87
|
-
expressions: [{ $ref: 'trigger', path: 'body.name' }],
|
|
88
|
-
},
|
|
89
|
-
})
|
|
90
|
-
} finally {
|
|
91
|
-
await rm(rootDir, { recursive: true, force: true })
|
|
92
|
-
}
|
|
93
|
-
})
|
|
94
|
-
})
|