@voltro/workflow 0.31.0 → 0.33.0
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 +368 -0
- package/dist/clusterTestSuite.js +1 -1
- package/dist/index.d.ts +30 -0
- package/dist/index.js +2 -2
- package/dist/{src-CJr1crqr.js → src-CP61KZ9V.js} +123 -113
- package/package.json +5 -4
package/CHANGELOG.md
CHANGED
|
@@ -39,6 +39,374 @@ _Changes staged for the next release accumulate here (rolled up from
|
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
42
|
+
## [0.33.0] — 2026-08-11
|
|
43
|
+
|
|
44
|
+
### ⚠ BREAKING
|
|
45
|
+
|
|
46
|
+
- **@voltro/protocol, @voltro/runtime, @voltro/voltro** — `CoordinatedScheduleHandle` gained `wake()`, `currentIntervalMs()` and `isArmed()`.
|
|
47
|
+
|
|
48
|
+
The type change that carries the poller work in this release (see *A coordinated tick is a FLOOR*). Two of the three shapes it touches are NOT breaking and are listed here so the classification is checkable rather than asserted:
|
|
49
|
+
|
|
50
|
+
- the effect parameter was **widened** — it may now return a tick outcome, and an existing `() => Promise<void>` still satisfies it; - `Coordinator.tryClaim` gained an **optional** third parameter (the caller's bucket width), so an existing implementation still conforms.
|
|
51
|
+
|
|
52
|
+
(The plugin-facing `scheduleCoordinated` also gained an OPTIONAL fourth argument, `{ disarmWhenIdle }` — additive, and how a plugin opts its own task out of polling entirely.)
|
|
53
|
+
|
|
54
|
+
What breaks is code that **constructs** a handle rather than receiving one: a hand-written test double of `PluginBindContext`, which is the ordinary way to unit-test a plugin's `bindDataStore`. Four of the framework's own suites carried one, and three of those compiled only because the stub was cast — which is also why the two new members must be REQUIRED rather than optional. An optional `wake()` would let a caller subscribe a change channel to a handle that silently has none, and a poller that never wakes is the failure this release exists to remove, arriving quietly.
|
|
55
|
+
|
|
56
|
+
The codemod is `manual`: the object literal needing the two fields carries no importable symbol and usually sits behind an `as never`, so no transform can tell it apart from an unrelated literal in the same test file. It is gated on the app mentioning `scheduleCoordinated` at all.
|
|
57
|
+
- **@voltro/runtime** — `@effect/opentelemetry` is now an **optional peer** of `@voltro/runtime` instead of a dependency. **If you export traces or metrics, install it:**
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
pnpm add @effect/opentelemetry
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
If you do not (no `FRAMEWORK_TRACING`, no `FRAMEWORK_METRICS`, no `OTEL_EXPORTER_OTLP_*`), nothing changes and your install gets 24 lines quieter.
|
|
64
|
+
|
|
65
|
+
It is reached from one dynamic `import()`, only when tracing is on, and it declares seven non-optional OpenTelemetry peers of which we supply five. So every `pnpm install` of every consumer ended with an unmet-peer block describing a condition that broke nothing. Declaring the two missing peers as real dependencies was the wrong direction — one of them is `@opentelemetry/sdk-trace-web`, the BROWSER tracer — and 0.64.0 is the current stable, so there is no upstream release marking them optional to wait for.
|
|
66
|
+
|
|
67
|
+
The reporting consumer's argument is what decided it: *"a check that is loud on every upgrade teaches people to skip the output, and the next warning in that block is the one that matters. We read past this one for four releases."*
|
|
68
|
+
|
|
69
|
+
A boot with tracing enabled and the package absent fails with a message naming this install line — a startup failure, not a silent loss of telemetry.
|
|
70
|
+
|
|
71
|
+
**`voltro update` carries you across this** — codemod `0.33.0/01_opentelemetry-optional-peer`.
|
|
72
|
+
|
|
73
|
+
### Added
|
|
74
|
+
|
|
75
|
+
- **@voltro/cli** — `voltro agents-md` now reports which `@voltro/cli` it seeded from, and warns when that is not the one the project installs.
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
seeded from @voltro/cli 0.31.0 (project has 0.32.0; whats-new describes 0.31.0;
|
|
79
|
+
modules COPIED into ./agent-docs)
|
|
80
|
+
WARN the `voltro` binary that ran is 0.31.0, but this project installs 0.32.0 —
|
|
81
|
+
everything just written describes the OLDER version.
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
A consumer reported a freshly-seeded `agent-docs/whats-new.md` one release behind their installed version, twice. The published packages are correct (verified with `npm pack`), so the content came from a different `@voltro/cli` than the one they installed — the command reads its templates relative to the RUNNING binary, and a globally-installed `voltro`, a stale `dist`, or a parent workspace's copy all produce exactly that, with output that looked identical either way.
|
|
85
|
+
|
|
86
|
+
It does not refuse and does not pick a cli for you: running the workspace binary against a checkout is legitimate and common.
|
|
87
|
+
- **@voltro/cli** — `voltro db encrypt-column <table>.<column>` — the data migration `.encrypted()` always needed.
|
|
88
|
+
|
|
89
|
+
`.encrypted()` encrypts on WRITE, so adding it to a populated column converts nothing that is already there, and there was no supported way to convert it. A consumer carried three plaintext credential columns for months with no next step: *"`.encrypted()` braucht einen Cipher UND eine Datenmigration der bestehenden Zeilen; gemeldet, nicht behoben."*
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
voltro db encrypt-column integrations.webhookSecret --dry-run
|
|
93
|
+
voltro db encrypt-column integrations.webhookSecret employees.meilisearchKey --yes
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Five guards, each for a way a naive version succeeds and destroys data:
|
|
97
|
+
|
|
98
|
+
- **Idempotent** — an already-ciphertext value is skipped, so an interrupted run is resumed by running it again. Double encryption is unrecoverable without the key history. - **Round-trip verified before the write** — every value is decrypted back in-process first, so a broken cipher fails with nothing written. - **Key checked against what the column already holds** — a *different* key round-trips fine, so the check above cannot see it. Resuming with the wrong key would leave a column readable with neither key alone. - **Width pre-flight** — ciphertext is `49 + 4×ceil(bytes/3)` characters, so a 64-char key needs 137 and a `varchar(100)` fails partway. Refuses with both numbers and the `.maxLength()` to set. Measured in BYTES: `'ä'.repeat(10)` is 10 characters and 20 bytes. - **`--yes` required**, `--dry-run` shows the counts, and no value — plaintext or ciphertext — is ever printed.
|
|
99
|
+
|
|
100
|
+
Verified against a real postgres: the conversion, the re-run no-op, both refusals writing nothing, and a decrypt back to the original including multi-byte content.
|
|
101
|
+
|
|
102
|
+
### Changed
|
|
103
|
+
|
|
104
|
+
- **@voltro/runtime** — `_voltro_schedule_claims` swaps its `(scheduleName, bucket)` index for `(scheduleName, claimedAt)`.
|
|
105
|
+
|
|
106
|
+
A consumer read `pg_stat_user_indexes` on their live table and measured, over its whole lifetime:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
_voltro_schedule_claims_pkey 348 978 scans
|
|
110
|
+
_voltro_schedule_claims_claimedAt_idx 3 949
|
|
111
|
+
_voltro_schedule_claims_scheduleName_bucket_idx 4
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Four. It was declared "for the case where you would rather ask by field", and nothing ever asks by field — every read of this table goes through the primary key, which *is* `<scheduleName>@<bucket>`. An index nothing uses is not free: it is written on every INSERT, into a table written once per tick per schedule.
|
|
115
|
+
|
|
116
|
+
`(scheduleName, claimedAt)` is the shape of a query that now exists — the per-schedule prune a winning claim runs (`WHERE scheduleName = ? AND claimedAt < ?`). The `claimedAt` index stays: the retention sweep's cutoff spans every schedule and needs it leading, which the composite cannot provide.
|
|
117
|
+
|
|
118
|
+
No codemod: a `_voltro_*` change rides the declarative differ on `voltro db apply` and on a `voltro dev` boot, on every dialect.
|
|
119
|
+
|
|
120
|
+
The same measurement corrected something the reporter had said in an earlier round and we had repeated back to them — that both indexes went unused. The primary key is used constantly. That makes the finding sharper rather than weaker: the ability to answer this question in one lookup is not merely available, it is demonstrably in use on the same table, and the one read path that needed it was the one not taking it.
|
|
121
|
+
|
|
122
|
+
### Fixed
|
|
123
|
+
|
|
124
|
+
- **@voltro/database** — A column ADDED with a `reference()` now gets its foreign key in the same plan.
|
|
125
|
+
|
|
126
|
+
`ADD COLUMN` emits no `REFERENCES` clause on any dialect, and the planner's FK branch lived only in the path for a column present on both sides — so adding a `reference()` column to an existing table planned an `add-column` and nothing else. The constraint appeared on the SECOND `voltro db apply`, when the column was live and the diff finally saw a live column with no FK.
|
|
127
|
+
|
|
128
|
+
Two applies converged, so the state was reachable, which is why this survived as a low-priority note for a long time. It is worse under `voltro dev`: the boot diff refuses to record a fingerprint while the re-plan is non-empty, so an app whose only pending change was such a column re-planned on every boot and never converged.
|
|
129
|
+
|
|
130
|
+
Both callers share one `addForeignKeyOps` builder now, and the existing dependency tiering already orders `add-column` before `add-foreign-key`.
|
|
131
|
+
- **@voltro/runtime, @voltro/protocol, @voltro/workflow, @voltro/cli** — A coordinated tick is a FLOOR now, and a claim no longer outlives its bucket.
|
|
132
|
+
|
|
133
|
+
A consumer's `_voltro_schedule_claims` reached **86 214 rows / 33 MB** on two days of uptime and took their deployment down: ten of a fifteen-slot pooler pinned on the claim read, an SSR render measured at **300 490 ms** behind them, every page in three frontends unusable, and a `rollout restart` that could not complete because the surge pod could not get a connection. Two hours of their own measurement produced the diagnosis, and both halves of it were right.
|
|
134
|
+
|
|
135
|
+
**Where the rows came from.** They declare one workflow, have never started it, use no flow control and no offloaded inference. Over one hour, with two replicas:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
voltro.ai.inference 1 259 rows/h (250 ms ticks) framework
|
|
139
|
+
voltro.workflow.admission 1 247 rows/h (1 s ticks) framework
|
|
140
|
+
their own eight schedules 18 rows/h
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
99.3 % of the ledger was the framework polling two structurally empty queues. A fixed interval has no way to learn that, so:
|
|
144
|
+
|
|
145
|
+
- **`scheduleCoordinated`'s effect may now REPORT its tick.** Return `{ idle: true }` and the runner backs off toward a ceiling; return `{ idle: true, nextDueInMs }` and it arms for that instant instead — which is what keeps a `debounce` window from being slept through. Returning nothing keeps the fixed interval, so every existing plugin task ticks exactly as before. - **Where an arrival is guaranteed to wake it, an idle task STOPS ENTIRELY** (`{ disarmWhenIdle: true }`). Both framework tasks do, on any deployment where a peer replica's write is visible locally — Postgres LISTEN/NOTIFY, or a broadcast broker. Measured against a real Postgres on a deployment that uses neither queue: **2 claim rows in five minutes**, one per task, both at boot. Where that guarantee does not hold, the ceiling (`VOLTRO_POLL_CEILING_MS`, default 30 s) is the correct behaviour and is what they get. - **`handle.wake()` runs a tick now.** Both framework queues are tables with the framework's own CDC triggers on them, so an enqueue already produces a change event on every replica; both dispatchers subscribe to it. The idle case gets ~120× cheaper and the busy case gets FASTER — work starts on the INSERT rather than up to a tick later. - The claim bucket stays floored by the BASE interval. Replicas do not share a backoff state, and two replicas computing different keys for one moment would both win.
|
|
146
|
+
|
|
147
|
+
**The cadence is declarable.** `scheduling: { admissionDrainMs, inferenceTickMs, cancelSweepMs, pollCeilingMs }` in `app.config.ts`, each with a matching `VOLTRO_*` env var that overrides it — the same ordering as `VOLTRO_TENANT_ISOLATION` over `tenancy.isolation`. They were internal constants, and a number the framework picks on a user's behalf belongs somewhere they can read it without reading our source. One resolver, called by both boot paths, so there is no second default to drift.
|
|
148
|
+
|
|
149
|
+
**Why the rows never left.** A claim answers one question about one bucket and was already answered the moment the bucket passed. A winning claim now deletes that schedule's own predecessors, so the table's size is a small multiple of the number of schedules rather than a function of uptime. How far back it prunes scales with the caller's bucket width — a cron keeps ~68 minutes of them (its firings carry their own instant, so a stalled one can re-present an old bucket), a 250 ms task ~1 minute (it recomputes its bucket at tick time, so an old one is unreachable). Deleting too early is a double fire; that grace is the whole safety argument. The 24-hour retention sweep stays as the backstop for a schedule that was renamed or deleted, which the per-schedule prune can never revisit.
|
|
150
|
+
|
|
151
|
+
Where reactivity is absent — a non-Postgres dialect with no broadcast broker — a remote replica's enqueue produces no local event and the ceiling is the whole latency budget. `VOLTRO_POLL_CEILING_MS` is there for that case and documented as such.
|
|
152
|
+
- **@voltro/logger** — The pretty log format now prints a nested `Error`'s `message`. It did not, and the JSON format did.
|
|
153
|
+
|
|
154
|
+
`Error.prototype.message` is non-enumerable, so `JSON.stringify(err)` emits the metadata and drops the message. `expandCauseForJson` has existed for a long time to solve exactly that — and it was wired into `jsonFormat` only. The section heading above it said "(JSON path)", which was literally accurate.
|
|
155
|
+
|
|
156
|
+
`voltro dev` prints the pretty format. What a consumer saw when their boot died on a saturated pooler:
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
auto-migrate failed — aborting boot
|
|
160
|
+
err={"failure":{"cause":{"length":117,…,"code":"XX000"},"message":"PgClient: Failed to connect"}}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`length: 117` is the length of a message that is not there. Recovered by hand, it was `(EMAXCONNSESSION) max clients reached in session mode - max clients are limited to pool_size: 15` — the whole diagnosis in one sentence, naming the fix.
|
|
164
|
+
|
|
165
|
+
Both the field tail and the plain-object cause branch expand now. The fix is in the formatter, not at the reporting call site: every `log.error('…', { err })` anywhere had the same hole.
|
|
166
|
+
- **@voltro/cli** — Every `@voltro/*` package now exports its own `package.json`, so `require('@voltro/cli/package.json').version` works.
|
|
167
|
+
|
|
168
|
+
It threw. Node has enforced this since 12: a package with an `exports` field exposes only what that field lists, and none of the 77 packages listed `"./package.json"`.
|
|
169
|
+
|
|
170
|
+
Reported by a consumer for whom it was the instruction WE gave for settling whether a security command had been running on stale code — so the verification step for a security question could not run at all. Both the workspace `exports` and the shipped `publishConfig.exports` are fixed, and a guard sweeps every package so a new one cannot ship without it.
|
|
171
|
+
- **@voltro/cli, @voltro/sql-postgres** — The `db pool:` boot line now counts the connections this process holds OUTSIDE the pool, and names them.
|
|
172
|
+
|
|
173
|
+
It reported `max × replicas` and called that the connection count. A consumer sizing a per-pod budget against a pooler measured the gap:
|
|
174
|
+
|
|
175
|
+
> `LISTEN` läuft außerhalb von `dbMaxConnections` (eine pro Pod, gemessen sogar > 3). Der echte Bedarf ist `dbMaxConnections + 1`.
|
|
176
|
+
|
|
177
|
+
Their measurement was right and their conclusion was one short. The framework opens a standalone connection in three places, and a full deployment holds all three:
|
|
178
|
+
|
|
179
|
+
| Process | Connection | When | |---|---|---| | api `voltro serve` | CDC `LISTEN` consumer | `changeStrategy: 'cdc'` | | web `voltro start` | ISR invalidator `LISTEN` | a page declares `cacheInvalidatesOn` | | web `voltro start` | postgres ISR cache client | `SSR_CACHE=postgres` |
|
|
180
|
+
|
|
181
|
+
The third is not a `LISTEN`, which is why counting `LISTEN` rows in `pg_stat_activity` undercounts, and why `+1` could not have been documented as a constant: the count is per PROCESS and only the process knows what it armed.
|
|
182
|
+
|
|
183
|
+
The line says `No connections outside the pool in this process` when there are none — silence about it is what made "counted, zero" indistinguishable from "not counted". The `maxConnections` docstring, which promised `+1` as if it were the deployment's number, is corrected. Production-hardening docs (both languages) gain the table plus the `maxSurge` arithmetic a rolling update needs.
|
|
184
|
+
- **@voltro/cli** — The retention sweep is registered on every dialect — it was postgres-only, and silently.
|
|
185
|
+
|
|
186
|
+
`wireRetentionSweep` opened with `if (dialect !== 'postgres') return`, so on mariadb, mysql, mssql and sqlite **none of its seven policies was registered, nothing was ever deleted, and the boot printed no armed-policies line** — so there was nothing to notice either. A consumer on MariaDB 11.8.8 measured it by reading the published bundle rather than their logs:
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
_voltro_schedule_claims 109 520 rows 32.8 MB over 20 days
|
|
190
|
+
_voltro_schedule_runs 17 507 rows 7.4 MB
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Their seven `VOLTRO_*_TTL_HOURS` variables were inert — read only inside the branch that never ran — and two of them were already set in their Helm chart.
|
|
194
|
+
|
|
195
|
+
**The gate was aimed at the right thing and applied to the wrong scope.** What is postgres-specific is the fast DELETE (`"camelCase"` quoting, `DELETE … RETURNING`), which is one branch of one function that has always had a portable fallback beside it. Gating the REGISTRATION on it turned a performance choice into a feature that does not exist. The dialect check now sits on the branch it describes.
|
|
196
|
+
|
|
197
|
+
Two things came out with it:
|
|
198
|
+
|
|
199
|
+
- **The fallback deleted row by row.** Acceptable while the path was unreachable; against the reporter's backlog it is 20 000 round trips per sweep pass. It reads a bounded batch of ids and issues ONE set-based delete for them — still bounded, so the DELETE never grows to lock the whole backlog. - **Two tests asserted the defect as intended behaviour**, with reasoning that was internally consistent and rested on the premise that was itself the bug (*"the sweep is postgres-only, and announcing a delete that will not happen is the mirror image of the defect"*). Both are inverted now and run across all five dialects.
|
|
200
|
+
|
|
201
|
+
This is the third turn of the same screw, and the reporter's framing is the one to keep: we fixed *a standing delete that never introduces itself*, then shipped *one that introduces itself and does not run* — and beside both of those sat one that silently did not exist.
|
|
202
|
+
- **@voltro/cli** — `voltro db scan-credentials` no longer reports the framework's own redaction markers as credentials, and no longer claims a match was a *key*.
|
|
203
|
+
|
|
204
|
+
A consumer with correctly-redacting plugins got:
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
✗ _voltro_row_history.data — 69 of 149 row(s) match a credential-shaped key
|
|
208
|
+
matched (rows per needle, may overlap): token (69)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
All 69 rows were `"_omitted": ["token"]` — `@voltro/plugin-versioning`'s record that a `.serverOnly().sensitive('secret')` column was deliberately left OUT of the snapshot. The scan matched the proof that nothing is stored there, called it a credential, and printed *purge them AND rotate the credentials* underneath.
|
|
212
|
+
|
|
213
|
+
Two changes. The headline says what the predicate does — it is a substring match over the whole serialized column, so it finds a credential-shaped **name** anywhere in the value, which it always did. And each hit is now EXPLAINED: a bounded second pass (500 matched rows per target) reads them back in-process and separates a JSON **key** from a **redaction marker** (`_omitted`, `__redacted`). Values are never printed and never logged.
|
|
214
|
+
|
|
215
|
+
A target whose every matched row is a marker reports as explained and exits `0`. The bar is deliberately high — every matched row examined, every one a marker and nothing else. A capped read-back, one real key, or one row that will not parse as JSON keeps the target a finding and still exits `1`.
|
|
216
|
+
|
|
217
|
+
The shape mattered more than the one key name: the more columns an app classifies correctly, the more markers it writes, and the redder the scan turned.
|
|
218
|
+
- **@voltro/runtime, @voltro/cli** — `advisoryLock` scheduling no longer reads the whole `_voltro_schedule_claims` table to answer whether one claim row exists, and that table is now swept on the scale it fills.
|
|
219
|
+
|
|
220
|
+
The existence check ran `SELECT "id" FROM "_voltro_schedule_claims"` with no `WHERE` and no `LIMIT`, then filtered in JavaScript — twice per claim attempt (the fast path, and the re-read that separates "lost the race" from "the claims table is broken"), on every replica, for every schedule firing. A consumer measured ten concurrent copies of that scan holding every connection of a 15-slot pooler, with an SSR render behind them at **300 490 ms**. It is a primary-key lookup bounded to one row now (`id` *is* the claim key).
|
|
221
|
+
|
|
222
|
+
The pool-acquire bound added in 0.32.0 turns that from a hang into an error; it does not stop the scan from filling the pool. Both are needed.
|
|
223
|
+
|
|
224
|
+
`VOLTRO_SCHEDULE_CLAIMS_TTL_HOURS` also defaults to **24 hours** instead of 30 days. The 30-day default was copied from the framework's history tables (`_voltro_schedule_runs` and friends), and a claim row is a lock ledger — it answers a question about one firing instant and nothing reads yesterday's. At the 1 557 rows/hour that consumer measured, a 30-day window reaches ~1.1 million rows before the first one ages out. Raise it deliberately if you need to; the number to reason about is the longest a replica may be paused and still be trusted not to re-fire a bucket it already lost.
|
|
225
|
+
|
|
226
|
+
The boot announcement can now express an age under a day (`older than 1h`); it previously rounded every TTL to whole days, so an operator setting one hour read their own policy back as `older than 0d`.
|
|
227
|
+
- **@voltro/runtime** — A coordinated periodic task armed below one second now runs at the interval it was given.
|
|
228
|
+
|
|
229
|
+
`scheduleCoordinated` floors the wall clock to its `intervalMs` and races on that instant; the claim key truncated it to second precision. A task at 250 ms therefore produced four bucket instants per second that collapsed to one key — the first tick won and the other three were dropped as "lost the claim". Measured: 1 of 4.
|
|
230
|
+
|
|
231
|
+
`voltro.ai.inference` is armed at 250 ms and was dispatching once per second, on every multi-replica deployment, with nothing above `warn` to say so.
|
|
232
|
+
|
|
233
|
+
This is the defect the coordinator's own comment describes at minute precision (6-field crons firing once a minute), one decimal place down; that comment was written before `scheduleCoordinated` existed, and `scheduleCoordinated` is the caller that goes below a second.
|
|
234
|
+
|
|
235
|
+
Milliseconds join the claim key only when non-zero, so every cron key is byte-identical to before — load-bearing during a rolling deploy, where old and new replicas computing different keys for one firing would both win and double-fire.
|
|
236
|
+
|
|
237
|
+
Note the consequence for table size: a sub-second task now writes claim rows at its true rate. Bounded by `VOLTRO_SCHEDULE_CLAIMS_TTL_HOURS` (24 h), and `ai.tickIntervalMs` raises the interval if you want fewer.
|
|
238
|
+
|
|
239
|
+
### Internal (no consumer-facing effect)
|
|
240
|
+
|
|
241
|
+
- **@voltro/cli** — No separate consumer-facing note on purpose: this refines the per-needle breakdown described in the UNRELEASED 0.32.0 section, and that section — which is what a reader will actually see — carries the correction. Documenting it twice would describe one change as two.
|
|
242
|
+
|
|
243
|
+
The refinement: the per-needle counts OVERLAP and do not sum to the hit count (a row holding both a token and a secret is counted by both). The output line says so now, because two numbers printed under a total invite being added up, and a reader who adds them and gets more than the total loses confidence in the whole report.
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## [0.32.0] — 2026-08-10
|
|
248
|
+
|
|
249
|
+
### ⚠ BREAKING
|
|
250
|
+
|
|
251
|
+
- **@voltro/plugin-audit** — **`auditPlugin` stored what a call RETURNED, verbatim, with no option to reach it. `redactOutcome` now exists and defaults to `'all'`.**
|
|
252
|
+
|
|
253
|
+
Found on the first run of `voltro db scan-credentials` after we widened its columns, against a real database:
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
✗ _voltro_audit_log.outcome — 9 of 263 row(s) match a credential-shaped key
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Four were `webhooks.create` rows carrying a live 64-character `signingSecret` in full. The reporter had BOTH existing options on — `redactInput: 'all'`, `redactSubject: 'metadata'` — and there was no third to reach this field with.
|
|
260
|
+
|
|
261
|
+
**The option that existed covers the field these calls leave empty.** `redactInput`'s own docstring names "an API key at issuance" as its motivating case, and for a credential-ISSUING call the secret is never in the input:
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
apiKeys.createPersonalApiKey({ name, scopes }) // input: nothing sensitive
|
|
265
|
+
→ { keyValue: '<the plaintext key>' } // outcome: the whole point
|
|
266
|
+
webhooks.create({ url, subscribedEvents }) // input: nothing sensitive
|
|
267
|
+
→ { signingSecret: '<live secret>' } // outcome: returned once, by design
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
MIGRATION: `outcome.value` and `outcome.error` become `{ __redacted: 'all' }`. The error's `_tag` SURVIVES — a trail recording "something failed" without saying what is not a trail, and a tag is a schema-declared discriminant that structurally cannot be a secret. `kind` and `durationMs` are untouched, the caller still receives the real result, and a `record` predicate still sees the live outcome. Set `redactOutcome: 'none'` to keep the old behaviour.
|
|
271
|
+
|
|
272
|
+
Also documented: a FUNCTION sink gets neither the `_voltro_audit_log` table nor the retention sweep — both are gated on `sink` being the literal `'datastore'`. A function that redacts and delegates to `dataStoreAuditSink` still writes rows on a database where the table exists, while creating it nowhere and arming the TTL nowhere. It works where you tested it and fails on the next fresh database. `redactOutcome` removes the reason to reach for that composition; the docstring now names the cliff for anyone who reaches for it anyway.
|
|
273
|
+
|
|
274
|
+
**`voltro update` carries you across this** — codemod `0.32.0/01_audit-redacts-outcome`.
|
|
275
|
+
|
|
276
|
+
### Added
|
|
277
|
+
|
|
278
|
+
- **@voltro/cli** — **`voltro doctor` said nothing when the unknown-scope rule did not run, which read exactly like a clean result.**
|
|
279
|
+
|
|
280
|
+
The rule needs a declared scope vocabulary — with `@voltro/plugin-rbac` that is the union of its `roles` map. An app doing RBAC without the plugin (roles as plain literal arrays in `lib/teamRoles.ts`) publishes none, so the rule stays quiet. A consumer confirmed it empirically: doctor reports **0** unknown-scope findings on the tree that contains the exact bug the rule was built for — `webhooks.test` guarded by `webhooks:test` while no role grants it. Silence read as coverage.
|
|
281
|
+
|
|
282
|
+
Two things now:
|
|
283
|
+
|
|
284
|
+
- **The dormant state SAYS it is dormant**, in the human report and in `--json` (`evaluated: false`, which used to be an empty findings array indistinguishable from a clean app). It ends with the sentence that matters: this section is not a clean bill of health. - **An app can point the rule at its own vocabulary**, which the consumer proposed and which doctor now reads:
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
// app.config.ts
|
|
288
|
+
doctor: { scopeVocabulary: './lib/teamRoles.ts#ALL_TEAM_SCOPES' }
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Deliberately a plain read of an exported string array. Anything cleverer (evaluating a roles map, following a builder) fails differently per app and lands back at "quiet for reasons you cannot see".
|
|
292
|
+
|
|
293
|
+
### Changed
|
|
294
|
+
|
|
295
|
+
- **@voltro/cli** — **`voltro db scan-credentials` now says its name set is a heuristic, so a clean run stops reading as an all-clear.**
|
|
296
|
+
|
|
297
|
+
Reported with the case that proves it: on the same table, in the same column, the scan found `signingSecret` and missed `keyValue` — the plaintext API key an `apiKeys.*` mutation returns. `keyValue` matches none of the needles and never will; no name list covers every convention an app can invent.
|
|
298
|
+
|
|
299
|
+
Not a defect, a heuristic being a heuristic — and the reporter's framing is the fix: *"one line in the output saying the name set IS a heuristic would stop a clean run reading as an all-clear."* The report ends with what a clean result actually means: no credential-SHAPED key found, which is not the same as no credential.
|
|
300
|
+
- **@voltro/cli** — **`voltro db scan-credentials` reported a hit count without saying which key matched, under advice that could not be carried out or argued with.**
|
|
301
|
+
|
|
302
|
+
The output was `69 of 149 row(s) match a credential-shaped key` followed by `Purge them AND rotate the credentials`. Purge what? Rotate which credential? And a column mentioning the word `token` in prose reads identically to one holding a live one — the reporter had both kinds in the same column and no way to separate them from the output.
|
|
303
|
+
|
|
304
|
+
Each hit line now names the needles and their row counts — which OVERLAP and
|
|
305
|
+
do not sum to the hit count, because a row holding both a token and a secret is
|
|
306
|
+
counted by both, and two numbers printed under a total otherwise invite being
|
|
307
|
+
added up:
|
|
308
|
+
|
|
309
|
+
```
|
|
310
|
+
✗ _voltro_audit_log.outcome — 69 of 149 row(s) match a credential-shaped key
|
|
311
|
+
matched (rows per needle, may overlap): token (61), secret (12)
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
One extra COUNT per needle, taken only for a target that already matched — so the cost lands exactly where somebody is about to do work and nowhere else.
|
|
315
|
+
|
|
316
|
+
### Fixed
|
|
317
|
+
|
|
318
|
+
- **@voltro/cli** — **`voltro update --dry-run` could never preview a jump's codemods — the manifest it reads has never been published.** Checked against the registry rather than inferred: `@voltro/cli` at 0.25.0, 0.29.0, 0.30.2 and 0.31.0 all ship no `voltro` field at all, while the repo's own `package.json` carries 65 entries.
|
|
319
|
+
|
|
320
|
+
`scripts/prepare-publish.mjs`'s `cleanManifest` builds a fresh publish manifest field by field rather than deleting from a copy — an allowlist by construction — so a new top-level key is dropped in silence and nothing downstream mentions it.
|
|
321
|
+
|
|
322
|
+
**What this did NOT cost, stated because the alarming reading is the wrong one:** no user has ever missed a codemod that should have run. The manifest feeds the PREVIEW only; the actual run happens after the install, out of the target CLI's own registry, which is present by then. And the missing case was already handled loudly — the preview printed *"could not preview … This is NOT the same as 'no codemods'"* rather than an confident "none". An honest "I could not look" for four releases, where the feature was built to look.
|
|
323
|
+
|
|
324
|
+
The fix carries `voltro` through, and the guard that goes with it is the part worth keeping: `codemodManifest.test.ts` asserts the REPO's package.json carries every codemod, and it was green on every one of those releases. A source-reading guard cannot see what the publish pipeline does downstream of it. So the assertion is made against the STAGED file, read back off disk after it is written, and it runs inside `prepare-publish` itself — which the gate's `Pack + verify` step already invokes, so it needs no separate wiring.
|
|
325
|
+
|
|
326
|
+
Red-verified by removing the one-line fix and re-running: the staged manifest comes back with NO entries against the source's 65, and publishing aborts.
|
|
327
|
+
- **@voltro/cli** — **`voltro dev` read the COMPILED config, so editing `app.config.ts` had no effect in any app that had ever run `voltro build`.**
|
|
328
|
+
|
|
329
|
+
`loadConfig` preferred `.framework/dist/server/appConfig.js` whenever it existed — a build output, i.e. whatever the config said the last time somebody built. The comment on that branch said the `.ts` source is the fallback "for dev", but the condition was `existsSync`, which cannot know which command is running.
|
|
330
|
+
|
|
331
|
+
Measured on the fixture: `app.config.ts` set to `locales: ['de','en'], defaultLocale: 'de'`, `voltro dev` booted, SSR response `<html lang="en">` — the value from a build hours earlier. Neither the declared default nor the first declared locale reached the render.
|
|
332
|
+
|
|
333
|
+
Found while reproducing a consumer's report that a declared `locales:` did not affect `<html lang>` in dev. An app that has never built is unaffected, which is why this survived.
|
|
334
|
+
|
|
335
|
+
**And the second half of the same report: dev never negotiated `Accept-Language` in resolver-only mode.** `makeSsrI18nResolver` returns `() => undefined` for an empty wrap set — right for the PROVIDER, wrong for the locale — so the caller fell through to `cookie ?? defaultLocale`. `voltro build` had already grown the resolver-only runtime; dev had not. The same dev/build drift as the release before, one release later, in the opposite direction.
|
|
336
|
+
|
|
337
|
+
Measured after the fix, all three signals: no headers → the declared default; `Accept-Language: en` → `en`; cookie beats `Accept-Language`. `ssrIntlDiagnostic` is gated on a REAL provider now, so resolver-only mode does not claim one was supplied.
|
|
338
|
+
- **@voltro/cli** — **The duplicate-version check read the pnpm store, so it fired for everyone on the release right after they upgraded.**
|
|
339
|
+
|
|
340
|
+
Last release taught it to see copies a walk from the app root cannot reach — a second version pulled in by a SIBLING workspace package. It did that by reading the pnpm virtual store directly, and the store also holds every version pnpm ever unpacked. On a real tree, one release later:
|
|
341
|
+
|
|
342
|
+
```
|
|
343
|
+
@voltro/cli — 0.29.0, 0.30.0, 0.30.1, 0.30.2, 0.31.0
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
…while EVERY `@voltro/*` in that workspace was linked at exactly 0.31.0. The extras were residue: nothing links them, `pnpm store prune` removes them, they cannot be loaded. The reporter's summary is the right one — *"the rule moved from blind-to-the-real-case to noisy-on-every-upgrade, and both endings are the same: the reader stops looking."* This ending is the worse of the two, because it fires for everyone at exactly the moment they are reading the output.
|
|
347
|
+
|
|
348
|
+
**A package manager's cache is not a statement about the program.** It counts what is LINKED now: one `package.json` per (workspace package × framework package), derived from `pnpm-workspace.yaml` or the conventional `packages/*` / `apps/*` layout. That is cheaper than the store walk it replaces, free of residue, and still finds the sibling case it was widened for.
|
|
349
|
+
- **@voltro/cli** — **The declared-event scan searched for the event NAME, so it missed every app following the typed path — 19 findings, 0 real.**
|
|
350
|
+
|
|
351
|
+
`emit` is typed `(event: string | OutgoingEventDescriptor<P> | DeclaredEventLike, …)`, and the DESCRIPTOR overload is the type-safe one — the path the schema-decode behaviour rewards. An app on it writes `emitEvent(ctx, apiKeyCreated, …)` and never repeats the name string anywhere near the emit.
|
|
352
|
+
|
|
353
|
+
So the rule found apps that emit by string literal and missed apps that emit by descriptor, which is backwards: the second group is the one doing it properly. The ADVISORY caveat we shipped was exact and useless — it said "an emit through a variable will read as missing here", and the variable IS the idiomatic call.
|
|
354
|
+
|
|
355
|
+
It resolves the binding now. The exported const and the `name:` are in ONE statement (`export const apiKeyCreated = defineEvent({ name: 'apiKey.created' … })`), so this is a local read of the declaring file rather than a resolution. Both spellings count as an emit. The caveat is narrowed to what actually remains invisible: a name assembled at runtime.
|
|
356
|
+
|
|
357
|
+
Reported with a working reference implementation attached, which is where the three lines came from.
|
|
358
|
+
- **@voltro/sql-postgres** — **A query with no free pooled connection waited forever, with no error and no log line.**
|
|
359
|
+
|
|
360
|
+
`new pg.Pool({...})` was constructed without `connectionTimeoutMillis`, and node-postgres defaults it to `0` — wait indefinitely. Reported as: *"ein Query ohne freie Verbindung wartet unbegrenzt, ohne Fehler und ohne Logzeile."*
|
|
361
|
+
|
|
362
|
+
The distinction that makes this worth a default: `statementTimeoutMs` bounds a query the SERVER is running; nothing bounded a query the CLIENT had not sent yet. Those are the two halves of "a request is stuck", and only one was covered.
|
|
363
|
+
|
|
364
|
+
Defaults to 10 s (`DEFAULT_ACQUIRE_TIMEOUT_MS`), overridable per connection with `acquireTimeoutMs`; `0` restores the driver's unbounded wait. A bounded failure is more useful than an unbounded wait even when the pool would have freed up: it names the pool as the cause at the moment it IS the cause, instead of surfacing as unexplained latency somewhere with no connection information in it.
|
|
365
|
+
|
|
366
|
+
**Also documented, from the same report:** with `changeStrategy: 'cdc'` a process needs `maxConnections + 1`. The LISTEN consumer cannot use a pooled connection, so `@effect/sql-pg` opens a standalone `new Pg.Client(pool.options)` that is outside `max` and outside every number derived from it. A per-pod budget built on `maxConnections` is short by exactly one, which surfaces as the last pod of a rollout failing to connect rather than as a pool warning.
|
|
367
|
+
- **@voltro/cli** — **Shipping `whats-new.md` for the right release depended on a human remembering a step between two other steps.**
|
|
368
|
+
|
|
369
|
+
The module is generated from the top CHANGELOG section, and the command that writes a new one (`changelog-release.mjs --release`) did not regenerate it. So whether the published guide described the release you just installed came down to whether someone ran the generator in the window between the roll and the publish.
|
|
370
|
+
|
|
371
|
+
Measured across the published tarballs rather than asserted:
|
|
372
|
+
|
|
373
|
+
| `@voltro/cli` | ships | |---|---| | 0.28.0 | `# What's new in 0.27.0` | | 0.29.0 – 0.31.0 | each names its own version |
|
|
374
|
+
|
|
375
|
+
So it has gone wrong once in the last six, not every time — and the four that are right are right because a human did the step, which is exactly the property being removed here. One in six is not a small number for a module whose whole job is *"read this FIRST when a task touches an area you have not worked in recently"*: on that release it was the one piece of the shipped guide guaranteed to be wrong about the version the reader had just installed. And it fails in the direction that reads as fine — a real version, described correctly, just not theirs.
|
|
376
|
+
|
|
377
|
+
The roll regenerates now, in the same command, rather than as a checklist line: a step a human must remember between two others is the step that gets skipped on the release nobody is watching. A regeneration failure is loud and non-fatal, and `check-whats-new-version.mjs` (which already gates this in static-checks) stays as the backstop for the paths that bypass the roll.
|
|
378
|
+
|
|
379
|
+
**If you are looking at a stale `whats-new.md` in your own project, this is probably not the cause.** The per-project copy is seeded once and never overwritten on boot — it refreshes only on `voltro agents-md --force`. That file being behind is the seeder's documented behaviour, not this defect, and no framework release fixes it for you.
|
|
380
|
+
- **@voltro/cli** — **The retention sweep was armed, announced at every boot, and never ran in a process that restarted more often than every five minutes.**
|
|
381
|
+
|
|
382
|
+
It was `setInterval(sweepAll, 5 * 60_000)` with no initial run, so the first sweep was always five minutes away. `voltro dev` restarts on every file save, and at least one consumer runs `voltro dev` as their deployment: their `_voltro_schedule_claims` reached **86 214 rows / 33 MB** with the policy registered, and the boot printing `retention: N policy(ies) armed — rows older than the TTL are DELETED` every time.
|
|
383
|
+
|
|
384
|
+
That is this file's own lesson one turn further in. We fixed "a standing delete that never introduces itself" and shipped a standing delete that introduces itself and then does not run.
|
|
385
|
+
|
|
386
|
+
The first sweep now fires 30 s after boot, then on the interval. The delay is a compromise with the failures on either side: zero would put a multi-table DELETE in front of the first request of every boot, five minutes is what produced the report. The timer is `unref`'d, so it never holds a process open on its own.
|
|
387
|
+
- **@voltro/cli** — **`voltro dev` reported `engine: "in-memory"` while running a cluster engine, and a consumer built a diagnosis on it.**
|
|
388
|
+
|
|
389
|
+
The line derived its label from `store === 'postgres' ? 'cluster-postgres' : 'in-memory'` — so mariadb, mysql, sqlite and mssql all printed `in-memory` while the block directly above had just built `cluster-sql` for exactly those stores. The boot therefore printed two lines that contradicted each other:
|
|
390
|
+
|
|
391
|
+
```
|
|
392
|
+
workflow engine: cluster-sql, dialect=mariadb
|
|
393
|
+
workflow engine selected · engine: "in-memory"
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
A consumer read the second — later, more definitive-sounding — and concluded that `voltro dev` runs workflows on a different engine than a deployment does. That is a fair reading of a line that is simply false, and it sent them down the wrong half of an investigation into why their outgoing webhooks delivered in dev and produced zero rows under `voltro serve`.
|
|
397
|
+
|
|
398
|
+
The label is now decided where the engine is chosen. A log line is an assertion the framework makes about itself, and this one had no reader that could catch it: it is not typed against the layer it describes, and nothing asserted the pair agreed.
|
|
399
|
+
|
|
400
|
+
**Both boot paths also report WHICH workflow entity types they registered.** `API listening … workflowWorkers: 0` counts app-exported worker LAYERS — an advanced surface almost no app uses — and reads as "nothing consumes durable work here", which is the question the consumer could then answer from no other line. A delivery that fails with `Entity type 'Workflow/voltro.deliverWebhook' not registered` and one that never starts look identical from outside; this separates them before anyone reproduces anything.
|
|
401
|
+
|
|
402
|
+
### Internal (no consumer-facing effect)
|
|
403
|
+
|
|
404
|
+
- **@voltro/database** — The count of `descriptor.order` read sites is ASSERTED rather than stated: the prose said ELEVEN and there are twelve (three in `sqlCompiler.ts`, nine in `jsonEagerCompiler.ts`). Written from memory of a replacement run instead of from the file — the exact mistake two consumer rounds have been about, made inside the document describing it.
|
|
405
|
+
|
|
406
|
+
`queryDescriptorOrderAbsent.test.ts` now pins `{ 'sqlCompiler.ts': 3, 'jsonEagerCompiler.ts': 9 }`, so a compiler growing or losing a read site fails the suite instead of drifting quietly into a number somebody quotes. No behaviour change.
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
42
410
|
## [0.31.0] — 2026-08-10
|
|
43
411
|
|
|
44
412
|
### Added
|
package/dist/clusterTestSuite.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { E as e, _ as t, h as n, w as r, x as i } from "./primitives-Bp98_F5L.js";
|
|
2
|
-
import { i as a } from "./src-
|
|
2
|
+
import { i as a } from "./src-CP61KZ9V.js";
|
|
3
3
|
import { i as o, p as s } from "./cluster-CyIyBO39.js";
|
|
4
4
|
import { Cron as c, Deferred as l, Effect as u, Fiber as d, Layer as f, Schema as p } from "effect";
|
|
5
5
|
import { SqlClient as m } from "@effect/sql";
|
package/dist/index.d.ts
CHANGED
|
@@ -807,6 +807,17 @@ export declare interface DrainOutcome {
|
|
|
807
807
|
* differently because of this flag.
|
|
808
808
|
*/
|
|
809
809
|
readonly examined: boolean;
|
|
810
|
+
/**
|
|
811
|
+
* When the earliest not-yet-due pending row comes due, as an epoch ms — the
|
|
812
|
+
* number that lets the drainer arm for that instant instead of polling until
|
|
813
|
+
* it arrives.
|
|
814
|
+
*
|
|
815
|
+
* `undefined` means "nothing in the read window is waiting". It is a LOWER
|
|
816
|
+
* bound on the true next deadline (the window is capped by `batchSize`), which
|
|
817
|
+
* is the safe direction: an understated deadline costs one early tick, an
|
|
818
|
+
* overstated one misses a start.
|
|
819
|
+
*/
|
|
820
|
+
readonly nextDueAt?: number;
|
|
810
821
|
}
|
|
811
822
|
|
|
812
823
|
/**
|
|
@@ -1100,6 +1111,25 @@ export declare interface PendingIntent {
|
|
|
1100
1111
|
*/
|
|
1101
1112
|
export declare const pendingSlotId: (workflowName: string, mode: string, controlKey: string) => string;
|
|
1102
1113
|
|
|
1114
|
+
/**
|
|
1115
|
+
* The same read, plus the answer to "when is the next one due".
|
|
1116
|
+
*
|
|
1117
|
+
* Both come out of ONE query on purpose. The drainer needs the second number to
|
|
1118
|
+
* arm its next tick for the exact moment a debounce window closes instead of
|
|
1119
|
+
* polling until it notices — and paying a second round trip for it would put
|
|
1120
|
+
* the read back on a timer, which is the cost the deadline is there to remove.
|
|
1121
|
+
*
|
|
1122
|
+
* `nextDueAt` is `undefined` when nothing in the window is waiting, which the
|
|
1123
|
+
* caller reads as "there is no deadline I know of" and NOT as "there is no work
|
|
1124
|
+
* ever" — the window is bounded by `limit`, so a full window can hide later
|
|
1125
|
+
* rows. That understates the deadline, never overstates it, and understating it
|
|
1126
|
+
* costs one early tick while overstating it would miss a due start.
|
|
1127
|
+
*/
|
|
1128
|
+
export declare const pendingWindow: (store: AdmissionDataStore, now: number, limit: number) => Promise<{
|
|
1129
|
+
readonly due: ReadonlyArray<PendingIntent>;
|
|
1130
|
+
readonly nextDueAt: number | undefined;
|
|
1131
|
+
}>;
|
|
1132
|
+
|
|
1103
1133
|
/**
|
|
1104
1134
|
* Write (or collapse into) the pending row a `defer` decision calls for.
|
|
1105
1135
|
* Returns the row id, so the caller can report it and the drainer can find it.
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { A as e, C as t, D as n, E as r, F as i, M as a, N as o, O as s, P as c, S as l, _ as u, a as d, b as f, c as p, d as m, f as h, g, h as _, i as v, j as y, k as b, l as x, m as S, n as C, o as w, p as T, r as E, s as D, t as O, u as k, v as A, w as j, x as M, y as N } from "./primitives-Bp98_F5L.js";
|
|
2
|
-
import { $ as P, A as F, B as I, C as L, D as R, E as z, F as B, G as V, H, I as U, J as W, K as G, L as K, M as q, N as J, O as Y, P as X, Q as Z, R as Q, S as $, T as ee, U as te, V as ne, W as re, X as ie, Y as ae, Z as oe, _ as se, a as ce, at as le, b as ue, c as de, ct as fe, d as pe, dt as me, et as he, f as ge, ft as _e, g as ve, h as ye, i as be, it as xe, j as Se, k as Ce, l as we, lt as Te, m as Ee,
|
|
3
|
-
export { J as ADMISSIONS_TABLE, ue as CANCEL_ON_WATERMARK, t as CurrentWorkflowExecutionId, j as CurrentWorkflowRunId,
|
|
2
|
+
import { $ as P, A as F, B as I, C as L, D as R, E as z, F as B, G as V, H, I as U, J as W, K as G, L as K, M as q, N as J, O as Y, P as X, Q as Z, R as Q, S as $, T as ee, U as te, V as ne, W as re, X as ie, Y as ae, Z as oe, _ as se, a as ce, at as le, b as ue, c as de, ct as fe, d as pe, dt as me, et as he, f as ge, ft as _e, g as ve, h as ye, i as be, it as xe, j as Se, k as Ce, l as we, lt as Te, m as Ee, mt as De, n as Oe, nt as ke, o as Ae, ot as je, p as Me, pt as Ne, q as Pe, r as Fe, rt as Ie, s as Le, st as Re, t as ze, tt as Be, u as Ve, ut as He, v as Ue, w as We, x as Ge, y as Ke, z as qe } from "./src-CP61KZ9V.js";
|
|
3
|
+
export { J as ADMISSIONS_TABLE, ue as CANCEL_ON_WATERMARK, t as CurrentWorkflowExecutionId, j as CurrentWorkflowRunId, Ge as DEFAULT_COLD_LOOKBACK_MS, Ue as DEFAULT_DRAIN_BATCH, $ as DEFAULT_EVENT_BATCH, X as DEFAULT_LEASE_MS, e as DEFERRING_CONTROLS, L as EVENTS_TABLE, P as FlowControlKeyError, B as PAUSES_TABLE, U as PENDING_TABLE, We as WATERMARKS_TABLE, O as WorkflowFlowControlProperty, C as WorkflowMessagesProperty, r as WorkflowRunRecorder, n as WorkflowStepInterceptorTag, E as WorkflowVersionTypeId, v as WorkflowWorkerLayerTypeId, Re as _voltroWorkflowAdmissionsTable, fe as _voltroWorkflowPausesTable, Te as _voltroWorkflowPendingTable, me as _voltroWorkflowRunEventsTable, _e as _voltroWorkflowRunStepsTable, Ne as _voltroWorkflowRunsTable, He as _voltroWorkflowStartContextsTable, F as _voltroWorkflowWatermarksTable, Se as admitStart, K as allIntents, y as assertConsistentPools, ve as awaitEvent, se as awaitSignal, we as awaitSignalSuspending, de as awaitUpdate, Fe as closeWorkflowChildrenForParent, he as comparePendingOrder, Ve as completeSuspendingSignal, Be as decideAdmission, a as deferringControlsOf, Q as deleteIntent, qe as discardIntent, Ke as drainTick, I as dueIntents, d as durableClock, w as durableQueue, D as durableQueueModule, p as durableRateLimiterModule, ne as evaluateAdmission, s as getCurrentWorkflowExecutionId, b as getCurrentWorkflowRunId, x as getWorkflowFlowControl, k as getWorkflowVersionMetadata, o as hasCancelOn, c as hasFlowControl, De as inMemoryWorkflowEngineLayer, Oe as inspectWorkflow, ke as isFinishExpired, Ie as isStartExpired, m as isWorkflowWorkerLayer, q as linkExecution, ze as makeInMemoryRecorder, be as makeWorkflowRunRecorder, ge as makeWorkflowUpdateId, Y as newestEventAt, xe as nextSlotAt, H as noteIntentAttempt, te as pauseWorkflow, re as pendingSlotId, V as pendingWindow, G as persistDefer, Ce as planCancellations, le as pooledConcurrencyKey, h as processQueue, Pe as pruneAdmissions, T as queueWorker, S as rateLimit, W as readAdmissionState, ae as readPausedWorkflows, ee as readWatermark, ie as recordAdmission, oe as releaseLease, je as resolveAdmissionKeys, Me as resolveWorkflowMessageRun, Z as resumeWorkflow, Ee as sendWorkflowSignal, ye as sendWorkflowUpdate, ce as serialiseWorkflowRowForWire, _ as sleep, g as sleepUntil, u as step, A as stepIdempotencyKey, N as stepModule, pe as suspendingSignalDeferredName, z as sweepCancelOn, Ae as truncateWorkflowValue, i as validateFlowControl, f as withCompensation, M as workflow, l as workflowModule, Le as wrapWorkflowExecuteWithRunRecording, R as writeWatermark };
|
|
@@ -446,16 +446,23 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
446
446
|
}, Ee = async (e, t, n) => {
|
|
447
447
|
let r = await e.query(H(R, g(v("executionId", t), v("outcome", "admitted"), C("releasedAt")), [], 10));
|
|
448
448
|
return r.length === 0 ? !1 : (await Promise.all(r.map((t) => e.update(R, String(t.id), { releasedAt: new Date(n) }))), !0);
|
|
449
|
-
}, De = async (e, t, n) => (await e.
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
449
|
+
}, De = async (e, t, n) => (await Oe(e, t, n)).due, Oe = async (e, t, n) => {
|
|
450
|
+
let r = (await e.query(H(L, void 0, [{
|
|
451
|
+
column: "priority",
|
|
452
|
+
direction: "desc"
|
|
453
|
+
}, {
|
|
454
|
+
column: "firstSeenAt",
|
|
455
|
+
direction: "asc"
|
|
456
|
+
}], n))).map(ve), i;
|
|
457
|
+
for (let e of r) e.dueAt <= t || (i === void 0 || e.dueAt < i) && (i = e.dueAt);
|
|
458
|
+
return {
|
|
459
|
+
due: r.filter((e) => e.dueAt <= t).sort(he),
|
|
460
|
+
nextDueAt: i
|
|
461
|
+
};
|
|
462
|
+
}, ke = async (e, t) => (await e.query(H(L, void 0, [{
|
|
456
463
|
column: "firstSeenAt",
|
|
457
464
|
direction: "asc"
|
|
458
|
-
}], t))).map(ve), W = async (e, t) => e.delete(L, t),
|
|
465
|
+
}], t))).map(ve), W = async (e, t) => e.delete(L, t), Ae = async (e, t, n, r) => {
|
|
459
466
|
let i = (await e.query(H(L, v("id", t), [], 1)))[0];
|
|
460
467
|
if (i === void 0) return !1;
|
|
461
468
|
let a = ve(i);
|
|
@@ -479,7 +486,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
479
486
|
});
|
|
480
487
|
} catch {}
|
|
481
488
|
return e.delete(L, t);
|
|
482
|
-
},
|
|
489
|
+
}, je = async (e, t, n, r) => {
|
|
483
490
|
await e.update(L, t.id, {
|
|
484
491
|
attempts: t.attempts + 1,
|
|
485
492
|
lastError: n.slice(0, 500),
|
|
@@ -500,13 +507,13 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
500
507
|
now: e.now,
|
|
501
508
|
reconsidering: e.reconsidering
|
|
502
509
|
});
|
|
503
|
-
},
|
|
510
|
+
}, Me = async (e, t, n = 1e3) => {
|
|
504
511
|
let r = await e.query(H(R, void 0, [{
|
|
505
512
|
column: "admittedAt",
|
|
506
513
|
direction: "asc"
|
|
507
514
|
}], n)), i = t.getTime(), a = r.filter((e) => e.releasedAt != null && B(e.admittedAt) < i);
|
|
508
515
|
return await Promise.all(a.map((t) => e.delete(R, String(t.id)))), a.length;
|
|
509
|
-
},
|
|
516
|
+
}, Ne = async (e) => {
|
|
510
517
|
let { store: t, control: n, now: r } = e, i = F(n, e.payload), a = await G({
|
|
511
518
|
store: t,
|
|
512
519
|
control: n,
|
|
@@ -566,7 +573,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
566
573
|
};
|
|
567
574
|
let o = e.payload, s = [], c = 1;
|
|
568
575
|
if (n.batch !== void 0 && i.batchKey !== void 0) {
|
|
569
|
-
let r = await
|
|
576
|
+
let r = await Pe(t, n.workflowName, i.batchKey, n.batch.maxSize);
|
|
570
577
|
o = { items: [...r.map((e) => e.payload), e.payload] }, s = r.map((e) => e.id), c = r.length + 1;
|
|
571
578
|
}
|
|
572
579
|
return {
|
|
@@ -594,7 +601,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
594
601
|
runId: a.evict.runId
|
|
595
602
|
} }
|
|
596
603
|
};
|
|
597
|
-
},
|
|
604
|
+
}, Pe = async (e, t, n, r) => (await e.query({
|
|
598
605
|
table: "_voltro_workflow_pending",
|
|
599
606
|
predicate: g(v("workflowName", t), v("controlKey", n), v("mode", "batch")),
|
|
600
607
|
order: [{
|
|
@@ -607,22 +614,22 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
607
614
|
})).map((e) => ({
|
|
608
615
|
id: String(e.id),
|
|
609
616
|
payload: e.payload
|
|
610
|
-
})),
|
|
617
|
+
})), Fe = async (e, t, n, r) => {
|
|
611
618
|
await e.update("_voltro_workflow_admissions", t, {
|
|
612
619
|
executionId: n,
|
|
613
620
|
runId: r
|
|
614
621
|
});
|
|
615
|
-
},
|
|
622
|
+
}, Ie = O("_voltro_workflow_watermarks", {
|
|
616
623
|
id: x({ prefix: "wfwm" }),
|
|
617
624
|
positionAt: A(),
|
|
618
625
|
updatedAt: A().default("now")
|
|
619
|
-
}),
|
|
626
|
+
}), Le = (e) => {
|
|
620
627
|
let t = e instanceof Error ? e.message : String(e);
|
|
621
628
|
return t.length > 400 ? `${t.slice(0, 400)}…` : t;
|
|
622
|
-
},
|
|
629
|
+
}, Re = (e) => {
|
|
623
630
|
let t = [], n = [], r = [], i = /* @__PURE__ */ new Set(), a = /* @__PURE__ */ new Set(), o = e.payloadSchema === void 0 ? (e) => d.right(e) : (t) => {
|
|
624
631
|
let n = h.decodeUnknownEither(e.payloadSchema)(t);
|
|
625
|
-
return d.isRight(n) ? d.right(n.right) : d.left(
|
|
632
|
+
return d.isRight(n) ? d.right(n.right) : d.left(Le(n.left));
|
|
626
633
|
}, s = e.runs.map((e) => ({
|
|
627
634
|
run: e,
|
|
628
635
|
decoded: o(e.payload)
|
|
@@ -639,7 +646,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
639
646
|
workflowName: e.workflowName,
|
|
640
647
|
event: o.event,
|
|
641
648
|
subject: l.id,
|
|
642
|
-
detail:
|
|
649
|
+
detail: Le(u.left)
|
|
643
650
|
});
|
|
644
651
|
continue;
|
|
645
652
|
}
|
|
@@ -659,7 +666,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
659
666
|
workflowName: e.workflowName,
|
|
660
667
|
event: o.event,
|
|
661
668
|
subject: n,
|
|
662
|
-
detail:
|
|
669
|
+
detail: Le(t)
|
|
663
670
|
}), !1;
|
|
664
671
|
}
|
|
665
672
|
};
|
|
@@ -682,26 +689,26 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
682
689
|
discards: n,
|
|
683
690
|
problems: r
|
|
684
691
|
};
|
|
685
|
-
},
|
|
692
|
+
}, ze = (e) => {
|
|
686
693
|
let t;
|
|
687
694
|
for (let n of e) (t === void 0 || n.occurredAt.getTime() > t.getTime()) && (t = n.occurredAt);
|
|
688
695
|
return t;
|
|
689
|
-
},
|
|
696
|
+
}, Be = "_voltro_workflow_watermarks", Ve = "_voltro_workflow_events", K = "cancelOn", He = 3600 * 1e3, Ue = 500, q = (e) => {
|
|
690
697
|
if (e instanceof Date) return e;
|
|
691
698
|
if (typeof e == "number") return new Date(e);
|
|
692
699
|
if (typeof e == "string") {
|
|
693
700
|
let t = new Date(e);
|
|
694
701
|
if (!Number.isNaN(t.getTime())) return t;
|
|
695
702
|
}
|
|
696
|
-
}, J = (e) => e instanceof Error ? e.message : String(e),
|
|
697
|
-
table:
|
|
703
|
+
}, J = (e) => e instanceof Error ? e.message : String(e), We = async (e, t) => q((await e.query({
|
|
704
|
+
table: Be,
|
|
698
705
|
predicate: v("id", t),
|
|
699
706
|
order: [],
|
|
700
707
|
take: 1,
|
|
701
708
|
skip: void 0,
|
|
702
709
|
projection: void 0
|
|
703
|
-
}))[0]?.positionAt),
|
|
704
|
-
await e.upsert(
|
|
710
|
+
}))[0]?.positionAt), Ge = async (e, t, n) => {
|
|
711
|
+
await e.upsert(Be, {
|
|
705
712
|
id: t,
|
|
706
713
|
positionAt: n,
|
|
707
714
|
updatedAt: /* @__PURE__ */ new Date()
|
|
@@ -718,7 +725,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
718
725
|
};
|
|
719
726
|
}
|
|
720
727
|
});
|
|
721
|
-
},
|
|
728
|
+
}, Ke = async (e, t, n) => {
|
|
722
729
|
let r = async (r) => e.query({
|
|
723
730
|
table: "_voltro_workflow_runs",
|
|
724
731
|
predicate: g(v("tag", t), v("status", r)),
|
|
@@ -739,7 +746,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
739
746
|
startedAt: n
|
|
740
747
|
}];
|
|
741
748
|
});
|
|
742
|
-
},
|
|
749
|
+
}, qe = async (e, t, n) => (await e.query({
|
|
743
750
|
table: "_voltro_workflow_pending",
|
|
744
751
|
predicate: v("workflowName", t),
|
|
745
752
|
order: [{
|
|
@@ -756,7 +763,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
756
763
|
payload: t.payload,
|
|
757
764
|
firstSeenAt: n
|
|
758
765
|
}];
|
|
759
|
-
}),
|
|
766
|
+
}), Je = async (e, t) => {
|
|
760
767
|
let n = t.workflows.filter((e) => e.entries.length > 0);
|
|
761
768
|
if (n.length === 0) return {
|
|
762
769
|
examined: !1,
|
|
@@ -771,8 +778,8 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
771
778
|
};
|
|
772
779
|
let r = e.now?.() ?? Date.now(), i = t.batch ?? 500, a = t.coldLookbackMs ?? 36e5, o = /* @__PURE__ */ new Set();
|
|
773
780
|
for (let e of n) for (let t of e.entries) o.add(t.event);
|
|
774
|
-
let s = await
|
|
775
|
-
table:
|
|
781
|
+
let s = await We(e.store, K), c = s ?? new Date(r - a), l = await e.store.query({
|
|
782
|
+
table: Ve,
|
|
776
783
|
predicate: b("occurredAt", c),
|
|
777
784
|
order: [{
|
|
778
785
|
column: "occurredAt",
|
|
@@ -797,7 +804,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
797
804
|
if (n.length === 0) continue;
|
|
798
805
|
let r = [], i = [];
|
|
799
806
|
try {
|
|
800
|
-
r = await
|
|
807
|
+
r = await Ke(e.store, t.workflowName, _), i = await qe(e.store, t.workflowName, _), (r.length >= _ || i.length >= _) && (h = !0);
|
|
801
808
|
} catch (e) {
|
|
802
809
|
f.push({
|
|
803
810
|
subject: t.workflowName,
|
|
@@ -805,7 +812,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
805
812
|
}), g = !0;
|
|
806
813
|
continue;
|
|
807
814
|
}
|
|
808
|
-
let a =
|
|
815
|
+
let a = Re({
|
|
809
816
|
workflowName: t.workflowName,
|
|
810
817
|
entries: t.entries,
|
|
811
818
|
payloadSchema: t.payloadSchema,
|
|
@@ -847,10 +854,10 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
847
854
|
});
|
|
848
855
|
}
|
|
849
856
|
}
|
|
850
|
-
let v =
|
|
857
|
+
let v = ze(u), y = s;
|
|
851
858
|
if (g) e.log?.warn("workflow.cancelOn: holding the watermark — some workflows could not be evaluated", { failures: f.length });
|
|
852
859
|
else if (v !== void 0) try {
|
|
853
|
-
await
|
|
860
|
+
await Ge(e.store, K, v), y = v;
|
|
854
861
|
} catch (e) {
|
|
855
862
|
f.push({
|
|
856
863
|
subject: K,
|
|
@@ -858,7 +865,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
858
865
|
});
|
|
859
866
|
}
|
|
860
867
|
else if (s === void 0) try {
|
|
861
|
-
await
|
|
868
|
+
await Ge(e.store, K, c), y = c;
|
|
862
869
|
} catch (e) {
|
|
863
870
|
f.push({
|
|
864
871
|
subject: K,
|
|
@@ -879,7 +886,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
879
886
|
failures: f,
|
|
880
887
|
watermark: y
|
|
881
888
|
};
|
|
882
|
-
},
|
|
889
|
+
}, Ye = {
|
|
883
890
|
scanned: 0,
|
|
884
891
|
admitted: 0,
|
|
885
892
|
deferred: 0,
|
|
@@ -890,14 +897,16 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
890
897
|
failed: 0,
|
|
891
898
|
timedOut: 0,
|
|
892
899
|
examined: !1
|
|
893
|
-
},
|
|
894
|
-
let n = e.now ?? (() => Date.now()), r = e.log, i = t.batchSize ?? 200, a, o;
|
|
900
|
+
}, Xe = 200, Ze = async (e, t = {}) => {
|
|
901
|
+
let n = e.now ?? (() => Date.now()), r = e.log, i = t.batchSize ?? 200, a, o, s;
|
|
895
902
|
try {
|
|
896
|
-
a = await ye(e.store)
|
|
903
|
+
a = await ye(e.store);
|
|
904
|
+
let t = await Oe(e.store, n(), i);
|
|
905
|
+
o = t.due, s = t.nextDueAt;
|
|
897
906
|
} catch (e) {
|
|
898
|
-
return r?.warn?.("workflow flow control: could not read the admission queue", { cause: String(e) }),
|
|
907
|
+
return r?.warn?.("workflow flow control: could not read the admission queue", { cause: String(e) }), Ye;
|
|
899
908
|
}
|
|
900
|
-
let
|
|
909
|
+
let c = {
|
|
901
910
|
scanned: o.length,
|
|
902
911
|
admitted: 0,
|
|
903
912
|
deferred: 0,
|
|
@@ -907,40 +916,41 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
907
916
|
evicted: 0,
|
|
908
917
|
failed: 0,
|
|
909
918
|
timedOut: 0
|
|
910
|
-
},
|
|
919
|
+
}, l = /* @__PURE__ */ new Map(), u = [];
|
|
911
920
|
for (let e of o) {
|
|
912
921
|
if (e.mode === "batch") {
|
|
913
|
-
let t = `${e.workflowName}\u0000${e.controlKey}`, n =
|
|
914
|
-
n === void 0 ?
|
|
922
|
+
let t = `${e.workflowName}\u0000${e.controlKey}`, n = l.get(t);
|
|
923
|
+
n === void 0 ? l.set(t, [e]) : n.push(e);
|
|
915
924
|
continue;
|
|
916
925
|
}
|
|
917
|
-
|
|
926
|
+
u.push(e);
|
|
918
927
|
}
|
|
919
|
-
for (let t of
|
|
920
|
-
await
|
|
928
|
+
for (let t of u) try {
|
|
929
|
+
await Qe(e, t, a, c, n());
|
|
921
930
|
} catch (i) {
|
|
922
|
-
|
|
931
|
+
c.failed++, r?.warn?.("workflow flow control: an intent failed to drain", {
|
|
923
932
|
workflow: t.workflowName,
|
|
924
933
|
intent: t.id,
|
|
925
934
|
cause: String(i)
|
|
926
|
-
}), await
|
|
935
|
+
}), await je(e.store, t, String(i), n()).catch(() => {});
|
|
927
936
|
}
|
|
928
|
-
for (let t of
|
|
929
|
-
await
|
|
937
|
+
for (let t of l.values()) try {
|
|
938
|
+
await $e(e, t, a, c, n());
|
|
930
939
|
} catch (i) {
|
|
931
|
-
|
|
940
|
+
c.failed++;
|
|
932
941
|
let a = t[0];
|
|
933
942
|
r?.warn?.("workflow flow control: a batch failed to drain", {
|
|
934
943
|
workflow: a?.workflowName,
|
|
935
944
|
size: t.length,
|
|
936
945
|
cause: String(i)
|
|
937
|
-
}), a !== void 0 && await
|
|
946
|
+
}), a !== void 0 && await je(e.store, a, String(i), n()).catch(() => {});
|
|
938
947
|
}
|
|
939
|
-
return
|
|
940
|
-
...
|
|
941
|
-
examined: !0
|
|
948
|
+
return c.timedOut = await tt(e, n()).catch(() => 0), {
|
|
949
|
+
...c,
|
|
950
|
+
examined: !0,
|
|
951
|
+
...s === void 0 ? {} : { nextDueAt: s }
|
|
942
952
|
};
|
|
943
|
-
},
|
|
953
|
+
}, Qe = async (e, t, n, r, i) => {
|
|
944
954
|
let a = e.controls.get(t.workflowName);
|
|
945
955
|
if (a === void 0) {
|
|
946
956
|
await e.onAbandoned({
|
|
@@ -974,7 +984,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
974
984
|
return;
|
|
975
985
|
}
|
|
976
986
|
let o = F(a, t.payload);
|
|
977
|
-
await
|
|
987
|
+
await et(e, {
|
|
978
988
|
control: a,
|
|
979
989
|
intent: t,
|
|
980
990
|
decision: await G({
|
|
@@ -990,7 +1000,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
990
1000
|
now: i,
|
|
991
1001
|
items: void 0
|
|
992
1002
|
});
|
|
993
|
-
},
|
|
1003
|
+
}, $e = async (e, t, n, r, i) => {
|
|
994
1004
|
let a = t[0];
|
|
995
1005
|
if (a === void 0) return;
|
|
996
1006
|
let o = e.controls.get(a.workflowName);
|
|
@@ -1012,7 +1022,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1012
1022
|
return;
|
|
1013
1023
|
}
|
|
1014
1024
|
let l = F(o, c.payload);
|
|
1015
|
-
await
|
|
1025
|
+
await et(e, {
|
|
1016
1026
|
control: o,
|
|
1017
1027
|
intent: c,
|
|
1018
1028
|
decision: await G({
|
|
@@ -1028,7 +1038,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1028
1038
|
now: i,
|
|
1029
1039
|
items: s
|
|
1030
1040
|
});
|
|
1031
|
-
},
|
|
1041
|
+
}, et = async (e, t) => {
|
|
1032
1042
|
let { control: n, intent: r, decision: i, keys: a, counters: o, now: s, items: c } = t;
|
|
1033
1043
|
if (i.kind === "defer") {
|
|
1034
1044
|
await we({
|
|
@@ -1112,7 +1122,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1112
1122
|
});
|
|
1113
1123
|
for (let t of c ?? [r]) await W(e.store, t.id);
|
|
1114
1124
|
o.admitted++;
|
|
1115
|
-
},
|
|
1125
|
+
}, tt = async (e, t) => {
|
|
1116
1126
|
if (e.listRunningRuns === void 0) return 0;
|
|
1117
1127
|
let n = 0;
|
|
1118
1128
|
for (let r of e.controls.values()) {
|
|
@@ -1129,16 +1139,16 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1129
1139
|
}), n++);
|
|
1130
1140
|
}
|
|
1131
1141
|
return n;
|
|
1132
|
-
},
|
|
1142
|
+
}, nt = async (e, t) => {
|
|
1133
1143
|
if (e) try {
|
|
1134
1144
|
await e.recordEvent(t);
|
|
1135
1145
|
} catch {}
|
|
1136
|
-
},
|
|
1146
|
+
}, rt = (e, n) => {
|
|
1137
1147
|
let a = u.gen(function* () {
|
|
1138
1148
|
let i = n.pollIntervalMs ?? 200, a = n.maxPollIntervalMs ?? 5e3, o = n.timeoutMs ?? 1440 * 6e4, s = yield* r, c = yield* u.serviceOption(t), l = c._tag === "Some" ? c.value : void 0;
|
|
1139
1149
|
if (s === void 0) return yield* u.die(/* @__PURE__ */ Error("awaitSignal: no active workflow run id in scope"));
|
|
1140
1150
|
let d = s;
|
|
1141
|
-
yield* u.promise(() =>
|
|
1151
|
+
yield* u.promise(() => nt(l, {
|
|
1142
1152
|
runId: d,
|
|
1143
1153
|
eventType: "signal-awaited",
|
|
1144
1154
|
payload: { signalName: n.name }
|
|
@@ -1163,7 +1173,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1163
1173
|
})).find((e) => e.payload?.signalName === n.name);
|
|
1164
1174
|
if (i !== void 0) {
|
|
1165
1175
|
let e = yield* h.decodeUnknown(n.schema)(i.payload?.value);
|
|
1166
|
-
return yield* u.promise(() =>
|
|
1176
|
+
return yield* u.promise(() => nt(l, {
|
|
1167
1177
|
runId: d,
|
|
1168
1178
|
eventType: "signal-received",
|
|
1169
1179
|
payload: {
|
|
@@ -1180,16 +1190,16 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1180
1190
|
success: n.schema,
|
|
1181
1191
|
execute: a
|
|
1182
1192
|
});
|
|
1183
|
-
},
|
|
1193
|
+
}, it = async (e, t) => {
|
|
1184
1194
|
if (e !== void 0) try {
|
|
1185
1195
|
await e.recordEvent(t);
|
|
1186
1196
|
} catch {}
|
|
1187
|
-
},
|
|
1197
|
+
}, at = (e, n) => {
|
|
1188
1198
|
let a = u.gen(function* () {
|
|
1189
1199
|
let i = n.pollIntervalMs ?? 200, a = n.maxPollIntervalMs ?? 5e3, o = n.timeoutMs ?? 1440 * 6e4, s = n.take ?? 500, c = yield* r, l = yield* u.serviceOption(t), d = l._tag === "Some" ? l.value : void 0;
|
|
1190
1200
|
if (c === void 0) return yield* u.die(/* @__PURE__ */ Error("awaitEvent: no active workflow run id in scope"));
|
|
1191
|
-
let f = c, p = yield* n.since === "run-start" ?
|
|
1192
|
-
yield* u.promise(() =>
|
|
1201
|
+
let f = c, p = yield* n.since === "run-start" ? ot(e, f) : u.succeed(/* @__PURE__ */ new Date());
|
|
1202
|
+
yield* u.promise(() => it(d, {
|
|
1193
1203
|
runId: f,
|
|
1194
1204
|
eventType: "event-awaited",
|
|
1195
1205
|
payload: {
|
|
@@ -1219,7 +1229,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1219
1229
|
y = i.length;
|
|
1220
1230
|
for (let e of i) {
|
|
1221
1231
|
let t = yield* h.decodeUnknown(n.schema)(e.payload);
|
|
1222
|
-
if (n.match(t)) return yield* u.promise(() =>
|
|
1232
|
+
if (n.match(t)) return yield* u.promise(() => it(d, {
|
|
1223
1233
|
runId: f,
|
|
1224
1234
|
eventType: "event-received",
|
|
1225
1235
|
payload: {
|
|
@@ -1236,7 +1246,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1236
1246
|
success: n.schema,
|
|
1237
1247
|
execute: a
|
|
1238
1248
|
});
|
|
1239
|
-
},
|
|
1249
|
+
}, ot = (e, t) => u.gen(function* () {
|
|
1240
1250
|
let n = (yield* u.tryPromise({
|
|
1241
1251
|
try: () => e.store.query({
|
|
1242
1252
|
table: "_voltro_workflow_runs",
|
|
@@ -1254,10 +1264,10 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1254
1264
|
if (!Number.isNaN(e.getTime())) return e;
|
|
1255
1265
|
}
|
|
1256
1266
|
return /* @__PURE__ */ new Date(0);
|
|
1257
|
-
}),
|
|
1267
|
+
}), st = (e) => new Promise((t) => setTimeout(t, e)), ct = () => {
|
|
1258
1268
|
let e = globalThis.crypto;
|
|
1259
1269
|
return typeof e?.randomUUID == "function" ? e.randomUUID() : `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`;
|
|
1260
|
-
},
|
|
1270
|
+
}, lt = () => `wfu_${ct()}`, Y = async (e, t) => {
|
|
1261
1271
|
let n = t.id ?? t.executionId;
|
|
1262
1272
|
if (!n) throw Error("workflow message target requires target.id or target.executionId");
|
|
1263
1273
|
let r = await e.query({
|
|
@@ -1279,7 +1289,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1279
1289
|
}))[0];
|
|
1280
1290
|
if (!i) throw Error(`workflow message target not found: ${n}`);
|
|
1281
1291
|
return i;
|
|
1282
|
-
},
|
|
1292
|
+
}, ut = async (e) => {
|
|
1283
1293
|
let t = await Y(e.store, e.target);
|
|
1284
1294
|
return { eventId: (await e.recorder.recordEvent({
|
|
1285
1295
|
runId: t.id,
|
|
@@ -1289,8 +1299,8 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1289
1299
|
value: e.payload
|
|
1290
1300
|
}
|
|
1291
1301
|
})).id };
|
|
1292
|
-
},
|
|
1293
|
-
let t = await Y(e.store, e.target), n =
|
|
1302
|
+
}, dt = async (e) => {
|
|
1303
|
+
let t = await Y(e.store, e.target), n = lt(), r = await e.recorder.recordEvent({
|
|
1294
1304
|
runId: t.id,
|
|
1295
1305
|
eventType: "update-requested",
|
|
1296
1306
|
payload: {
|
|
@@ -1327,15 +1337,15 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1327
1337
|
}), t;
|
|
1328
1338
|
}
|
|
1329
1339
|
if (Date.now() - i >= a) throw Error(`Timed out waiting for workflow update '${e.updateName}' (${n})`);
|
|
1330
|
-
await
|
|
1340
|
+
await st(o);
|
|
1331
1341
|
}
|
|
1332
|
-
},
|
|
1342
|
+
}, ft = 1440 * 6e4, pt = "voltro/suspending-signal", mt = (e, t) => `${pt}/${e}/${t}`, ht = (e) => o.make(e, { success: h.Unknown }), gt = async (e, t) => {
|
|
1333
1343
|
if (e) try {
|
|
1334
1344
|
await e.recordEvent(t);
|
|
1335
1345
|
} catch {}
|
|
1336
|
-
},
|
|
1337
|
-
let e = n.timeoutMs ??
|
|
1338
|
-
s !== void 0 && (yield* u.promise(() =>
|
|
1346
|
+
}, _t = (e, n) => u.gen(function* () {
|
|
1347
|
+
let e = n.timeoutMs ?? ft, i = ht(mt((yield* c.WorkflowInstance).workflow.name, n.name)), s = yield* r, l = yield* u.serviceOption(t), d = l._tag === "Some" ? l.value : void 0;
|
|
1348
|
+
s !== void 0 && (yield* u.promise(() => gt(d, {
|
|
1339
1349
|
runId: s,
|
|
1340
1350
|
eventType: "signal-awaited",
|
|
1341
1351
|
payload: { signalName: n.name }
|
|
@@ -1344,7 +1354,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1344
1354
|
name: `await-signal-timeout/${n.name}`,
|
|
1345
1355
|
duration: `${e} millis`
|
|
1346
1356
|
}).pipe(u.andThen(u.die(/* @__PURE__ */ Error(`awaitSignalSuspending('${n.name}') timed out after ${e}ms`))))) : yield* f, m = yield* h.decodeUnknown(n.schema)(p);
|
|
1347
|
-
return s !== void 0 && (yield* u.promise(() =>
|
|
1357
|
+
return s !== void 0 && (yield* u.promise(() => gt(d, {
|
|
1348
1358
|
runId: s,
|
|
1349
1359
|
eventType: "signal-received",
|
|
1350
1360
|
payload: {
|
|
@@ -1352,7 +1362,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1352
1362
|
value: m
|
|
1353
1363
|
}
|
|
1354
1364
|
}))), m;
|
|
1355
|
-
}),
|
|
1365
|
+
}), vt = (e) => u.gen(function* () {
|
|
1356
1366
|
let t = e.target.executionId, n = e.target.workflowName;
|
|
1357
1367
|
if (t === void 0 || n === void 0) {
|
|
1358
1368
|
let r = e.store;
|
|
@@ -1364,7 +1374,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1364
1374
|
t ??= i.executionId, n ??= i.tag;
|
|
1365
1375
|
}
|
|
1366
1376
|
if (t === void 0 || n === void 0) return { completed: !1 };
|
|
1367
|
-
let r =
|
|
1377
|
+
let r = mt(n, e.signalName), i = ht(r), a = new o.TokenParsed({
|
|
1368
1378
|
workflowName: n,
|
|
1369
1379
|
executionId: t,
|
|
1370
1380
|
deferredName: r
|
|
@@ -1377,13 +1387,13 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1377
1387
|
if (e) try {
|
|
1378
1388
|
await e.recordEvent(t);
|
|
1379
1389
|
} catch {}
|
|
1380
|
-
},
|
|
1390
|
+
}, yt = (e) => {
|
|
1381
1391
|
let t = l.failureOption(e), n = Array.from(l.defects(e)), r = t._tag === "Some" ? t.value : n[0] ?? l.squash(e);
|
|
1382
1392
|
return {
|
|
1383
1393
|
errorTag: r && typeof r == "object" && "_tag" in r ? String(r._tag) : null,
|
|
1384
1394
|
errorMessage: r?.message ?? String(r)
|
|
1385
1395
|
};
|
|
1386
|
-
},
|
|
1396
|
+
}, bt = (e, n) => {
|
|
1387
1397
|
let a = u.gen(function* () {
|
|
1388
1398
|
let i = n.pollIntervalMs ?? 200, a = n.maxPollIntervalMs ?? 5e3, o = n.timeoutMs ?? 3e4, s = yield* r, c = yield* u.serviceOption(t), l = c._tag === "Some" ? c.value : void 0;
|
|
1389
1399
|
if (s === void 0) return yield* u.die(/* @__PURE__ */ Error("awaitUpdate: no active workflow run id in scope"));
|
|
@@ -1421,7 +1431,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1421
1431
|
payload: {
|
|
1422
1432
|
updateId: e,
|
|
1423
1433
|
updateName: n.name,
|
|
1424
|
-
...
|
|
1434
|
+
...yt(t)
|
|
1425
1435
|
}
|
|
1426
1436
|
})))), r = yield* (n.handle ? n.handle(t) : u.succeed(t)).pipe(u.flatMap((e) => n.success ? h.decodeUnknown(n.success)(e) : u.succeed(e))).pipe(u.tapErrorCause((t) => u.promise(() => X(l, {
|
|
1427
1437
|
runId: d,
|
|
@@ -1429,7 +1439,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1429
1439
|
payload: {
|
|
1430
1440
|
updateId: e,
|
|
1431
1441
|
updateName: n.name,
|
|
1432
|
-
...
|
|
1442
|
+
...yt(t)
|
|
1433
1443
|
}
|
|
1434
1444
|
}))));
|
|
1435
1445
|
return yield* u.promise(() => X(l, {
|
|
@@ -1450,10 +1460,10 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1450
1460
|
success: n.success ?? n.schema,
|
|
1451
1461
|
execute: a
|
|
1452
1462
|
});
|
|
1453
|
-
},
|
|
1463
|
+
}, xt = 8 * 1024, St = () => {
|
|
1454
1464
|
let e = globalThis.crypto;
|
|
1455
1465
|
return typeof e?.randomUUID == "function" ? e.randomUUID() : `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`;
|
|
1456
|
-
},
|
|
1466
|
+
}, Ct = (e) => `${e}_${St()}`, Z = (e, t = xt) => {
|
|
1457
1467
|
if (e == null) return e;
|
|
1458
1468
|
try {
|
|
1459
1469
|
let n = JSON.stringify(e);
|
|
@@ -1469,8 +1479,8 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1469
1479
|
let t = {};
|
|
1470
1480
|
for (let [n, r] of Object.entries(e)) t[n] = r instanceof Date ? r.toISOString() : r;
|
|
1471
1481
|
return t;
|
|
1472
|
-
},
|
|
1473
|
-
let t = e.truncateBytes ??
|
|
1482
|
+
}, wt = (e) => {
|
|
1483
|
+
let t = e.truncateBytes ?? xt, n = e.makeId ?? Ct, r = (t, n) => e.log?.warn(t, n), i = e.encryptStepPayload ?? ((e) => e), a = e.emit;
|
|
1474
1484
|
return {
|
|
1475
1485
|
startStep: async ({ runId: o, stepName: s, attempt: c, stepInput: l, retryPolicy: u }) => {
|
|
1476
1486
|
try {
|
|
@@ -1573,8 +1583,8 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1573
1583
|
return { id: l };
|
|
1574
1584
|
}
|
|
1575
1585
|
};
|
|
1576
|
-
},
|
|
1577
|
-
let o = i.truncateBytes ??
|
|
1586
|
+
}, Tt = (i, a) => {
|
|
1587
|
+
let o = i.truncateBytes ?? xt, c = i.makeId ?? Ct, d = i.recorder ?? wt(i), m = (e, t) => i.log?.warn(e, t), h = i.emit, g = (e, t, n) => u.promise(async () => {
|
|
1578
1588
|
if (i.onParentClose) try {
|
|
1579
1589
|
await i.onParentClose({
|
|
1580
1590
|
workflowName: i.name,
|
|
@@ -1791,7 +1801,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1791
1801
|
completedAtSec: Date.now() / 1e3
|
|
1792
1802
|
})), N;
|
|
1793
1803
|
});
|
|
1794
|
-
},
|
|
1804
|
+
}, Et = (e) => e === "cancel" || e === "terminate", Dt = (e) => e === "running" || e === "suspended", Ot = async (e) => {
|
|
1795
1805
|
let t = await e.store.query({
|
|
1796
1806
|
table: "_voltro_workflow_runs",
|
|
1797
1807
|
order: [{
|
|
@@ -1817,7 +1827,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1817
1827
|
});
|
|
1818
1828
|
continue;
|
|
1819
1829
|
}
|
|
1820
|
-
if (!
|
|
1830
|
+
if (!Et(r.parentClosePolicy)) {
|
|
1821
1831
|
n.push({
|
|
1822
1832
|
...t,
|
|
1823
1833
|
action: "skipped",
|
|
@@ -1825,7 +1835,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1825
1835
|
});
|
|
1826
1836
|
continue;
|
|
1827
1837
|
}
|
|
1828
|
-
if (!
|
|
1838
|
+
if (!Dt(r.status)) {
|
|
1829
1839
|
n.push({
|
|
1830
1840
|
...t,
|
|
1831
1841
|
action: "skipped",
|
|
@@ -1873,7 +1883,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1873
1883
|
}
|
|
1874
1884
|
}
|
|
1875
1885
|
return n;
|
|
1876
|
-
},
|
|
1886
|
+
}, kt = (e) => {
|
|
1877
1887
|
if (e == null) return null;
|
|
1878
1888
|
if (e instanceof Date) return e;
|
|
1879
1889
|
if (typeof e == "string" || typeof e == "number") {
|
|
@@ -1881,7 +1891,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1881
1891
|
return Number.isNaN(t.getTime()) ? null : t;
|
|
1882
1892
|
}
|
|
1883
1893
|
return null;
|
|
1884
|
-
},
|
|
1894
|
+
}, At = (e) => typeof e == "number" ? e : e == null ? null : Number(e), $ = (e) => e == null ? null : String(e), jt = (e) => {
|
|
1885
1895
|
let t = /* @__PURE__ */ new Map();
|
|
1886
1896
|
for (let n of e) {
|
|
1887
1897
|
let e = String(n.stepName), r = t.get(e);
|
|
@@ -1889,27 +1899,27 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1889
1899
|
}
|
|
1890
1900
|
let n = [];
|
|
1891
1901
|
for (let [e, r] of t) {
|
|
1892
|
-
r.sort((e, t) => (
|
|
1902
|
+
r.sort((e, t) => (At(e.attempt) ?? 0) - (At(t.attempt) ?? 0));
|
|
1893
1903
|
let t = r[r.length - 1], i = $(t.errorTag), a = $(t.errorMessage), o = i !== null || a !== null ? {
|
|
1894
1904
|
tag: i,
|
|
1895
1905
|
message: a ?? ""
|
|
1896
1906
|
} : null;
|
|
1897
1907
|
n.push({
|
|
1898
1908
|
name: e,
|
|
1899
|
-
attempt:
|
|
1909
|
+
attempt: At(t.attempt) ?? r.length,
|
|
1900
1910
|
attempts: r.length,
|
|
1901
1911
|
status: t.status ?? "running",
|
|
1902
1912
|
input: t.input ?? null,
|
|
1903
1913
|
output: t.output ?? null,
|
|
1904
1914
|
error: o,
|
|
1905
|
-
durationMs:
|
|
1915
|
+
durationMs: At(t.durationMs)
|
|
1906
1916
|
});
|
|
1907
1917
|
}
|
|
1908
1918
|
return n.sort((t, n) => {
|
|
1909
1919
|
let r = e.find((e) => String(e.stepName) === t.name), i = e.find((e) => String(e.stepName) === n.name);
|
|
1910
|
-
return (
|
|
1920
|
+
return (kt(r?.startedAt)?.getTime() ?? 0) - (kt(i?.startedAt)?.getTime() ?? 0);
|
|
1911
1921
|
}), n;
|
|
1912
|
-
},
|
|
1922
|
+
}, Mt = (e) => {
|
|
1913
1923
|
if (e == null) return null;
|
|
1914
1924
|
if (typeof e == "string") try {
|
|
1915
1925
|
return JSON.parse(e);
|
|
@@ -1917,7 +1927,7 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1917
1927
|
return null;
|
|
1918
1928
|
}
|
|
1919
1929
|
return typeof e == "object" ? e : null;
|
|
1920
|
-
},
|
|
1930
|
+
}, Nt = async (e, t) => {
|
|
1921
1931
|
let n = E(M), r = (await t.query(n.where(T(v("id", e), v("executionId", e))).limit(1).descriptor))[0];
|
|
1922
1932
|
if (r === void 0) return null;
|
|
1923
1933
|
let i = String(r.id), a = E(N), o = await t.query(a.where(v("runId", i)).descriptor);
|
|
@@ -1928,16 +1938,16 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1928
1938
|
status: r.status ?? "running",
|
|
1929
1939
|
input: r.payload ?? null,
|
|
1930
1940
|
output: r.output ?? null,
|
|
1931
|
-
subject:
|
|
1941
|
+
subject: Mt(r.subject),
|
|
1932
1942
|
source: $(r.source),
|
|
1933
|
-
steps:
|
|
1934
|
-
startedAt:
|
|
1935
|
-
finishedAt:
|
|
1943
|
+
steps: jt(o),
|
|
1944
|
+
startedAt: kt(r.startedAt) ?? /* @__PURE__ */ new Date(0),
|
|
1945
|
+
finishedAt: kt(r.completedAt),
|
|
1936
1946
|
traceId: $(r.traceId),
|
|
1937
1947
|
parentExecutionId: $(r.parentExecutionId),
|
|
1938
1948
|
parentClosePolicy: $(r.parentClosePolicy)
|
|
1939
1949
|
};
|
|
1940
|
-
},
|
|
1950
|
+
}, Pt = () => {
|
|
1941
1951
|
let e = [], n = [], r = 0, i = 0;
|
|
1942
1952
|
return {
|
|
1943
1953
|
layer: m.succeed(t, {
|
|
@@ -1986,4 +1996,4 @@ var j = c.layerMemory, M = O("_voltro_workflow_runs", {
|
|
|
1986
1996
|
};
|
|
1987
1997
|
};
|
|
1988
1998
|
//#endregion
|
|
1989
|
-
export {
|
|
1999
|
+
export { ce as $, Ie as A, De as B, Ve as C, Ge as D, Je as E, z as F, Oe as G, je as H, L as I, Se as J, we as K, ke as L, Fe as M, R as N, ze as O, ge as P, xe as Q, W as R, Ue as S, We as T, be as U, G as V, _e as W, U as X, ye as Y, Ee as Z, rt as _, Q as a, le as at, K as b, bt as c, se as ct, mt as d, re as dt, he as et, lt as f, N as ft, at as g, dt as h, wt as i, ue as it, Ne as j, Re as k, _t as l, ae as lt, ut as m, j as mt, Nt as n, me as nt, Z as o, F as ot, Y as p, M as pt, Me as q, Ot as r, pe as rt, Tt as s, oe as st, Pt as t, de as tt, vt as u, ie as ut, Xe as v, Be as w, He as x, Ze as y, Ae as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/workflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
4
4
|
"description": "Durable workflows for Voltro — the workflow() descriptor + step() / awaitSignal primitives over @effect/cluster, with a browser-safe define subpath.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
"types": "./dist/clusterTestSuite.d.ts",
|
|
38
38
|
"import": "./dist/clusterTestSuite.js",
|
|
39
39
|
"default": "./dist/clusterTestSuite.js"
|
|
40
|
-
}
|
|
40
|
+
},
|
|
41
|
+
"./package.json": "./package.json"
|
|
41
42
|
},
|
|
42
43
|
"main": "./dist/index.js",
|
|
43
44
|
"module": "./dist/index.js",
|
|
@@ -50,8 +51,8 @@
|
|
|
50
51
|
"@effect/cluster": "^0.60.0",
|
|
51
52
|
"@effect/sql": "^0.52.0",
|
|
52
53
|
"@effect/workflow": "^0.19.0",
|
|
53
|
-
"@voltro/database": "0.
|
|
54
|
-
"@voltro/protocol": "0.
|
|
54
|
+
"@voltro/database": "0.33.0",
|
|
55
|
+
"@voltro/protocol": "0.33.0"
|
|
55
56
|
},
|
|
56
57
|
"peerDependencies": {
|
|
57
58
|
"effect": "^3.22.0",
|