@pikku/inspector 0.12.43 → 0.12.45
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 +261 -0
- package/dist/add/add-credential.js +2 -0
- package/dist/add/add-keyed-wiring.js +2 -0
- package/dist/add/add-workflow.js +9 -4
- package/dist/utils/schema-generator.js +19 -4
- package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +373 -91
- package/dist/utils/workflow/dsl/extract-dsl-workflow.js +218 -99
- package/dist/utils/workflow/graph/convert-dsl-to-graph.js +62 -5
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +3 -3
- package/package.json +3 -3
- package/run-tests.sh +1 -1
- package/src/add/add-auth.test.ts +4 -4
- package/src/add/add-credential.ts +2 -0
- package/src/add/add-keyed-wiring.ts +2 -0
- package/src/add/add-workflow-edge-cases.test.ts +201 -0
- package/src/add/add-workflow-fanout.test.ts +60 -0
- package/src/add/add-workflow-on-error.test.ts +118 -0
- package/src/add/add-workflow-return-switch.test.ts +121 -0
- package/src/add/add-workflow-silent-drops.test.ts +163 -0
- package/src/add/add-workflow-sleep-expression.test.ts +97 -0
- package/src/add/add-workflow.ts +9 -4
- package/src/add/config-value-metadata.test.ts +169 -0
- package/src/utils/schema-generator.test.ts +164 -0
- package/src/utils/schema-generator.ts +26 -3
- package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +480 -119
- package/src/utils/workflow/dsl/deserialize-fanout-body.test.ts +71 -0
- package/src/utils/workflow/dsl/deserialize-fanout-fidelity.test.ts +99 -0
- package/src/utils/workflow/dsl/deserialize-graph-next.test.ts +161 -0
- package/src/utils/workflow/dsl/deserialize-roundtrip-bindings.test.ts +121 -0
- package/src/utils/workflow/dsl/deserialize-roundtrip-values.test.ts +116 -0
- package/src/utils/workflow/dsl/deserialize-roundtrip.test.ts +145 -0
- package/src/utils/workflow/dsl/extract-dsl-workflow.ts +245 -105
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +67 -10
- package/src/utils/workflow/graph/fanout-node-identity.test.ts +105 -0
- package/src/utils/workflow/graph/workflow-graph.types.ts +3 -2
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,264 @@
|
|
|
1
|
+
## 0.12.45
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- ae4f59a: Gate admin capabilities on scopes, and scaffold user management
|
|
6
|
+
|
|
7
|
+
Admin capabilities were gated on `user.role === 'admin'` — a single text column
|
|
8
|
+
meaning "can do everything". Impersonating a user, rebinding a shared
|
|
9
|
+
credential and reading the user directory are distinct capabilities that one
|
|
10
|
+
user can hold independently, so they are now scopes on an `admin` tree:
|
|
11
|
+
|
|
12
|
+
| Gate | Scope |
|
|
13
|
+
| -------------------------------------- | ------------------------ |
|
|
14
|
+
| impersonation | `admin:impersonate` |
|
|
15
|
+
| `credentialOAuth`'s `canLinkSingleton` | `admin:credentials:link` |
|
|
16
|
+
| reading the user directory | `admin:users:list` |
|
|
17
|
+
| creating a user out of band | `admin:users:create` |
|
|
18
|
+
| ban / unban | `admin:users:ban` |
|
|
19
|
+
| delete a user | `admin:users:remove` |
|
|
20
|
+
| revoke a user's sessions | `admin:users:sessions` |
|
|
21
|
+
| set a user's password | `admin:users:password` |
|
|
22
|
+
|
|
23
|
+
Holding the bare `admin` scope satisfies all of them via pikku's existing
|
|
24
|
+
parent-grant rule, so it is a one-for-one replacement for the old role.
|
|
25
|
+
|
|
26
|
+
better-auth's `admin()` plugin is still what implements ban, delete,
|
|
27
|
+
session-revocation and set-password, so it stays. Its `user.role` column is no
|
|
28
|
+
longer something pikku grants: it is _projected_ from the scope store when a
|
|
29
|
+
session is built, and only from the scopes whose capability better-auth's own
|
|
30
|
+
endpoints gate on the caller's role. Someone granted `admin:users:list` can read
|
|
31
|
+
the directory — which goes straight to the auth adapter — without gaining the
|
|
32
|
+
power to ban, and revoking a scope demotes the role on the next sign-in. Scopes
|
|
33
|
+
remain the single source of truth.
|
|
34
|
+
|
|
35
|
+
New `scaffold.userAdmin` in `pikku.config.json` generates the whole set —
|
|
36
|
+
`pikkuAdminListUsers`, `pikkuAdminCreateUser`, `pikkuAdminSetUserBanned`,
|
|
37
|
+
`pikkuAdminRemoveUser`, `pikkuAdminRevokeUserSessions` and
|
|
38
|
+
`pikkuAdminSetUserPassword` — into your project. Listing or banning a user is
|
|
39
|
+
ordinary application behaviour and must not require installing the console.
|
|
40
|
+
Codegen fails with an actionable error if better-auth is wired without
|
|
41
|
+
`admin()`. The console's Users page calls these same functions, showing each
|
|
42
|
+
action only where the caller holds its scope.
|
|
43
|
+
|
|
44
|
+
Every scaffold now emits a directory named for its domain — `scaffold/admin/`,
|
|
45
|
+
`scaffold/rpc/`, `scaffold/agent/`, `scaffold/auth/`, `scaffold/console/`,
|
|
46
|
+
`scaffold/graph/`, `scaffold/realtime/`, `scaffold/scenarios/`,
|
|
47
|
+
`scaffold/webhook/`, `scaffold/workflow/` — holding its wiring file beside a
|
|
48
|
+
`*.schemas.gen.ts` sibling, and every generated payload is a zod schema instead
|
|
49
|
+
of a TypeScript generic. The schemas have to stand alone: the inspector reads a
|
|
50
|
+
zod schema by importing the module that declares it, which it cannot do for a
|
|
51
|
+
wiring file whose relative pikku-types import per-unit deploy codegen rewrites.
|
|
52
|
+
|
|
53
|
+
Resolving a schema by reference rather than by name also fixes the agent HTTP
|
|
54
|
+
surface. `agentCaller` and `agentStreamCaller` take the same payload but had to
|
|
55
|
+
repeat the type literal verbatim in each generic position, because the extractor
|
|
56
|
+
synthesised the schema name from the _function_ name and so recorded an
|
|
57
|
+
`inputSchemaName` with no schema behind it whenever the two shared a named
|
|
58
|
+
alias — every agent call through that alias failed with `MissingSchemaError`.
|
|
59
|
+
One `AgentCall` schema now backs both.
|
|
60
|
+
|
|
61
|
+
Where a payload's shape belongs to `@pikku/core` (`WorkflowRunStatus`,
|
|
62
|
+
`FunctionCoverageReport`, `StubCall[]`) the generated function carries no
|
|
63
|
+
`output` schema and the inspector infers it from the handler's return type;
|
|
64
|
+
re-declaring a core type in zod would be a second definition free to drift.
|
|
65
|
+
|
|
66
|
+
Upgrading rewrites the layout in place: codegen prunes the pre-directory copy of
|
|
67
|
+
each scaffold file before it inspects the source tree, since the old flat file
|
|
68
|
+
still wires the same routes and leaving it behind would wire everything twice.
|
|
69
|
+
|
|
70
|
+
`@pikku/core` gains `hasScopes(required, held)`, the non-throwing counterpart to
|
|
71
|
+
`verifyScopes`, and declares `auth` on `CoreSingletonServices` — the auth
|
|
72
|
+
instance the generated `pikkuServices` wrapper already injected but never typed.
|
|
73
|
+
A scope root declared twice (an addon and its host both contributing the same
|
|
74
|
+
`admin` tree) now flattens to one entry per id instead of emitting it twice.
|
|
75
|
+
|
|
76
|
+
BREAKING: there is no role fallback for the scope-gated capabilities. An app
|
|
77
|
+
that relied on the old default must register a `ScopeService` and grant `admin`
|
|
78
|
+
(or a narrower `admin:*` scope). Every gate fails closed and warns when no
|
|
79
|
+
`ScopeService` is registered. `delegatedAuth`'s `defaultRole`/`mapRole` now
|
|
80
|
+
grant a pikku role through the `ScopeService` instead of writing better-auth's
|
|
81
|
+
`role` column, and the `credentialOAuth` platform user no longer sets `banned`.
|
|
82
|
+
|
|
83
|
+
BREAKING: the console reads its user directory over the scaffolded
|
|
84
|
+
`pikkuAdminListUsers` RPC (gated on `admin:users:list`, backed by better-auth's
|
|
85
|
+
`$context.adapter`) instead of `client.admin.listUsers`, and
|
|
86
|
+
`UsersTableUser`/`UsersTableLabels` no longer carry `role` — there is no role
|
|
87
|
+
column to render. `@pikku/addon-console` no longer ships a `console:listUsers`
|
|
88
|
+
function: user management is not the console's job, so a host that wants the
|
|
89
|
+
Users page must enable `scaffold.userAdmin`.
|
|
90
|
+
|
|
91
|
+
- Updated dependencies [ae4f59a]
|
|
92
|
+
- @pikku/core@0.12.67
|
|
93
|
+
|
|
94
|
+
## 0.12.44
|
|
95
|
+
|
|
96
|
+
### Patch Changes
|
|
97
|
+
|
|
98
|
+
- 1a86d3f: Stop silently dropping DSL workflow constructs during extraction.
|
|
99
|
+
|
|
100
|
+
Four constructs produced a wrong workflow graph with no diagnostic:
|
|
101
|
+
- A property access on the fanout item (`users.map((u) => workflow.do(..., { userId: u.id }))`)
|
|
102
|
+
was dropped from the step's inputs — `extractInputSource` resolved property
|
|
103
|
+
access against the input param and output variables but not loop variables.
|
|
104
|
+
- `const [org, user] = await Promise.all([...])` dropped **both** steps, because
|
|
105
|
+
extraction bailed on any non-identifier binding name before reaching the
|
|
106
|
+
`Promise.all` branch. Array destructuring now binds each name to its matching
|
|
107
|
+
child step's output.
|
|
108
|
+
- A brace-less `for (const x of xs) await workflow.do(...)` dropped the entire
|
|
109
|
+
loop, since only block bodies were walked.
|
|
110
|
+
- Object destructuring of a step result and multi-declarator statements now
|
|
111
|
+
report a diagnostic instead of vanishing.
|
|
112
|
+
|
|
113
|
+
- 1a86d3f: Fix a fanout collapsing into a single step, and preserve graph node config.
|
|
114
|
+
- A fanout took its `stepName` from the first step of its body. Node ids _are_
|
|
115
|
+
step names, so the loop and that step got the same id and the step overwrote
|
|
116
|
+
the loop: `await Promise.all(users.map(...))` rendered as one plain call, and
|
|
117
|
+
everything after the loop became unreachable. A fanout is not itself a cached
|
|
118
|
+
step, so it no longer borrows a name.
|
|
119
|
+
- A `workflow.sleep` or `workflow.suspend` inside a fanout body was dropped at
|
|
120
|
+
extraction — `FanoutStepMeta.body` was typed RPC-only. It now admits sleep and
|
|
121
|
+
suspend, and the regenerated body emits them.
|
|
122
|
+
- Regenerating a `pikkuWorkflowGraph` dropped `onError`, `retries` and
|
|
123
|
+
`retryDelay` from every node, and graph-level `notes`. All four are honoured
|
|
124
|
+
at runtime, so the round trip silently changed behaviour.
|
|
125
|
+
|
|
126
|
+
- 1a86d3f: Support multi-step fanout bodies in DSL workflows.
|
|
127
|
+
|
|
128
|
+
A `Promise.all(array.map(...))` (or `for...of`) body containing more than one
|
|
129
|
+
`workflow.do` call previously extracted only a single step: `const`-captured
|
|
130
|
+
steps were skipped entirely by the parallel extractor, so a body like
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
await Promise.all(
|
|
134
|
+
users.map(async (u) => {
|
|
135
|
+
const digestData = await workflow.do('Get pipeline', 'getDigestData', {
|
|
136
|
+
userId: u.id,
|
|
137
|
+
})
|
|
138
|
+
await workflow.do('Send digest', 'sendDigestEmail', { ...digestData })
|
|
139
|
+
})
|
|
140
|
+
)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
produced a graph with `getDigestData` missing and `sendDigestEmail` referencing
|
|
144
|
+
an unregistered variable. `FanoutStepMeta.child` is replaced by
|
|
145
|
+
`FanoutStepMeta.body: RpcStepMeta[]`, holding the per-iteration steps inline in
|
|
146
|
+
the same workflow — no sub-workflow boundary. Per-iteration `const` bindings are
|
|
147
|
+
now registered so later steps in the same iteration can reference them, and the
|
|
148
|
+
sequential path no longer hard-errors on bodies with more than one step.
|
|
149
|
+
|
|
150
|
+
- 1a86d3f: Add `onError` compensation to DSL workflows.
|
|
151
|
+
|
|
152
|
+
A DSL workflow had no way to express error handling at all — `try/catch` is not
|
|
153
|
+
an allowed statement, and step options carried only `retries`/`retryDelay`. A
|
|
154
|
+
step can now name a compensation RPC:
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
await workflow.do(
|
|
158
|
+
'Charge',
|
|
159
|
+
'chargeCard',
|
|
160
|
+
{ id },
|
|
161
|
+
{
|
|
162
|
+
retries: 3,
|
|
163
|
+
onError: 'refundOrder',
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Semantics mirror a graph node's `onError` exactly: once the step's retries are
|
|
169
|
+
exhausted the handler is invoked with `{ error: { message } }` and the original
|
|
170
|
+
error is still thrown. This is compensation, not recovery — the workflow fails
|
|
171
|
+
either way. The handler runs as its own durable step, so a replay cannot
|
|
172
|
+
compensate twice, and it does not inherit `onError` itself.
|
|
173
|
+
|
|
174
|
+
The handler is materialised as a real graph node, so it is wired like any other
|
|
175
|
+
RPC and the console draws a dashed red "on error" edge to it rather than the
|
|
176
|
+
route being invisible.
|
|
177
|
+
|
|
178
|
+
- 1a86d3f: Keep a numeric `retryDelay` numeric through the graph round-trip. The serialized
|
|
179
|
+
graph typed it as `string` and the DSL→graph conversion called `.toString()`, so
|
|
180
|
+
`retryDelay: 500` regenerated as `retryDelay: '500'` — a different value to the
|
|
181
|
+
runtime, which parses strings as durations.
|
|
182
|
+
- 1a86d3f: Stop silently dropping switch cases and spread returns from workflow graphs.
|
|
183
|
+
- A fall-through case (`case 'a': case 'b': ...`) recorded only the last value.
|
|
184
|
+
A run entering on `'a'` therefore appeared to match no case at all. Empty
|
|
185
|
+
clauses now carry through to the entry they fall into — the next non-empty
|
|
186
|
+
case, otherwise `default`, otherwise the switch exit.
|
|
187
|
+
- `return { ...r, extra: 1 }` produced a return node listing only `extra`, so
|
|
188
|
+
the graph claimed an output shape the workflow does not have, with no
|
|
189
|
+
diagnostic. `return r` produced no return node at all. `ReturnStepMeta` now
|
|
190
|
+
records a `spread` list, and the regenerated code emits it.
|
|
191
|
+
|
|
192
|
+
- 1a86d3f: Stop deleting code when regenerating a DSL workflow from its graph.
|
|
193
|
+
|
|
194
|
+
Regenerating a workflow (as the console's graph editor does) silently dropped
|
|
195
|
+
steps:
|
|
196
|
+
- Only the **first** step of an `if` arm survived. The walk stopped via a
|
|
197
|
+
heuristic that tested whether the next node id contained `_then_`/`_else_`,
|
|
198
|
+
but node ids are step names, so it never matched. Branch and switch bodies now
|
|
199
|
+
walk the `next` chain up to an explicit exit boundary — the enclosing flow
|
|
200
|
+
node's own `next`.
|
|
201
|
+
- Switch cases emitted only their entry node, never walking `next` at all.
|
|
202
|
+
- Every step was renamed to `Call <rpcName>`, because the graph conversion never
|
|
203
|
+
wrote `stepName` onto the node. Step names are the durable replay cache key,
|
|
204
|
+
so a round-trip silently invalidated in-flight runs.
|
|
205
|
+
- `workflow.suspend()` had no graph node, yet the preceding step's `next` still
|
|
206
|
+
pointed at its id — traversal dead-ended there and every following step was
|
|
207
|
+
deleted. `suspend` is now a real flow node, and both `suspend` and `approval`
|
|
208
|
+
have deserializer cases.
|
|
209
|
+
- Numeric and boolean `switch` case values were emitted quoted (`case '1':`),
|
|
210
|
+
so the case could never match. Step names and reasons containing a quote are
|
|
211
|
+
now escaped.
|
|
212
|
+
|
|
213
|
+
Also fixed in the same pass:
|
|
214
|
+
- `const [org, user] = await Promise.all([...])` regenerated as a bare
|
|
215
|
+
`await Promise.all([...])`, leaving both names unbound.
|
|
216
|
+
- A step result assigned inside a branch was re-declared with `const` inside
|
|
217
|
+
that branch, so any later reference was out of scope. Hoisting analysis was
|
|
218
|
+
keyed off the same dead node-id heuristic and never fired.
|
|
219
|
+
- A top-level step whose _name_ contained `_case`, `_item_`, `_then_`,
|
|
220
|
+
`_else_`, `_child_` or `_default_` was silently deleted, because node ids are
|
|
221
|
+
step names and were matched against those structural substrings. Ownership is
|
|
222
|
+
now read from the parent constructs themselves.
|
|
223
|
+
|
|
224
|
+
- 1a86d3f: Stop corrupting values when regenerating a workflow from its graph.
|
|
225
|
+
- A numeric `workflow.sleep('Wait', 5000)` came back as `'5000'`, and a numeric
|
|
226
|
+
`retryDelay` likewise. Durations are `string | number`; only strings are
|
|
227
|
+
quoted now.
|
|
228
|
+
- An assignment to a context variable was stored as an opaque `value`, so
|
|
229
|
+
`count = count + 1` regenerated as `count = 'count + 1'` — an expression
|
|
230
|
+
turned into a string literal. `SetStepMeta` now carries a separate
|
|
231
|
+
`expression` field (mirroring `SwitchCaseMeta`), so a string literal and a
|
|
232
|
+
code expression are no longer indistinguishable in the meta.
|
|
233
|
+
- A `next` that was not a single node id was coerced with a string cast: an
|
|
234
|
+
array became the bogus id `'a,b'` and a branch-key record became
|
|
235
|
+
`'[object Object]'`, severing every downstream node. Arrays, key-based
|
|
236
|
+
routing tables and condition lists now each render in their own shape.
|
|
237
|
+
- A `filter`/`some`/`every` node with no `outputVar` emitted
|
|
238
|
+
`const undefined = ...`, which does not parse.
|
|
239
|
+
|
|
240
|
+
- 1a86d3f: Keep a `workflow.sleep` whose duration is only known at runtime (a loop
|
|
241
|
+
variable, a field off the input). The closure evaluates it, so it is legal DSL;
|
|
242
|
+
its source text is recorded as an `expression` and emitted raw when regenerating
|
|
243
|
+
code, as a set step already does.
|
|
244
|
+
- 314ace3: Cut the schema generator's ts.Program cost — the dominant contributor to `pikku all` memory and time.
|
|
245
|
+
|
|
246
|
+
Two independent fixes: the program is now scoped to the virtual file's import
|
|
247
|
+
closure (870 source files instead of the whole tsconfig's 2572), and it is
|
|
248
|
+
released once schemas are generated instead of being pinned at module scope for
|
|
249
|
+
the life of the process. On a 279-function tree this cuts cold codegen ~20% and
|
|
250
|
+
in-pass live heap ~21%, with byte-identical schema output.
|
|
251
|
+
|
|
252
|
+
- 3d76f51: Add an optional `docsUrl` to `wireSecret`, `wireVariable`, and `wireCredential`, so a console or deploy UI reporting a missing value can link the user to where they obtain it instead of showing a bare identifier.
|
|
253
|
+
- Updated dependencies [1a86d3f]
|
|
254
|
+
- Updated dependencies [1a86d3f]
|
|
255
|
+
- Updated dependencies [1a86d3f]
|
|
256
|
+
- Updated dependencies [1a86d3f]
|
|
257
|
+
- Updated dependencies [1a86d3f]
|
|
258
|
+
- Updated dependencies [1a86d3f]
|
|
259
|
+
- Updated dependencies [3d76f51]
|
|
260
|
+
- @pikku/core@0.12.65
|
|
261
|
+
|
|
1
262
|
## 0.12.43
|
|
2
263
|
|
|
3
264
|
### Patch Changes
|
|
@@ -22,6 +22,7 @@ export const addCredential = (logger, node, checker, state, _options) => {
|
|
|
22
22
|
const displayNameValue = getPropertyValue(obj, 'displayName');
|
|
23
23
|
const descriptionValue = getPropertyValue(obj, 'description');
|
|
24
24
|
const typeValue = getPropertyValue(obj, 'type');
|
|
25
|
+
const docsUrlValue = getPropertyValue(obj, 'docsUrl');
|
|
25
26
|
if (!nameValue) {
|
|
26
27
|
logger.critical(ErrorCode.MISSING_NAME, "Credential is missing the required 'name' property.");
|
|
27
28
|
return;
|
|
@@ -113,6 +114,7 @@ export const addCredential = (logger, node, checker, state, _options) => {
|
|
|
113
114
|
displayName: displayNameValue,
|
|
114
115
|
description: descriptionValue || undefined,
|
|
115
116
|
type: typeValue,
|
|
117
|
+
docsUrl: docsUrlValue || undefined,
|
|
116
118
|
schema: schemaLookupName,
|
|
117
119
|
oauth2,
|
|
118
120
|
sourceFile,
|
|
@@ -26,6 +26,7 @@ export const createAddKeyedWiring = (config) => {
|
|
|
26
26
|
const displayNameValue = getPropertyValue(obj, 'displayName');
|
|
27
27
|
const descriptionValue = getPropertyValue(obj, 'description');
|
|
28
28
|
const rotationPeriodValue = getPropertyValue(obj, 'rotationPeriod');
|
|
29
|
+
const docsUrlValue = getPropertyValue(obj, 'docsUrl');
|
|
29
30
|
const idValue = getPropertyValue(obj, config.idField);
|
|
30
31
|
let schemaVariableName = null;
|
|
31
32
|
let schemaSourceFile = null;
|
|
@@ -102,6 +103,7 @@ export const createAddKeyedWiring = (config) => {
|
|
|
102
103
|
displayName: displayNameValue,
|
|
103
104
|
description: descriptionValue || undefined,
|
|
104
105
|
rotationPeriod: rotationPeriodValue || undefined,
|
|
106
|
+
docsUrl: docsUrlValue || undefined,
|
|
105
107
|
[config.idField]: idValue,
|
|
106
108
|
schema: schemaLookupName,
|
|
107
109
|
sourceFile,
|
package/dist/add/add-workflow.js
CHANGED
|
@@ -62,8 +62,8 @@ function hasInlineSteps(steps) {
|
|
|
62
62
|
if (step.defaultSteps && hasInlineSteps(step.defaultSteps))
|
|
63
63
|
return true;
|
|
64
64
|
}
|
|
65
|
-
else if (step.type === 'fanout' && step.
|
|
66
|
-
if (hasInlineSteps(
|
|
65
|
+
else if (step.type === 'fanout' && step.body) {
|
|
66
|
+
if (hasInlineSteps(step.body))
|
|
67
67
|
return true;
|
|
68
68
|
}
|
|
69
69
|
else if (step.type === 'parallel' && step.children) {
|
|
@@ -80,6 +80,11 @@ export function collectInvokedRPCs(steps, rpcs) {
|
|
|
80
80
|
for (const step of steps) {
|
|
81
81
|
if (step.type === 'rpc' && step.rpcName) {
|
|
82
82
|
rpcs.add(step.rpcName);
|
|
83
|
+
// The compensation handler is invoked at runtime too, so it has to be
|
|
84
|
+
// wired like any other step.
|
|
85
|
+
if (step.options?.onError) {
|
|
86
|
+
rpcs.add(step.options.onError);
|
|
87
|
+
}
|
|
83
88
|
}
|
|
84
89
|
else if (step.type === 'branch') {
|
|
85
90
|
for (const branch of step.branches) {
|
|
@@ -96,8 +101,8 @@ export function collectInvokedRPCs(steps, rpcs) {
|
|
|
96
101
|
if (step.defaultSteps)
|
|
97
102
|
collectInvokedRPCs(step.defaultSteps, rpcs);
|
|
98
103
|
}
|
|
99
|
-
else if (step.type === 'fanout' && step.
|
|
100
|
-
collectInvokedRPCs(
|
|
104
|
+
else if (step.type === 'fanout' && step.body) {
|
|
105
|
+
collectInvokedRPCs(step.body, rpcs);
|
|
101
106
|
}
|
|
102
107
|
else if (step.type === 'parallel' && step.children) {
|
|
103
108
|
collectInvokedRPCs(step.children, rpcs);
|
|
@@ -67,7 +67,9 @@ const inspectorVersion = (() => {
|
|
|
67
67
|
try {
|
|
68
68
|
const pkgUrl = new URL('../../package.json', import.meta.url);
|
|
69
69
|
const pkg = JSON.parse(readFileSync(pkgUrl, 'utf-8'));
|
|
70
|
-
return typeof pkg.version === 'string'
|
|
70
|
+
return typeof pkg.version === 'string'
|
|
71
|
+
? pkg.version
|
|
72
|
+
: `v${SCHEMA_CACHE_VERSION}`;
|
|
71
73
|
}
|
|
72
74
|
catch {
|
|
73
75
|
return `v${SCHEMA_CACHE_VERSION}`;
|
|
@@ -112,7 +114,7 @@ function writeDiskTSSchemas(logger, cacheDir, key, schemas) {
|
|
|
112
114
|
logger.debug(`Failed to persist TS-schema cache: ${e.message}`);
|
|
113
115
|
}
|
|
114
116
|
}
|
|
115
|
-
function createProgramWithVirtualFile(tsconfig, virtualFilePath, virtualFileContent) {
|
|
117
|
+
function createProgramWithVirtualFile(logger, tsconfig, virtualFilePath, virtualFileContent) {
|
|
116
118
|
const configPath = resolve(tsconfig);
|
|
117
119
|
// Cache the parsed tsconfig — it doesn't change between runs
|
|
118
120
|
if (!cachedParsedConfig || cachedTsconfigPath !== configPath) {
|
|
@@ -123,7 +125,11 @@ function createProgramWithVirtualFile(tsconfig, virtualFilePath, virtualFileCont
|
|
|
123
125
|
cachedSchemaProgram = undefined;
|
|
124
126
|
}
|
|
125
127
|
const resolvedVirtualPath = resolve(virtualFilePath);
|
|
126
|
-
|
|
128
|
+
// The virtual file imports every type it references, so it is a complete root
|
|
129
|
+
// on its own and TypeScript pulls in exactly the transitive closure it needs.
|
|
130
|
+
// Rooting at cachedParsedConfig.fileNames instead loaded the whole project
|
|
131
|
+
// (2572 files vs 870 on a real tree) without widening what could be resolved.
|
|
132
|
+
const fileNames = [resolvedVirtualPath];
|
|
127
133
|
const defaultHost = ts.createCompilerHost(cachedParsedConfig.options);
|
|
128
134
|
const customHost = {
|
|
129
135
|
...defaultHost,
|
|
@@ -144,9 +150,11 @@ function createProgramWithVirtualFile(tsconfig, virtualFilePath, virtualFileCont
|
|
|
144
150
|
return defaultHost.readFile(fileName);
|
|
145
151
|
},
|
|
146
152
|
};
|
|
153
|
+
const startProgram = performance.now();
|
|
147
154
|
const program = ts.createProgram(fileNames, cachedParsedConfig.options, customHost, cachedSchemaProgram // reuse previous program for incremental compilation
|
|
148
155
|
);
|
|
149
156
|
cachedSchemaProgram = program;
|
|
157
|
+
logger.debug(`Created schema program in ${(performance.now() - startProgram).toFixed(0)}ms (${program.getSourceFiles().length} files)`);
|
|
150
158
|
return program;
|
|
151
159
|
}
|
|
152
160
|
function generateTSSchemas(logger, tsconfig, customTypesContent, typesMap, functionMeta, httpWiringsMeta, additionalTypes, additionalProperties = false, generatedZodSchemas) {
|
|
@@ -192,7 +200,7 @@ function generateTSSchemas(logger, tsconfig, customTypesContent, typesMap, funct
|
|
|
192
200
|
}
|
|
193
201
|
logger.debug(`generateTSSchemas needed for ${uncoveredSchemas.length} types: ${uncoveredSchemas.slice(0, 3).join(', ')}${uncoveredSchemas.length > 3 ? '...' : ''}`);
|
|
194
202
|
const virtualFilePath = join(dirname(resolve(tsconfig)), '__pikku_virtual_types__.ts');
|
|
195
|
-
const program = createProgramWithVirtualFile(tsconfig, virtualFilePath, customTypesContent);
|
|
203
|
+
const program = createProgramWithVirtualFile(logger, tsconfig, virtualFilePath, customTypesContent);
|
|
196
204
|
const generator = createGenerator({
|
|
197
205
|
tsProgram: program,
|
|
198
206
|
skipTypeCheck: true,
|
|
@@ -408,6 +416,13 @@ export async function generateAllSchemas(logger, config, state) {
|
|
|
408
416
|
}
|
|
409
417
|
}
|
|
410
418
|
const tsSchemas = generateTSSchemas(logger, config.tsconfig, customTypesContent, state.functions.typesMap, state.functions.meta, state.http.meta, config.schemasFromTypes, config.schema?.additionalProperties, zodSchemas);
|
|
419
|
+
// Release the program once the schemas are out of it. It spans the whole
|
|
420
|
+
// tsconfig (~2.5k files, ~600MB on a large tree) and was previously pinned at
|
|
421
|
+
// module scope for the life of the process, so it stacked on top of the main
|
|
422
|
+
// inspector program and pushed `pikku all` past a 2GB heap (#982). The result
|
|
423
|
+
// cache below is what the same-process fast path actually needs; the program
|
|
424
|
+
// only bought incremental reuse on a rebuild, at a cost that dwarfs it.
|
|
425
|
+
cachedSchemaProgram = undefined;
|
|
411
426
|
cachedCustomTypesContent = customTypesContent;
|
|
412
427
|
cachedTSSchemas = tsSchemas;
|
|
413
428
|
if (config.cacheDir && cacheKey) {
|