okengine 0.10.1 → 0.10.3
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/README.md +1 -1
- package/package.json +1 -1
- package/site/content/docs/ai/mcp.mdx +0 -1
- package/site/content/docs/elements/ai.mdx +0 -6
- package/site/content/docs/elements/channel.mdx +0 -6
- package/site/content/docs/elements/clock.mdx +2 -8
- package/site/content/docs/elements/flow.mdx +10 -17
- package/site/content/docs/elements/gate.mdx +4 -10
- package/site/content/docs/elements/signal.mdx +4 -13
- package/site/content/docs/elements/store.mdx +9 -16
- package/site/content/docs/elements/vault.mdx +1 -7
- package/site/content/docs/get-started/basic-usage.mdx +1 -9
- package/site/content/docs/get-started/introduction.mdx +1 -1
- package/site/content/docs/get-started/why.mdx +1 -2
- package/site/content/docs/index.mdx +0 -1
- package/site/content/docs/meta.json +0 -1
- package/site/content/docs/reference/cli.md +12 -12
- package/site/content/docs/reference/fx.mdx +6 -7
- package/src/auth/auth.test.ts +1 -2
- package/src/auth/bindings.ts +5 -15
- package/src/auth/gate-auth.test.ts +2 -2
- package/src/cli/ai-setup/ai-setup.test.ts +3 -1
- package/src/cli/ai-setup/apply.ts +13 -0
- package/src/cli/build.test.ts +67 -0
- package/src/cli/build.ts +33 -0
- package/src/cli/db-seed.ts +1 -1
- package/src/cli/dev-controls.test.ts +43 -48
- package/src/cli/dev-controls.ts +23 -172
- package/src/cli/dev.test.ts +90 -5
- package/src/cli/dev.ts +39 -16
- package/src/cli/doctor.ts +98 -5
- package/src/cli/load-config.images.test.ts +31 -1
- package/src/cli/load-config.ts +2 -2
- package/src/cli/safe-defaults.test.ts +1 -1
- package/src/client/notes-contract.test.ts +3 -4
- package/src/compiler/aot.test.ts +1 -2
- package/src/compiler/extract.test.ts +76 -21
- package/src/compiler/extract.ts +15 -3
- package/src/compiler/fixtures/raw/raw-unannotated.ts +2 -4
- package/src/compiler/fixtures/skyport/oke.config.ts +4 -2
- package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +4 -8
- package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +1 -2
- package/src/compiler/fixtures/skyport/src/flows/support/index.ts +1 -2
- package/src/compiler/fixtures/skyport.expected.json +3 -3
- package/src/compiler/fixtures/triggers/five-triggers.ts +5 -10
- package/src/compiler/generate-adopt.test.ts +85 -0
- package/src/compiler/generate-adopt.ts +111 -0
- package/src/config/driver-defaults.test.ts +122 -0
- package/src/config/driver-defaults.ts +186 -0
- package/src/config/index.ts +97 -7
- package/src/console/server/dry-run.audit.test.ts +2 -2
- package/src/console/server/flows.ts +50 -150
- package/src/docker/compose.ts +13 -3
- package/src/docker/images-config.test.ts +168 -0
- package/src/docker/recipes/llama-cpp.ts +10 -1
- package/src/docker/stack-id.ts +1 -0
- package/src/elements/channel/declare.ts +27 -1
- package/src/elements/clock/chaos-child.ts +3 -6
- package/src/elements/clock/chaos.test.ts +1 -2
- package/src/elements/clock.test.ts +2 -4
- package/src/elements/signal/declare.ts +25 -1
- package/src/elements/store/declare.ts +27 -1
- package/src/elements/store/resource.ts +6 -11
- package/src/elements/store/upsert-app.test.ts +1 -2
- package/src/elements/vault/declare.ts +29 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +103 -0
- package/src/kernel/app.ts +66 -9
- package/src/kernel/auto-registry.test.ts +198 -0
- package/src/kernel/boot-bind/channel.ts +12 -4
- package/src/kernel/boot-bind/clock.ts +3 -3
- package/src/kernel/boot-bind/gate.ts +7 -4
- package/src/kernel/boot-bind/honor-config.test.ts +5 -5
- package/src/kernel/boot-bind/journal.ts +3 -3
- package/src/kernel/boot-bind/signal.ts +3 -1
- package/src/kernel/boot-bind/store.test.ts +19 -0
- package/src/kernel/boot-bind/store.ts +24 -9
- package/src/kernel/boot-bind/vault.ts +3 -3
- package/src/kernel/boot.test.ts +19 -15
- package/src/kernel/boot.ts +81 -1
- package/src/kernel/call.test.ts +5 -10
- package/src/kernel/compensate.test.ts +3 -6
- package/src/kernel/concurrency.test.ts +3 -5
- package/src/kernel/correlation.test.ts +4 -8
- package/src/kernel/edge.test.ts +1 -1
- package/src/kernel/effects-stamping.test.ts +4 -7
- package/src/kernel/element-registries.ts +27 -0
- package/src/kernel/errors.ts +11 -0
- package/src/kernel/flow.test.ts +8 -15
- package/src/kernel/flow.ts +12 -14
- package/src/kernel/hook-timing.test.ts +2 -2
- package/src/kernel/hooks.test.ts +5 -10
- package/src/kernel/horizontal-child.ts +5 -10
- package/src/kernel/journal-boot.test.ts +2 -4
- package/src/kernel/pipeline.test.ts +6 -12
- package/src/kernel/plugin/capabilities.test.ts +1 -1
- package/src/kernel/plugin/decorate.test.ts +3 -8
- package/src/kernel/plugin/scoping.test.ts +6 -16
- package/src/kernel/plugin-elements.test.ts +1 -1
- package/src/kernel/plugin-needs.test.ts +1 -1
- package/src/kernel/ready.test.ts +1 -2
- package/src/kernel/registry-isolation.test.ts +5 -5
- package/src/kernel/triggers.ts +1 -1
- package/src/plugins/anonymous.ts +1 -3
- package/src/plugins/compression.test.ts +6 -8
- package/src/plugins/config-source.test.ts +1 -1
- package/src/plugins/config-source.ts +1 -2
- package/src/plugins/cors.test.ts +10 -10
- package/src/plugins/csrf.test.ts +2 -2
- package/src/plugins/headers.test.ts +14 -15
- package/src/plugins/ip-allowlist.test.ts +9 -9
- package/src/plugins/magic-link.ts +2 -6
- package/src/plugins/maintenance-mode.test.ts +6 -6
- package/src/plugins/otp.ts +5 -15
- package/src/plugins/passkey.ts +4 -12
- package/src/plugins/two-factor.ts +3 -9
- package/src/plugins/username.ts +2 -6
- package/src/release/measure.ts +1 -1
- package/src/runs/runs.test.ts +3 -5
- package/src/runtime/serve.test.ts +2 -4
- package/src/test/create-test-app.test.ts +3 -7
- package/src/test/provisions.integration.test.ts +4 -9
- package/src/test/reset-element-registries.ts +31 -0
- package/site/content/docs/console/access.mdx +0 -29
- package/site/content/docs/console/ai.mdx +0 -35
- package/site/content/docs/console/architecture.mdx +0 -35
- package/site/content/docs/console/channels.mdx +0 -37
- package/site/content/docs/console/clock.mdx +0 -51
- package/site/content/docs/console/flows.mdx +0 -31
- package/site/content/docs/console/gates.mdx +0 -73
- package/site/content/docs/console/index.mdx +0 -54
- package/site/content/docs/console/manifest-diff.mdx +0 -34
- package/site/content/docs/console/meta.json +0 -24
- package/site/content/docs/console/overview.mdx +0 -40
- package/site/content/docs/console/plugins.mdx +0 -41
- package/site/content/docs/console/privacy.mdx +0 -32
- package/site/content/docs/console/runs.mdx +0 -50
- package/site/content/docs/console/signals.mdx +0 -40
- package/site/content/docs/console/store.mdx +0 -32
- package/site/content/docs/console/tenancy.mdx +0 -32
- package/site/content/docs/console/traces.mdx +0 -38
- package/site/content/docs/console/vault.mdx +0 -41
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -98,7 +98,6 @@ The docs content ships inside the `okengine` package, so the index your agent se
|
|
|
98
98
|
|
|
99
99
|
## Learn more
|
|
100
100
|
|
|
101
|
-
- [Console · Access](/docs/console/access) — how operator tokens and grants work
|
|
102
101
|
- [Agent contracts](/docs/ai/skills) — what agents are taught about the system they operate
|
|
103
102
|
- [Flow](/docs/elements/flow) — the effects the MCP reads back
|
|
104
103
|
|
|
@@ -289,7 +289,6 @@ Bound it at declaration: `maxSteps` caps iterations, `budget.maxCostPerRun` caps
|
|
|
289
289
|
|
|
290
290
|
- [Flow](/docs/elements/flow) — `fx.ask` and `fx.search` inside `do`
|
|
291
291
|
- [Store](/docs/elements/store) — `store.index`, the home of embeddings
|
|
292
|
-
- [Console · AI](/docs/console/ai) — prompts, versions, cost per run
|
|
293
292
|
|
|
294
293
|
## Next
|
|
295
294
|
|
|
@@ -304,9 +303,4 @@ Bound it at declaration: `maxSteps` caps iterations, `budget.maxCostPerRun` caps
|
|
|
304
303
|
description="Eight elements overview."
|
|
305
304
|
href="/docs/get-started/introduction"
|
|
306
305
|
/>
|
|
307
|
-
<Card
|
|
308
|
-
title="Console"
|
|
309
|
-
description="Panels derived from the Manifest."
|
|
310
|
-
href="/docs/console/overview"
|
|
311
|
-
/>
|
|
312
306
|
</Cards>
|
|
@@ -326,7 +326,6 @@ Confirm a visible OS notification titled `okengine webpush interop`.
|
|
|
326
326
|
## Learn more
|
|
327
327
|
|
|
328
328
|
- [Flow](/docs/elements/flow) — `fx.send` inside `do`
|
|
329
|
-
- [Console · Channels](/docs/console/channels) — receipts, attempts, suppression
|
|
330
329
|
- [Environment Variables](/docs/reference/environment-variables) — Channel boot credentials
|
|
331
330
|
- [Configuration](/docs/reference/configuration) — `drivers.channel.*` maps
|
|
332
331
|
- [i18n](/docs/reference/i18n) — ICU / `fx.t` vs Channel `{{field}}` catalogs
|
|
@@ -341,9 +340,4 @@ Confirm a visible OS notification titled `okengine webpush interop`.
|
|
|
341
340
|
description="Eight elements overview."
|
|
342
341
|
href="/docs/get-started/introduction"
|
|
343
342
|
/>
|
|
344
|
-
<Card
|
|
345
|
-
title="Console"
|
|
346
|
-
description="Panels derived from the Manifest."
|
|
347
|
-
href="/docs/console/overview"
|
|
348
|
-
/>
|
|
349
343
|
</Cards>
|
|
@@ -26,7 +26,7 @@ import { on, flow, every } from "okengine";
|
|
|
26
26
|
|
|
27
27
|
export const purgeOld = on(
|
|
28
28
|
every("1h"),
|
|
29
|
-
flow({
|
|
29
|
+
flow("links.purgeOld", {
|
|
30
30
|
do: async (_, fx) => {
|
|
31
31
|
const cutoff = fx.clock.now() - 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
32
32
|
await fx.store(db).delete(links).where(lt(links.createdAt, cutoff));
|
|
@@ -55,7 +55,7 @@ export const dailyReport = clock("daily-report", {
|
|
|
55
55
|
```typescript title="src/flows/reports/daily.ts"
|
|
56
56
|
export const sendDaily = on(
|
|
57
57
|
dailyReport,
|
|
58
|
-
flow({
|
|
58
|
+
flow("reports.sendDaily", {
|
|
59
59
|
do: async (_, fx) => {
|
|
60
60
|
/* … */
|
|
61
61
|
},
|
|
@@ -195,7 +195,6 @@ They are not sharing one CronStore. In-memory leases are per process. Point ever
|
|
|
195
195
|
|
|
196
196
|
- [Flow](/docs/elements/flow) — `on(trigger, flow)` and the `fx` surface
|
|
197
197
|
- [fx · Runs](/docs/reference/fx#runs-observability-read) — native SLO checkers via `on(every(…))` + `fx.runs`
|
|
198
|
-
- [Console · Clock](/docs/console/clock) — health numbers, pause / edit / wake early
|
|
199
198
|
- [Signal](/docs/elements/signal) — reacting to events instead of time
|
|
200
199
|
|
|
201
200
|
## Next
|
|
@@ -207,9 +206,4 @@ They are not sharing one CronStore. In-memory leases are per process. Point ever
|
|
|
207
206
|
description="Eight elements overview."
|
|
208
207
|
href="/docs/get-started/introduction"
|
|
209
208
|
/>
|
|
210
|
-
<Card
|
|
211
|
-
title="Console"
|
|
212
|
-
description="Panels derived from the Manifest."
|
|
213
|
-
href="/docs/console/overview"
|
|
214
|
-
/>
|
|
215
209
|
</Cards>
|
|
@@ -28,7 +28,7 @@ import { z } from "zod";
|
|
|
28
28
|
|
|
29
29
|
export const createOrder = on(
|
|
30
30
|
http.post("/orders"),
|
|
31
|
-
flow({
|
|
31
|
+
flow("orders.create", {
|
|
32
32
|
in: z.object({ sku: z.string(), qty: z.number().int().min(1) }),
|
|
33
33
|
out: z.object({ id: z.string() }),
|
|
34
34
|
errors: { OutOfStock: z.object({ left: z.number() }) },
|
|
@@ -106,7 +106,7 @@ Built-in codes attach a localized `message` ([Errors](/docs/reference/errors));
|
|
|
106
106
|
```typescript
|
|
107
107
|
export const findOrder = on(
|
|
108
108
|
http.get("/orders/:id").gate(member), // gates evaluate before do runs
|
|
109
|
-
flow({
|
|
109
|
+
flow("orders.find", {
|
|
110
110
|
in: z.object({ id: z.string() }),
|
|
111
111
|
out: Order,
|
|
112
112
|
errors: { NotFound: z.object({}) },
|
|
@@ -124,7 +124,7 @@ await fx.emit(orderPlaced, { orderId: id }); // inside the producing flow
|
|
|
124
124
|
|
|
125
125
|
on(
|
|
126
126
|
orderPlaced,
|
|
127
|
-
flow({
|
|
127
|
+
flow("orders.sendReceipt", {
|
|
128
128
|
do: async ({ orderId }, fx) => {
|
|
129
129
|
/* … */
|
|
130
130
|
},
|
|
@@ -139,7 +139,7 @@ on(
|
|
|
139
139
|
```typescript
|
|
140
140
|
on(
|
|
141
141
|
every("1h"),
|
|
142
|
-
flow({
|
|
142
|
+
flow("sessions.sweepExpired", {
|
|
143
143
|
do: async (_, fx) => {
|
|
144
144
|
const cutoff = fx.clock.now() - 30 * 24 * 60 * 60 * 1000;
|
|
145
145
|
await fx.store(db).delete(sessions).where(lt(sessions.createdAt, cutoff));
|
|
@@ -155,7 +155,7 @@ CDC is built in; the flow receives `{ before, after }`:
|
|
|
155
155
|
```typescript
|
|
156
156
|
on(
|
|
157
157
|
db.table(orders).changed("status"),
|
|
158
|
-
flow({
|
|
158
|
+
flow("orders.reverify", {
|
|
159
159
|
do: ({ before, after }, fx) => fx.log.info("status", { from: before.status, to: after.status }),
|
|
160
160
|
}),
|
|
161
161
|
);
|
|
@@ -166,7 +166,7 @@ on(
|
|
|
166
166
|
Drop `on()` and it is still a real Flow — contracts, Manifest entry, everything. Other flows call it through `fx.call`:
|
|
167
167
|
|
|
168
168
|
```typescript
|
|
169
|
-
export const getOrder = flow({
|
|
169
|
+
export const getOrder = flow("orders.get", {
|
|
170
170
|
in: OrderRef,
|
|
171
171
|
out: Order,
|
|
172
172
|
do: async ({ id }, fx) => {
|
|
@@ -211,7 +211,7 @@ Everything a flow may touch, on one object:
|
|
|
211
211
|
Set `durable: true` and every `fx` call is journaled. Wrap side effects in `fx.step` and they never re-run on replay:
|
|
212
212
|
|
|
213
213
|
```typescript
|
|
214
|
-
export const chargeOrder = flow({
|
|
214
|
+
export const chargeOrder = flow("payments.chargeOrder", {
|
|
215
215
|
durable: true, // every fx call below is journaled
|
|
216
216
|
in: OrderRef,
|
|
217
217
|
out: z.boolean(),
|
|
@@ -233,14 +233,14 @@ export const chargeOrder = flow({
|
|
|
233
233
|
|
|
234
234
|
Replay is **at-least-once** for a step whose body never persisted (crash mid-step, lease overrun): that step re-runs on reclaim, so keep step bodies short or internally idempotent. A client transport retry starts a _new_ run with a fresh `runId` — request-level idempotency is an application concern, not the journal's.
|
|
235
235
|
|
|
236
|
-
For flaky sub-steps, wrap the work in `fx.retry` **inside** `fx.step` so a completed charge is never retried on resume. Coarse whole-body retry is also available as `flow({ retry: { retries, delay, backoff, jitter } })` and reuses the same journal session.
|
|
236
|
+
For flaky sub-steps, wrap the work in `fx.retry` **inside** `fx.step` so a completed charge is never retried on resume. Coarse whole-body retry is also available as `flow(name, { retry: { retries, delay, backoff, jitter } })` and reuses the same journal session.
|
|
237
237
|
|
|
238
238
|
After a **terminal** failure (`fx.fail` or exhausted retries / uncaught throw), optional
|
|
239
239
|
`compensate` runs under the same journal. Undo work must use **new** step names
|
|
240
240
|
(`undo:…`) — never rewind forward steps:
|
|
241
241
|
|
|
242
242
|
```typescript
|
|
243
|
-
export const chargeOrder = flow({
|
|
243
|
+
export const chargeOrder = flow("payments.chargeOrder", {
|
|
244
244
|
durable: true,
|
|
245
245
|
in: OrderRef,
|
|
246
246
|
out: z.boolean(),
|
|
@@ -295,7 +295,7 @@ Use `fx.all` (or `fx.race`) with thunks. The first rejection aborts siblings thr
|
|
|
295
295
|
</Accordion>
|
|
296
296
|
<Accordion title="Durable flow retried a completed charge">
|
|
297
297
|
|
|
298
|
-
Put `fx.retry` **inside** `fx.step`, not around it. Completed steps replay from the journal and never re-run. `flow({ retry })` is for coarse whole-body retries on the same session.
|
|
298
|
+
Put `fx.retry` **inside** `fx.step`, not around it. Completed steps replay from the journal and never re-run. `flow(name, { retry })` is for coarse whole-body retries on the same session.
|
|
299
299
|
|
|
300
300
|
</Accordion>
|
|
301
301
|
<Accordion title="When does compensate run?">
|
|
@@ -320,8 +320,6 @@ Only that run fails — the schedule keeps firing and the process does not exit.
|
|
|
320
320
|
- [fx](/docs/reference/fx) — full `fx` surface, including ICU `fx.t` / `fx.locale`
|
|
321
321
|
- [Signal](/docs/elements/signal) — delivery physics (`once` · `broadcast` · `live`)
|
|
322
322
|
- [Clock](/docs/elements/clock) — schedules and durable sleep
|
|
323
|
-
- [Console · Flows](/docs/console/flows) — the Manifest-derived panel
|
|
324
|
-
- [Runs](/docs/console/runs) — how a flow execution is observed
|
|
325
323
|
|
|
326
324
|
## Next
|
|
327
325
|
|
|
@@ -332,9 +330,4 @@ Only that run fails — the schedule keeps firing and the process does not exit.
|
|
|
332
330
|
description="Eight elements overview."
|
|
333
331
|
href="/docs/get-started/introduction"
|
|
334
332
|
/>
|
|
335
|
-
<Card
|
|
336
|
-
title="Console"
|
|
337
|
-
description="Panels derived from the Manifest."
|
|
338
|
-
href="/docs/console/overview"
|
|
339
|
-
/>
|
|
340
333
|
</Cards>
|
|
@@ -66,7 +66,7 @@ import { member, canBook, fair } from "../../gates";
|
|
|
66
66
|
|
|
67
67
|
export const health = on(
|
|
68
68
|
http.get("/health").gate(gate.public),
|
|
69
|
-
flow({
|
|
69
|
+
flow("health.check", {
|
|
70
70
|
out: HealthOut,
|
|
71
71
|
do: () => ({ ok: true as const }),
|
|
72
72
|
}),
|
|
@@ -74,7 +74,7 @@ export const health = on(
|
|
|
74
74
|
|
|
75
75
|
export const shorten = on(
|
|
76
76
|
http.post("/links").gate(member, canBook, fair),
|
|
77
|
-
flow({
|
|
77
|
+
flow("links.shorten", {
|
|
78
78
|
in: NewLink,
|
|
79
79
|
out: LinkCode,
|
|
80
80
|
errors: { Taken },
|
|
@@ -174,8 +174,8 @@ Every HTTP trigger must carry a gate or `gate.public`. Omitting both fails boot
|
|
|
174
174
|
`GateBootError` — message starts with `gate boot failed —` and lists every gap.
|
|
175
175
|
|
|
176
176
|
```typescript
|
|
177
|
-
on(http.get("/health").gate(gate.public), flow({/* … */}));
|
|
178
|
-
on(http.post("/bookings").gate(member, canBook), flow({/* … */}));
|
|
177
|
+
on(http.get("/health").gate(gate.public), flow("health.check", {/* … */}));
|
|
178
|
+
on(http.post("/bookings").gate(member, canBook), flow("bookings.create", {/* … */}));
|
|
179
179
|
```
|
|
180
180
|
|
|
181
181
|
**Consequence:** first `fetch` / `execute` auto-boots (`autoBoot` defaults to `true`).
|
|
@@ -308,7 +308,6 @@ Keying an authenticated endpoint by IP punishes shared NAT.
|
|
|
308
308
|
- [fx](/docs/reference/fx) — `fx.auth`, `fx.operator`, `fx.principal`
|
|
309
309
|
- [Client](/docs/reference/client) — Bearer `createClient` + `okengine/client/auth`
|
|
310
310
|
- [Plugins](/docs/plugins) — username, magic link, OTP, TOTP, passkeys
|
|
311
|
-
- [Console · Gates](/docs/console/gates) — decision audit and posture
|
|
312
311
|
- [Vault](/docs/elements/vault) — credentials your policies protect
|
|
313
312
|
|
|
314
313
|
## Next
|
|
@@ -316,9 +315,4 @@ Keying an authenticated endpoint by IP punishes shared NAT.
|
|
|
316
315
|
<Cards>
|
|
317
316
|
<Card title="Vault" description="Continue to Vault." href="/docs/elements/vault" />
|
|
318
317
|
<Card title="Plugins" description="Sign-in method plugins under /auth." href="/docs/plugins" />
|
|
319
|
-
<Card
|
|
320
|
-
title="Console"
|
|
321
|
-
description="Panels derived from the Manifest."
|
|
322
|
-
href="/docs/console/overview"
|
|
323
|
-
/>
|
|
324
318
|
</Cards>
|
|
@@ -58,7 +58,7 @@ A signal is a trigger like any other — the consumer is an ordinary Flow:
|
|
|
58
58
|
```typescript title="src/flows/orders/send-confirmation.ts"
|
|
59
59
|
export const sendConfirmation = on(
|
|
60
60
|
orderPlaced,
|
|
61
|
-
flow({
|
|
61
|
+
flow("orders.sendConfirmation", {
|
|
62
62
|
in: z.object({ orderId: z.string(), total: z.number() }),
|
|
63
63
|
do: async (input, fx) => {
|
|
64
64
|
await fx.send(orderConfirmed, { to: "user-1", data: input });
|
|
@@ -131,8 +131,7 @@ export const orderStatus = signal("order-status", {
|
|
|
131
131
|
```typescript title="src/flows/fulfillment/on-order.ts"
|
|
132
132
|
on(
|
|
133
133
|
orderPlaced,
|
|
134
|
-
flow({
|
|
135
|
-
name: "fulfillment.onOrder",
|
|
134
|
+
flow("fulfillment.onOrder", {
|
|
136
135
|
do: async ({ orderId }, fx) => {
|
|
137
136
|
await fx.emit(orderChanged, { orderId, kind: "placed" });
|
|
138
137
|
await fx.emit(orderStatus, { orderId, status: "fulfilling" });
|
|
@@ -147,8 +146,7 @@ on(
|
|
|
147
146
|
```typescript title="src/flows/orders/side-effects.ts"
|
|
148
147
|
on(
|
|
149
148
|
orderChanged,
|
|
150
|
-
flow({
|
|
151
|
-
name: "cache.onOrderChanged",
|
|
149
|
+
flow("cache.onOrderChanged", {
|
|
152
150
|
do: async ({ orderId }, fx) => {
|
|
153
151
|
/* invalidate order:{orderId} */
|
|
154
152
|
},
|
|
@@ -157,8 +155,7 @@ on(
|
|
|
157
155
|
|
|
158
156
|
on(
|
|
159
157
|
orderChanged,
|
|
160
|
-
flow({
|
|
161
|
-
name: "notify.onOrderChanged",
|
|
158
|
+
flow("notify.onOrderChanged", {
|
|
162
159
|
do: async ({ orderId }, fx) => {
|
|
163
160
|
await fx.send(orderConfirmed, { to: "user-1", data: { orderId } });
|
|
164
161
|
},
|
|
@@ -294,7 +291,6 @@ At-least-once: crash-after-claim reclaims when the lease expires, and a handler
|
|
|
294
291
|
## Learn more
|
|
295
292
|
|
|
296
293
|
- [Flow](/docs/elements/flow) — `on(trigger, flow)` and `fx.emit`
|
|
297
|
-
- [Console · Signals](/docs/console/signals) — topology, delivery stats, DLQ replay
|
|
298
294
|
- [Clock](/docs/elements/clock) — scheduled and delayed work
|
|
299
295
|
|
|
300
296
|
## Next
|
|
@@ -306,9 +302,4 @@ At-least-once: crash-after-claim reclaims when the lease expires, and a handler
|
|
|
306
302
|
description="Eight elements overview."
|
|
307
303
|
href="/docs/get-started/introduction"
|
|
308
304
|
/>
|
|
309
|
-
<Card
|
|
310
|
-
title="Console"
|
|
311
|
-
description="Panels derived from the Manifest."
|
|
312
|
-
href="/docs/console/overview"
|
|
313
|
-
/>
|
|
314
305
|
</Cards>
|
|
@@ -80,7 +80,7 @@ All data access goes through `fx.store(db)` — a typed session, one table at a
|
|
|
80
80
|
```typescript title="src/flows/notes/create.ts"
|
|
81
81
|
export const createNote = on(
|
|
82
82
|
http.post("/notes"),
|
|
83
|
-
flow({
|
|
83
|
+
flow("notes.create", {
|
|
84
84
|
in: z.object({ title: z.string(), body: z.string() }),
|
|
85
85
|
out: z.object({ id: z.string() }),
|
|
86
86
|
do: async (input, fx) => {
|
|
@@ -490,9 +490,8 @@ await fx.store(sessions).set(`sess:${token}`, { userId }, "7d");
|
|
|
490
490
|
const keys = await fx.store(sessions).list("sess:");
|
|
491
491
|
```
|
|
492
492
|
|
|
493
|
-
**Consequence:** `list`
|
|
494
|
-
|
|
495
|
-
readable.
|
|
493
|
+
**Consequence:** `list` enumerates a namespace. Prefer stable key prefixes (`user:`, `sess:`) so
|
|
494
|
+
your Flows and operator tools stay readable.
|
|
496
495
|
|
|
497
496
|
### Per-environment KV drivers
|
|
498
497
|
|
|
@@ -637,9 +636,9 @@ images: {
|
|
|
637
636
|
|
|
638
637
|
**Known limit:** `fs` is single-host. Under horizontal scale each replica sees its own filesystem (silently inconsistent object views). Boot prints a one-shot warning when `drivers.store.files` is `fs`. Use `s3` for docker/prod (create-oke templates already do).
|
|
639
638
|
|
|
640
|
-
<Callout title="
|
|
641
|
-
|
|
642
|
-
|
|
639
|
+
<Callout title="Operator tools can browse, not edit bytes">
|
|
640
|
+
Operator tools can list keys and delete them. Direct edit is **KV + SQL only** — blob bodies are
|
|
641
|
+
not patched.
|
|
643
642
|
</Callout>
|
|
644
643
|
|
|
645
644
|
### Images — `image` / `putImage`
|
|
@@ -861,9 +860,9 @@ Container images come from the `images` map — change the vendor by changing th
|
|
|
861
860
|
|
|
862
861
|
Columns tagged `.pii()` or `.sensitive()` are masked at the store boundary — flows, logs, and the Console see a mask, not the value. Revealing cleartext PII requires an explicit `pii:reveal` gate on the flow, so access is a permission, not a convention.
|
|
863
862
|
|
|
864
|
-
Optional `description` on a store (and `.describe("…")` on fields) becomes
|
|
865
|
-
|
|
866
|
-
|
|
863
|
+
Optional `description` on a store (and `.describe("…")` on fields) becomes its human title in
|
|
864
|
+
operator tools. Single-table `reads` / `writes` in the Manifest power cache invalidation and that
|
|
865
|
+
same PII masking — see the Callout under [Querying by hand](#querying-by-hand).
|
|
867
866
|
|
|
868
867
|
## Troubleshooting
|
|
869
868
|
|
|
@@ -915,7 +914,6 @@ Unlike KV/Files, unset `drivers.store.index` does **not** promote to `pgvector`
|
|
|
915
914
|
- [Flow](/docs/elements/flow) — the `fx.store` session inside `do`
|
|
916
915
|
- [AI](/docs/elements/ai) — `ai.embed` into a vector `store.index`, searched via `fx.search`
|
|
917
916
|
- [Gate](/docs/elements/gate) — `pii:reveal` and other permissions on flows
|
|
918
|
-
- [Console · Store](/docs/console/store) — browse data, cache keys, PII masking
|
|
919
917
|
- [CLI Reference](/docs/reference/cli) — `oke db push` · `generate` · `migrate`
|
|
920
918
|
- [Configuration](/docs/reference/configuration) — `drivers.store` maps and `images` pins
|
|
921
919
|
- [Environment variables](/docs/reference/environment-variables) — Redis · S3 · meilisearch URLs
|
|
@@ -929,9 +927,4 @@ Unlike KV/Files, unset `drivers.store.index` does **not** promote to `pgvector`
|
|
|
929
927
|
description="Eight elements overview."
|
|
930
928
|
href="/docs/get-started/introduction"
|
|
931
929
|
/>
|
|
932
|
-
<Card
|
|
933
|
-
title="Console"
|
|
934
|
-
description="Panels derived from the Manifest."
|
|
935
|
-
href="/docs/console/overview"
|
|
936
|
-
/>
|
|
937
930
|
</Cards>
|
|
@@ -62,7 +62,7 @@ Call `.reveal()` once at the boundary that needs the real credential:
|
|
|
62
62
|
```typescript title="src/flows/billing/charge.ts"
|
|
63
63
|
export const charge = on(
|
|
64
64
|
http.post("/charge"),
|
|
65
|
-
flow({
|
|
65
|
+
flow("billing.charge", {
|
|
66
66
|
in: ChargeInput,
|
|
67
67
|
out: z.object({ id: z.string() }),
|
|
68
68
|
do: async (input, fx) => {
|
|
@@ -251,7 +251,6 @@ Set the new value (`oke vault set` or the Console). The Console's rotation view
|
|
|
251
251
|
|
|
252
252
|
## Learn more
|
|
253
253
|
|
|
254
|
-
- [Console · Vault](/docs/console/vault) — fingerprints, resolution chain, rotation blast radius
|
|
255
254
|
- [Flow](/docs/elements/flow) — how `fx.vault` reads secrets inside `do`
|
|
256
255
|
- [CLI Reference](/docs/reference/cli) — `oke vault set` · `list` · `import`
|
|
257
256
|
|
|
@@ -264,9 +263,4 @@ Set the new value (`oke vault set` or the Console). The Console's rotation view
|
|
|
264
263
|
description="Eight elements overview."
|
|
265
264
|
href="/docs/get-started/introduction"
|
|
266
265
|
/>
|
|
267
|
-
<Card
|
|
268
|
-
title="Console"
|
|
269
|
-
description="Panels derived from the Manifest."
|
|
270
|
-
href="/docs/console/overview"
|
|
271
|
-
/>
|
|
272
266
|
</Cards>
|
|
@@ -44,9 +44,7 @@ import { z } from "zod";
|
|
|
44
44
|
|
|
45
45
|
export const health = on(
|
|
46
46
|
http.get("/health").gate(gate.public),
|
|
47
|
-
flow({
|
|
48
|
-
name: "main.health",
|
|
49
|
-
unit: "main",
|
|
47
|
+
flow("main.health", {
|
|
50
48
|
out: z.object({ ok: z.literal(true) }),
|
|
51
49
|
do: () => ({ ok: true as const }),
|
|
52
50
|
}),
|
|
@@ -148,7 +146,6 @@ bun test
|
|
|
148
146
|
- [Flow](/docs/elements/flow) — triggers, contracts, effects, and composition
|
|
149
147
|
- [Store](/docs/elements/store) — SQL, KV, files, index
|
|
150
148
|
- [fx](/docs/reference/fx) — the full effect surface
|
|
151
|
-
- [Console · Overview](/docs/console/overview) — Manifest-derived panels
|
|
152
149
|
|
|
153
150
|
## Next
|
|
154
151
|
|
|
@@ -163,9 +160,4 @@ bun test
|
|
|
163
160
|
description="SQL, KV, files, index, and declarative resources."
|
|
164
161
|
href="/docs/elements/store"
|
|
165
162
|
/>
|
|
166
|
-
<Card
|
|
167
|
-
title="Console"
|
|
168
|
-
description="Manifest-derived flows, effects, traces, and architecture."
|
|
169
|
-
href="/docs/console/overview"
|
|
170
|
-
/>
|
|
171
163
|
</Cards>
|
|
@@ -49,7 +49,7 @@ shared lifecycle, optional live DB config, nothing to re-copy.
|
|
|
49
49
|
Your observability stack learned your routes from sampled traffic. The Flow
|
|
50
50
|
you deployed an hour ago is invisible until someone wires it by hand.
|
|
51
51
|
|
|
52
|
-
**OKE derives:**
|
|
52
|
+
**OKE derives:** its operator interface reads the Manifest —
|
|
53
53
|
flows, effects, traces, architecture — current on every save, in dev and prod
|
|
54
54
|
(`:6533`).
|
|
55
55
|
|
|
@@ -118,7 +118,6 @@ ninth element — the set of eight is closed.
|
|
|
118
118
|
- [Flow](/docs/elements/flow) — how effects are recorded and inferred
|
|
119
119
|
- [Vault](/docs/elements/vault) — fail-loud secret contracts
|
|
120
120
|
- [Plugins](/docs/reference/plugins) — the official HTTP glue set
|
|
121
|
-
- [Console · Overview](/docs/console/overview) — the Manifest-derived operator UI
|
|
122
121
|
|
|
123
122
|
## Next
|
|
124
123
|
|
|
@@ -31,7 +31,6 @@ on(orderPlaced, sendReceipt);
|
|
|
31
31
|
description="Official extensions — security, ops, performance."
|
|
32
32
|
href="/docs/plugins"
|
|
33
33
|
/>
|
|
34
|
-
<Card title="Console" description="Manifest-derived operator panels." href="/docs/console" />
|
|
35
34
|
<Card
|
|
36
35
|
title="Deployment"
|
|
37
36
|
description="Compose, Swarm, Kubernetes, reverse proxy."
|
|
@@ -67,18 +67,18 @@ so ● updates live if you stop a container; AI ● tracks model phase while the
|
|
|
67
67
|
AI container is up. Boot does not wait for the model to become ready.
|
|
68
68
|
|
|
69
69
|
On a TTY with `-d`, keyboard controls stay active under Logs. Status ● for
|
|
70
|
-
compose/AI live on the board **above** Logs (not as log lines)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
|
77
|
-
|
|
|
78
|
-
|
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
70
|
+
compose/AI live on the board **above** Logs (not as log lines) — it already
|
|
71
|
+
lists every service, so refresh is enough to see the stack; there is no
|
|
72
|
+
separate services panel. Press `r` to clear the log pane and reprint the
|
|
73
|
+
latest board. `?` also refreshes first so the help panel is not mixed into
|
|
74
|
+
request logs.
|
|
75
|
+
|
|
76
|
+
| Key | Action |
|
|
77
|
+
| --------- | ----------------------------------- |
|
|
78
|
+
| `?` | help |
|
|
79
|
+
| `r` | refresh — clear logs, show latest ● |
|
|
80
|
+
| `q` | quit `oke dev` |
|
|
81
|
+
| `u` / `x` | compose up / stop (whole stack) |
|
|
82
82
|
|
|
83
83
|
Compose stays quiet unless it fails. Process-local boot notices print once under
|
|
84
84
|
the hero (not once per process).
|
|
@@ -7,7 +7,7 @@ icon: "Braces"
|
|
|
7
7
|
`fx` is the second argument of every `do` — the single door to the world. This page is the whole surface; each entry notes the **effect it records**, which is what feeds the Manifest, caching, and capability checks.
|
|
8
8
|
|
|
9
9
|
```typescript
|
|
10
|
-
flow({
|
|
10
|
+
flow("orders.example", {
|
|
11
11
|
do: async (input, fx) => {
|
|
12
12
|
// everything below, on this one object
|
|
13
13
|
},
|
|
@@ -51,8 +51,7 @@ Declare `effects: { reads: ["runs"] }`. Powers native SLO checkers (Clock + Chan
|
|
|
51
51
|
```typescript
|
|
52
52
|
on(
|
|
53
53
|
every("5m"),
|
|
54
|
-
flow({
|
|
55
|
-
name: "ops.slo-check",
|
|
54
|
+
flow("ops.slo-check", {
|
|
56
55
|
effects: { reads: ["runs"], sends: ["slo-alert"] },
|
|
57
56
|
do: async (_, fx) => {
|
|
58
57
|
const breaches = await fx.runs.checkSlo(
|
|
@@ -133,7 +132,7 @@ const rows = await fx.using(
|
|
|
133
132
|
);
|
|
134
133
|
```
|
|
135
134
|
|
|
136
|
-
**Consequence:** put `fx.retry` inside `fx.step` on durable flows so a completed charge never re-runs on resume. Coarse whole-body retry is `flow({ retry: { … } })` on the same journal session.
|
|
135
|
+
**Consequence:** put `fx.retry` inside `fx.step` on durable flows so a completed charge never re-runs on resume. Coarse whole-body retry is `flow(name, { retry: { … } })` on the same journal session.
|
|
137
136
|
|
|
138
137
|
## Channel
|
|
139
138
|
|
|
@@ -260,9 +259,9 @@ must log who started the call chain.
|
|
|
260
259
|
## Not on `fx`
|
|
261
260
|
|
|
262
261
|
<Callout title="No fx.metric">
|
|
263
|
-
Investigated and declined.
|
|
264
|
-
|
|
265
|
-
|
|
262
|
+
Investigated and declined. `fx.runs` already provides per-invocation observability as wide events.
|
|
263
|
+
Native alerting is `fx.runs` + Clock + Channel — not a second counter/gauge API. Optional OTLP
|
|
264
|
+
export for existing Grafana/Datadog stacks is additive and never required.
|
|
266
265
|
</Callout>
|
|
267
266
|
|
|
268
267
|
## Learn more
|
package/src/auth/auth.test.ts
CHANGED
|
@@ -177,8 +177,7 @@ describe("cross-plane compile error", () => {
|
|
|
177
177
|
path: "src/flows/console/store.ts",
|
|
178
178
|
flow: "console.store.query",
|
|
179
179
|
source: `
|
|
180
|
-
export const query = flow({
|
|
181
|
-
name: "console.store.query",
|
|
180
|
+
export const query = flow("console.store.query", {
|
|
182
181
|
plane: "operator",
|
|
183
182
|
do: (_i, fx) => fx.auth.userId,
|
|
184
183
|
});
|
package/src/auth/bindings.ts
CHANGED
|
@@ -205,9 +205,7 @@ export function createAuthHttpBindings(
|
|
|
205
205
|
|
|
206
206
|
const base = config.basePath;
|
|
207
207
|
|
|
208
|
-
const refresh = flow({
|
|
209
|
-
name: "auth.refresh",
|
|
210
|
-
unit: "auth",
|
|
208
|
+
const refresh = flow("auth.refresh", {
|
|
211
209
|
plane: "user",
|
|
212
210
|
in: RefreshIn,
|
|
213
211
|
out: SessionTokensOut,
|
|
@@ -231,9 +229,7 @@ export function createAuthHttpBindings(
|
|
|
231
229
|
},
|
|
232
230
|
});
|
|
233
231
|
|
|
234
|
-
const revoke = flow({
|
|
235
|
-
name: "auth.revoke",
|
|
236
|
-
unit: "auth",
|
|
232
|
+
const revoke = flow("auth.revoke", {
|
|
237
233
|
plane: "user",
|
|
238
234
|
in: RevokeIn,
|
|
239
235
|
out: z.object({ ok: z.literal(true) }),
|
|
@@ -252,9 +248,7 @@ export function createAuthHttpBindings(
|
|
|
252
248
|
},
|
|
253
249
|
});
|
|
254
250
|
|
|
255
|
-
const me = flow({
|
|
256
|
-
name: "auth.me",
|
|
257
|
-
unit: "auth",
|
|
251
|
+
const me = flow("auth.me", {
|
|
258
252
|
plane: "user",
|
|
259
253
|
out: MeOut,
|
|
260
254
|
errors: { AuthFailed },
|
|
@@ -289,9 +283,7 @@ export function createAuthHttpBindings(
|
|
|
289
283
|
];
|
|
290
284
|
|
|
291
285
|
if (config.emailAndPassword.enabled) {
|
|
292
|
-
const signInEmail = flow({
|
|
293
|
-
name: "auth.signInEmail",
|
|
294
|
-
unit: "auth",
|
|
286
|
+
const signInEmail = flow("auth.signInEmail", {
|
|
295
287
|
plane: "user",
|
|
296
288
|
in: EmailPasswordIn,
|
|
297
289
|
out: SessionTokensOut,
|
|
@@ -319,9 +311,7 @@ export function createAuthHttpBindings(
|
|
|
319
311
|
},
|
|
320
312
|
});
|
|
321
313
|
|
|
322
|
-
const signUpEmail = flow({
|
|
323
|
-
name: "auth.signUpEmail",
|
|
324
|
-
unit: "auth",
|
|
314
|
+
const signUpEmail = flow("auth.signUpEmail", {
|
|
325
315
|
plane: "user",
|
|
326
316
|
in: EmailPasswordIn,
|
|
327
317
|
out: SessionTokensOut,
|
|
@@ -136,7 +136,7 @@ describe("gate.auth — autoBoot / posture", () => {
|
|
|
136
136
|
});
|
|
137
137
|
|
|
138
138
|
test(".needs(auth) satisfied by gate.auth without .plug(auth())", async () => {
|
|
139
|
-
on(http.get("/x").gate(AUTH_SESSION_GATE), flow(
|
|
139
|
+
on(http.get("/x").gate(AUTH_SESSION_GATE), flow("x", { do: () => ({ ok: true }) }));
|
|
140
140
|
const dependent = plugin("needs-auth", { version: "0.0.1" }).needs("auth");
|
|
141
141
|
const app = oke({
|
|
142
142
|
name: "needs-gate-auth",
|
|
@@ -309,7 +309,7 @@ describe("gate.auth — email Flows + security", () => {
|
|
|
309
309
|
});
|
|
310
310
|
|
|
311
311
|
test("missing gate on custom HTTP still GateBootError", async () => {
|
|
312
|
-
on(http.get("/open"), flow(
|
|
312
|
+
on(http.get("/open"), flow("open", { do: () => ({ ok: true }) }));
|
|
313
313
|
const app = oke({
|
|
314
314
|
name: "gap",
|
|
315
315
|
env: "local",
|