okengine 0.19.2 → 0.19.4
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/package.json +1 -1
- package/site/content/docs/elements/channel/index.mdx +1 -1
- package/site/content/docs/elements/clock/index.mdx +106 -17
- package/site/content/docs/elements/clock/schedules.mdx +39 -11
- package/site/content/docs/elements/flow/consumers.mdx +129 -86
- package/site/content/docs/elements/flow/index.mdx +3 -3
- package/site/content/docs/elements/flow/routing.mdx +11 -8
- package/site/content/docs/elements/signal/broadcast.mdx +15 -14
- package/site/content/docs/elements/signal/index.mdx +99 -36
- package/site/content/docs/elements/signal/live.mdx +5 -13
- package/site/content/docs/elements/signal/once.mdx +88 -28
- package/site/content/docs/reference/errors.mdx +41 -27
- package/site/content/docs/understand/the-architecture.mdx +2 -2
- package/src/cli/competitor-mention-removal.test.ts +3 -3
- package/src/compiler/extract.test.ts +237 -15
- package/src/compiler/extract.ts +75 -4
- package/src/compiler/flow-path.test.ts +23 -0
- package/src/compiler/flow-path.ts +17 -1
- package/src/console/ui-next/dist/assets/{access-page-DceEWH9u.js → access-page-rO2HLPab.js} +1 -1
- package/src/console/ui-next/dist/assets/{agent-disclosure-tC9s2VFd.js → agent-disclosure-ae8w8JLx.js} +1 -1
- package/src/console/ui-next/dist/assets/{cache-glyph-C-naNQSR.js → cache-glyph-C-uHdh5d.js} +1 -1
- package/src/console/ui-next/dist/assets/{call-pii-button-DnZ_MlDn.js → call-pii-button-BJH54w4s.js} +1 -1
- package/src/console/ui-next/dist/assets/{collapsible-RekgR6Qz.js → collapsible-CaE8cs9p.js} +1 -1
- package/src/console/ui-next/dist/assets/{duration-tone-DugtWBS0.js → duration-tone-DdOQkQVK.js} +1 -1
- package/src/console/ui-next/dist/assets/{flows-page-DVmn1ZuQ.js → flows-page-DWeBszQQ.js} +1 -1
- package/src/console/ui-next/dist/assets/{highlighted-json-CvBGaiSD.js → highlighted-json-B2gBsC4B.js} +1 -1
- package/src/console/ui-next/dist/assets/{http-method-D7_OXbdC.js → http-method-BnMl0dJt.js} +1 -1
- package/src/console/ui-next/dist/assets/{index-DH0K2f6N.js → index-CBAP48v5.js} +3 -3
- package/src/console/ui-next/dist/assets/index-VxoEz295.css +2 -0
- package/src/console/ui-next/dist/assets/{observability-page-qJzF2nSN.js → observability-page-DVpcJEnW.js} +1 -1
- package/src/console/ui-next/dist/assets/{replica-lag-Vqk0pUBA.js → replica-lag-BUQf2Nf6.js} +1 -1
- package/src/console/ui-next/dist/assets/{request-meta-BtShi4sG.js → request-meta-CPkGDQgT.js} +1 -1
- package/src/console/ui-next/dist/assets/{store-page-CMsYH_vH.js → store-page-CwS_cY4V.js} +1 -1
- package/src/console/ui-next/dist/assets/{trace-detail-sheet-ycFB2uua.js → trace-detail-sheet-DNX7Y2bd.js} +1 -1
- package/src/console/ui-next/dist/assets/{tree-expand-toggle-iG1jcWgU.js → tree-expand-toggle-Bhdk27aF.js} +1 -1
- package/src/console/ui-next/dist/assets/{units-page-Bavchke4.js → units-page-CsBh0XbI.js} +1 -1
- package/src/console/ui-next/dist/assets/{vault-page-DxUFhiZI.js → vault-page-CsTlPtK9.js} +1 -1
- package/src/console/ui-next/dist/index.html +2 -2
- package/src/kernel/app.ts +87 -1
- package/src/kernel/errors-flow-name.ts +20 -4
- package/src/kernel/errors-once-signal.ts +25 -0
- package/src/kernel/errors.registry.test.ts +12 -2
- package/src/kernel/flow-name.test.ts +105 -20
- package/src/kernel/flow.ts +3 -3
- package/src/kernel/on.ts +0 -5
- package/src/kernel/once-signal.test.ts +71 -0
- package/src/kernel/stamp-http.test.ts +2 -1
- package/src/kernel/stamp-http.ts +5 -7
- package/src/kernel/unit.ts +1 -2
- package/src/console/ui-next/dist/assets/index-D0zS5rKO.css +0 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "okengine",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.4",
|
|
4
4
|
"description": "One law. Eight elements. One contract. The backend model stays small; operational surfaces are derived from it instead of maintained separately.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -5,7 +5,7 @@ icon: "Clock"
|
|
|
5
5
|
source: "docs/spec/unified-theory.md"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
Clock is how your backend **knows what time it is and when to run again**. A monthly invoice job, a 30s health ping, and a three-day trial reminder share one vocabulary: declare a named schedule, bind it with `on(clockDecl, flow)`, and read time only through `fx.clock`.
|
|
8
|
+
Clock is how your backend **knows what time it is and when to run again**. A monthly invoice job, a 30s health ping, and a three-day trial reminder share one vocabulary: declare a named schedule, bind it with `on(clockDecl, flow("name", { do }))`, and read time only through `fx.clock`.
|
|
9
9
|
|
|
10
10
|
For developers scheduling work on okengine — one handle shape; drivers swap by environment.
|
|
11
11
|
|
|
@@ -58,9 +58,8 @@ export const digest = on(
|
|
|
58
58
|
### See it tick
|
|
59
59
|
|
|
60
60
|
With `oke dev`, the scheduler reconciles `notes.digest` into the Store and leader-elects
|
|
61
|
-
before each fire. `do` receives no payload — read time with `fx.clock.now()` (epoch-ms
|
|
62
|
-
|
|
63
|
-
(`new Date(fx.clock.now()).toISOString()`).
|
|
61
|
+
before each fire. `do` receives no payload — read time with `fx.clock.now()` (epoch-ms).
|
|
62
|
+
Map to ISO on the wire with `new Date(fx.clock.now()).toISOString()`.
|
|
64
63
|
|
|
65
64
|
Under `drivers.clock.test = "frozen"`, advance time in tests instead of waiting a day.
|
|
66
65
|
|
|
@@ -69,19 +68,88 @@ Under `drivers.clock.test = "frozen"`, advance time in tests instead of waiting
|
|
|
69
68
|
</Steps>
|
|
70
69
|
|
|
71
70
|
<Callout title="Jobs are Flows">
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
Write the schedule inline or export it — [Inline or named export](#inline-or-named-export).
|
|
72
|
+
Every consumer still uses `flow("name", { do })`. See
|
|
73
|
+
[Consumers · Clock Jobs](/docs/elements/flow/consumers#clock-jobs).
|
|
74
74
|
</Callout>
|
|
75
75
|
|
|
76
76
|
## Inline or named export
|
|
77
77
|
|
|
78
|
-
| Style
|
|
79
|
-
|
|
|
80
|
-
| `on(clock.every("name", "1h"), flow({ do }))` |
|
|
81
|
-
| `export const x = clock.every("name", "1h")`
|
|
78
|
+
| Style | When |
|
|
79
|
+
| --------------------------------------------------------- | --------------------------------- |
|
|
80
|
+
| `on(clock.every("name", "1h"), flow("ops.ping", { do }))` | One Flow owns this schedule |
|
|
81
|
+
| `export const x = clock.every("name", "1h")` | Several Flows share this schedule |
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
`
|
|
83
|
+
<Callout title="Why Clock can be inline">
|
|
84
|
+
`fx.clock` is now / sleep / offsets — not an emit. Signal stays an exported const so producers can
|
|
85
|
+
`fx.emit(handle, payload)`. Export a Clock const only to share one schedule across named Flows.
|
|
86
|
+
</Callout>
|
|
87
|
+
|
|
88
|
+
Both styles pass a real `flow("name", { do })`. Nameless `flow({ do })` fails
|
|
89
|
+
**OKE1072** — the trigger's name is never the Flow's name.
|
|
90
|
+
|
|
91
|
+
<Tabs items={["Inline", "Named"]}>
|
|
92
|
+
|
|
93
|
+
<Tab value="Inline">
|
|
94
|
+
|
|
95
|
+
One file — declare and bind together. The scheduler fires it; no other Flow
|
|
96
|
+
binds this cadence:
|
|
97
|
+
|
|
98
|
+
```typescript title="src/flows/health/ping.ts"
|
|
99
|
+
import { on, flow, clock } from "okengine";
|
|
100
|
+
|
|
101
|
+
export const pingExternal = on(
|
|
102
|
+
clock.every("health.pingExternal", "30s"),
|
|
103
|
+
flow("health.pingExternal", {
|
|
104
|
+
plane: "operator",
|
|
105
|
+
do: async (_, fx) => {
|
|
106
|
+
await fx.call(pingUpstream);
|
|
107
|
+
},
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
</Tab>
|
|
113
|
+
|
|
114
|
+
<Tab value="Named">
|
|
115
|
+
|
|
116
|
+
Export the handle when a second `on()` must reuse the same schedule. Each Flow
|
|
117
|
+
keeps its own explicit name — **OKE1070** if they collide:
|
|
118
|
+
|
|
119
|
+
```typescript title="src/clocks/metrics.ts"
|
|
120
|
+
import { clock } from "okengine";
|
|
121
|
+
|
|
122
|
+
export const tickClock = clock.every("metrics.tick", "1h");
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```typescript title="src/flows/ops/metrics.ts"
|
|
126
|
+
import { on, flow } from "okengine";
|
|
127
|
+
import { tickClock } from "@/clocks/metrics";
|
|
128
|
+
|
|
129
|
+
export const sweep = on(
|
|
130
|
+
tickClock,
|
|
131
|
+
flow("ops.sweep", {
|
|
132
|
+
plane: "operator",
|
|
133
|
+
do: async (_, fx) => {
|
|
134
|
+
await fx.call(sweepMetrics);
|
|
135
|
+
},
|
|
136
|
+
}),
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
export const report = on(
|
|
140
|
+
tickClock,
|
|
141
|
+
flow("ops.report", {
|
|
142
|
+
plane: "operator",
|
|
143
|
+
do: async (_, fx) => {
|
|
144
|
+
await fx.call(reportMetrics);
|
|
145
|
+
},
|
|
146
|
+
}),
|
|
147
|
+
);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
</Tab>
|
|
151
|
+
|
|
152
|
+
</Tabs>
|
|
85
153
|
|
|
86
154
|
## Progressive Patterns
|
|
87
155
|
|
|
@@ -93,12 +161,17 @@ Same `clock` + `fx.clock` from a calendar cron to an interval, a durable pause,
|
|
|
93
161
|
|
|
94
162
|
Five-field cron plus helpers. Prefer an app-wide zone so schedules stay short:
|
|
95
163
|
|
|
96
|
-
```typescript title="src/
|
|
97
|
-
import {
|
|
164
|
+
```typescript title="src/clocks/reports.ts"
|
|
165
|
+
import { clock } from "okengine";
|
|
98
166
|
|
|
99
167
|
export const dailyReportClock = clock.daily("reports.daily", {
|
|
100
168
|
at: "06:00",
|
|
101
169
|
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
```typescript title="src/flows/reports/daily.ts"
|
|
173
|
+
import { on, flow } from "okengine";
|
|
174
|
+
import { dailyReportClock } from "@/clocks/reports";
|
|
102
175
|
|
|
103
176
|
export const runDailyReport = on(
|
|
104
177
|
dailyReportClock,
|
|
@@ -121,10 +194,15 @@ see [Schedules](/docs/elements/clock/schedules).
|
|
|
121
194
|
|
|
122
195
|
Human durations — integer + unit, no weeks: `"200ms"` · `"30s"` · `"5m"` · `"1h"` · `"7d"`:
|
|
123
196
|
|
|
124
|
-
```typescript title="src/
|
|
125
|
-
import {
|
|
197
|
+
```typescript title="src/clocks/health.ts"
|
|
198
|
+
import { clock } from "okengine";
|
|
126
199
|
|
|
127
200
|
export const pingClock = clock.every("health.pingExternal", "30s");
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
```typescript title="src/flows/health/ping.ts"
|
|
204
|
+
import { on, flow } from "okengine";
|
|
205
|
+
import { pingClock } from "@/clocks/health";
|
|
128
206
|
|
|
129
207
|
export const pingExternal = on(
|
|
130
208
|
pingClock,
|
|
@@ -279,6 +357,17 @@ export default defineConfig({
|
|
|
279
357
|
declaration in source.
|
|
280
358
|
</Accordion>
|
|
281
359
|
|
|
360
|
+
<Accordion title="OKE1070 — flow name defined twice">
|
|
361
|
+
Cause: `Flow "{flow}" is defined twice.` Two `flow("…")` strings collide. Give at least one a
|
|
362
|
+
distinct name. Clock allows several consumers on one schedule — each needs its own name.
|
|
363
|
+
</Accordion>
|
|
364
|
+
|
|
365
|
+
<Accordion title="OKE1072 — Clock flow unnamed">
|
|
366
|
+
Cause: `A clock flow on "{trigger}" has no name.`
|
|
367
|
+
Fix: pass an explicit name — `on(clockDecl, flow("notes.digest", { do }))`.
|
|
368
|
+
Inline `clock.every` still needs `flow("…")` — [Inline or named export](#inline-or-named-export).
|
|
369
|
+
</Accordion>
|
|
370
|
+
|
|
282
371
|
</Accordions>
|
|
283
372
|
|
|
284
373
|
## Learn more
|
|
@@ -288,7 +377,7 @@ export default defineConfig({
|
|
|
288
377
|
- [Consumers · Clock Jobs](/docs/elements/flow/consumers#clock-jobs) — bind with `on(clockDecl, flow)`
|
|
289
378
|
- [Workflows](/docs/elements/flow/workflows) — `durable: true` + `fx.step` around sleeps
|
|
290
379
|
- [fx](/docs/reference/fx) — full `fx.clock` table
|
|
291
|
-
- [Errors](/docs/reference/errors) — `ScheduleNotOverridableError` · `ClockResourceNotFoundError`
|
|
380
|
+
- [Errors](/docs/reference/errors) — `ScheduleNotOverridableError` · `ClockResourceNotFoundError` · OKE1070 · OKE1072
|
|
292
381
|
|
|
293
382
|
## Next
|
|
294
383
|
|
|
@@ -25,10 +25,15 @@ For developers running daily digests, health pings, and per-tenant billing loops
|
|
|
25
25
|
<Step>
|
|
26
26
|
### Declare a schedule and bind a Flow
|
|
27
27
|
|
|
28
|
-
```typescript title="src/
|
|
29
|
-
import {
|
|
28
|
+
```typescript title="src/clocks/reports.ts"
|
|
29
|
+
import { clock } from "okengine";
|
|
30
30
|
|
|
31
31
|
export const dailyReportClock = clock.daily("reports.daily", { at: "06:00" });
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```typescript title="src/flows/reports/daily.ts"
|
|
35
|
+
import { on, flow } from "okengine";
|
|
36
|
+
import { dailyReportClock } from "@/clocks/reports";
|
|
32
37
|
|
|
33
38
|
export const runDailyReport = on(
|
|
34
39
|
dailyReportClock,
|
|
@@ -112,10 +117,15 @@ chosen programmatically: `clock("x", { cron: "0 6 * * *" })`, Bun nicknames
|
|
|
112
117
|
|
|
113
118
|
Fixed duration loops — `"200ms"` · `"30s"` · `"5m"` · `"1h"` · `"7d"`:
|
|
114
119
|
|
|
115
|
-
```typescript title="src/
|
|
116
|
-
import {
|
|
120
|
+
```typescript title="src/clocks/health.ts"
|
|
121
|
+
import { clock } from "okengine";
|
|
117
122
|
|
|
118
123
|
export const pingClock = clock.every("health.pingExternal", "30s");
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```typescript title="src/flows/health/ping.ts"
|
|
127
|
+
import { on, flow } from "okengine";
|
|
128
|
+
import { pingClock } from "@/clocks/health";
|
|
119
129
|
|
|
120
130
|
export const pingExternal = on(
|
|
121
131
|
pingClock,
|
|
@@ -146,9 +156,14 @@ Unknown duration strings parse as `0` ms and never become due. A `"d"` is exactl
|
|
|
146
156
|
template name is never ticked:
|
|
147
157
|
|
|
148
158
|
```typescript title="src/clocks/invoices.ts"
|
|
149
|
-
import {
|
|
159
|
+
import { clock } from "okengine";
|
|
150
160
|
|
|
151
161
|
export const invoicesClock = clock.perTenant("invoices", { every: "1h" });
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
```typescript title="src/flows/billing/invoices.ts"
|
|
165
|
+
import { on, flow } from "okengine";
|
|
166
|
+
import { invoicesClock } from "@/clocks/invoices";
|
|
152
167
|
|
|
153
168
|
export const runInvoices = on(
|
|
154
169
|
invoicesClock,
|
|
@@ -275,16 +290,22 @@ do not consult the zone for tick spacing; the zone still lands on the Store row.
|
|
|
275
290
|
|
|
276
291
|
## Binding & Input
|
|
277
292
|
|
|
278
|
-
Bind
|
|
279
|
-
|
|
293
|
+
Bind with `on(clockDecl, flow("name", { do }))`. One Flow can write `clock.every`
|
|
294
|
+
inside `on()` — [Clock · Inline or named export](/docs/elements/clock#inline-or-named-export).
|
|
295
|
+
`do` has no payload; read time through `fx.clock`:
|
|
296
|
+
|
|
297
|
+
```typescript title="src/clocks/metrics.ts"
|
|
298
|
+
import { clock } from "okengine";
|
|
299
|
+
|
|
300
|
+
export const cleanupClock = clock.every("metrics.cleanup", "1h");
|
|
301
|
+
```
|
|
280
302
|
|
|
281
303
|
```typescript title="src/flows/metrics/cleanup.ts"
|
|
282
|
-
import { on, flow
|
|
304
|
+
import { on, flow } from "okengine";
|
|
283
305
|
import { lt } from "drizzle-orm";
|
|
306
|
+
import { cleanupClock } from "@/clocks/metrics";
|
|
284
307
|
import { db, metricLogs } from "@/schema";
|
|
285
308
|
|
|
286
|
-
export const cleanupClock = clock.every("metrics.cleanup", "1h");
|
|
287
|
-
|
|
288
309
|
export const cleanup = on(
|
|
289
310
|
cleanupClock,
|
|
290
311
|
flow("metrics.cleanup", {
|
|
@@ -470,6 +491,12 @@ forms; on overlap days crontab fires **once** (first occurrence).
|
|
|
470
491
|
that is not in the reconciled Store — check spelling against your `clock()` declarations.
|
|
471
492
|
</Accordion>
|
|
472
493
|
|
|
494
|
+
<Accordion title="OKE1072 — Clock flow unnamed">
|
|
495
|
+
Cause: `A clock flow on "{trigger}" has no name.`
|
|
496
|
+
Fix: pass an explicit name — `on(clockDecl, flow("reports.runDaily", { do }))`.
|
|
497
|
+
Inline `clock.every` still needs `flow("…")` — [Clock · Inline or named export](/docs/elements/clock#inline-or-named-export).
|
|
498
|
+
</Accordion>
|
|
499
|
+
|
|
473
500
|
<Accordion title="DST gap / overlap warning on the row">
|
|
474
501
|
Informational. Pick a UTC cron, a non-ambiguous local hour, or accept the warning. The job still
|
|
475
502
|
schedules.
|
|
@@ -485,10 +512,11 @@ forms; on overlap days crontab fires **once** (first occurrence).
|
|
|
485
512
|
## Learn more
|
|
486
513
|
|
|
487
514
|
- [Clock overview](/docs/elements/clock) — `fx.clock` helpers and drivers
|
|
515
|
+
- [Clock · Inline or named export](/docs/elements/clock#inline-or-named-export) — one Flow vs shared schedule
|
|
488
516
|
- [Durable Sleep](/docs/elements/clock/sleep) — `fx.clock.sleep(label, duration)`
|
|
489
517
|
- [Consumers · Clock Jobs](/docs/elements/flow/consumers#clock-jobs) — bind with `on(clockDecl, flow)`
|
|
490
518
|
- [fx](/docs/reference/fx) — `fx.clock.now` / `ago` / `fromNow` / `duration`
|
|
491
|
-
- [Errors](/docs/reference/errors) — `ScheduleNotOverridableError` · `ClockResourceNotFoundError`
|
|
519
|
+
- [Errors](/docs/reference/errors) — `ScheduleNotOverridableError` · `ClockResourceNotFoundError` · OKE1072
|
|
492
520
|
|
|
493
521
|
## Next
|
|
494
522
|
|