okengine 0.6.1 → 0.8.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/AGENTS.md +2 -2
- package/README.md +73 -181
- package/package.json +3 -3
- package/site/content/docs/console/clock.mdx +25 -5
- package/site/content/docs/console/signals.mdx +13 -4
- package/site/content/docs/elements/ai.mdx +29 -2
- package/site/content/docs/elements/channel.mdx +42 -25
- package/site/content/docs/elements/clock.mdx +59 -25
- package/site/content/docs/elements/flow.mdx +8 -2
- package/site/content/docs/elements/gate.mdx +8 -1
- package/site/content/docs/elements/signal.mdx +173 -29
- package/site/content/docs/elements/store.mdx +595 -73
- package/site/content/docs/elements/vault.mdx +15 -5
- package/site/content/docs/get-started/basic-usage.mdx +13 -6
- package/site/content/docs/get-started/installation.mdx +52 -14
- package/site/content/docs/get-started/introduction.mdx +1 -1
- package/site/content/docs/plugins/magic-link.mdx +42 -0
- package/site/content/docs/plugins/phone-number.mdx +78 -17
- package/site/content/docs/plugins/two-factor.mdx +1 -0
- package/site/content/docs/reference/cli.md +32 -2
- package/site/content/docs/reference/configuration.mdx +15 -13
- package/site/content/docs/reference/environment-variables.mdx +32 -18
- package/site/content/docs/reference/errors.mdx +12 -11
- package/site/content/docs/reference/fx.mdx +15 -13
- package/site/content/docs/reference/security.md +3 -1
- package/src/cli/ai-setup/ai-setup.test.ts +144 -0
- package/src/cli/ai-setup/apply.ts +214 -0
- package/src/cli/ai-setup/catalog.ts +263 -0
- package/src/cli/ai-setup/detect-ollama.ts +166 -0
- package/src/cli/ai-setup/index.ts +228 -0
- package/src/cli/ai-setup/prompts.ts +649 -0
- package/src/cli/ai-setup/recommend.test.ts +100 -0
- package/src/cli/ai-setup/recommend.ts +203 -0
- package/src/cli/ai.ts +38 -0
- package/src/cli/db-auto-push.test.ts +6 -1
- package/src/cli/db-auto-push.ts +8 -3
- package/src/cli/db-seed.ts +359 -0
- package/src/cli/db.test.ts +341 -3
- package/src/cli/db.ts +75 -8
- package/src/cli/dev-db-push.test.ts +59 -0
- package/src/cli/dev.test.ts +40 -0
- package/src/cli/dev.ts +55 -1
- package/src/cli/docker-clean.test.ts +179 -0
- package/src/cli/docker-clean.ts +406 -0
- package/src/cli/docker.ts +16 -4
- package/src/cli/ensure-drizzle-config.ts +1 -1
- package/src/cli/hero-meta.test.ts +6 -5
- package/src/cli/hero-meta.ts +7 -1
- package/src/cli/index.ts +5 -0
- package/src/cli/load-config.images.test.ts +22 -0
- package/src/cli/load-config.ts +7 -2
- package/src/cli/openbao-bootstrap.test.ts +1 -0
- package/src/cli/openbao-bootstrap.ts +9 -2
- package/src/cli/registry.ts +116 -2
- package/src/client/live-gap.test.ts +35 -0
- package/src/client/transport.test.ts +21 -0
- package/src/client/transport.ts +27 -5
- package/src/compiler/effects-infer.ts +1 -0
- package/src/compiler/fixtures/skyport/oke.config.ts +1 -1
- package/src/config/define-config.test.ts +6 -6
- package/src/config/index.ts +5 -1
- package/src/console/server/console.test.ts +45 -0
- package/src/console/server/flows.ts +39 -9
- package/src/console/server/i18n.ts +41 -0
- package/src/console/server/vault.ts +12 -38
- package/src/console/ui/dist/assets/index-ClX0ZCe5.js +10 -0
- package/src/console/ui/dist/assets/{panel-access-BGv45snf.js → panel-access-ri1X9Otj.js} +1 -1
- package/src/console/ui/dist/assets/{panel-ai-B2S7LEii.js → panel-ai-DLHjRoJk.js} +1 -1
- package/src/console/ui/dist/assets/{panel-architecture-D7UJh91v.js → panel-architecture-DI3bQF3J.js} +1 -1
- package/src/console/ui/dist/assets/{panel-channels-9T3ybqRu.js → panel-channels-BDYWX29p.js} +1 -1
- package/src/console/ui/dist/assets/{panel-clock-Cb1UXGRQ.js → panel-clock-i-JpTjVB.js} +1 -1
- package/src/console/ui/dist/assets/{panel-diff-DmYbKWmN.js → panel-diff-CFuWxiXy.js} +1 -1
- package/src/console/ui/dist/assets/{panel-flows-PiHwT55z.js → panel-flows-B8TRhcoe.js} +1 -1
- package/src/console/ui/dist/assets/{panel-gates-BQGYXvjT.js → panel-gates-CfkBgco7.js} +1 -1
- package/src/console/ui/dist/assets/{panel-overview-BBnRO18l.js → panel-overview-DONDzBd2.js} +1 -1
- package/src/console/ui/dist/assets/{panel-plugins-D0PsmVw2.js → panel-plugins-DDJwTehL.js} +1 -1
- package/src/console/ui/dist/assets/{panel-runs-CWuRDe0r.js → panel-runs-D5zf-D9c.js} +1 -1
- package/src/console/ui/dist/assets/{panel-signals-Bbg4ewpP.js → panel-signals-Dsxdu_AR.js} +1 -1
- package/src/console/ui/dist/assets/{panel-store-CPCbsDRa.js → panel-store-hy7O8HOs.js} +1 -1
- package/src/console/ui/dist/assets/{panel-traces-DVAzuA_S.js → panel-traces-CBiAe5go.js} +1 -1
- package/src/console/ui/dist/assets/{panel-vault-D1_MvOmo.js → panel-vault-Za1GyJfM.js} +1 -1
- package/src/console/ui/dist/index.html +1 -1
- package/src/console/ui/shell/components/ui.tsx +5 -1
- package/src/console/ui/shell/setup/Wizard.tsx +26 -5
- package/src/docker/cleanup.test.ts +193 -0
- package/src/docker/cleanup.ts +355 -0
- package/src/docker/docker.test.ts +4 -0
- package/src/docker/index.ts +19 -0
- package/src/docker/recipes/ollama.ts +10 -5
- package/src/drivers/ai-ollama.ts +45 -2
- package/src/drivers/ai-providers.test.ts +31 -0
- package/src/drivers/channel-sently.test.ts +8 -0
- package/src/drivers/channel-taqnyat-mail.ts +34 -0
- package/src/drivers/channel-types.ts +71 -0
- package/src/drivers/clock-postgres.test.ts +258 -0
- package/src/drivers/clock-postgres.ts +410 -0
- package/src/drivers/index.ts +21 -0
- package/src/drivers/journal-postgres.test.ts +175 -0
- package/src/drivers/journal-postgres.ts +492 -0
- package/src/drivers/signal-engine.ts +98 -25
- package/src/drivers/signal-nats.ts +4 -4
- package/src/drivers/signal-postgres.ts +158 -64
- package/src/drivers/signal-redis.ts +4 -4
- package/src/drivers/signal-types.ts +61 -2
- package/src/drivers/vault-driver-removal.test.ts +5 -1
- package/src/elements/channel/runtime.ts +51 -0
- package/src/elements/channel.test.ts +71 -0
- package/src/elements/clock/chaos-child.ts +389 -0
- package/src/elements/clock/chaos.test.ts +204 -0
- package/src/elements/clock/durable.ts +7 -0
- package/src/elements/clock/reconcile.ts +110 -0
- package/src/elements/clock/runtime.ts +13 -0
- package/src/elements/clock.test.ts +164 -2
- package/src/elements/clock.ts +2 -1
- package/src/elements/gate/runtime.ts +6 -0
- package/src/elements/signal/chaos-child.ts +41 -4
- package/src/elements/signal/declare.ts +4 -1
- package/src/elements/signal/delivery-modes.test.ts +179 -0
- package/src/elements/signal/key-ordering.test.ts +306 -0
- package/src/elements/signal/lease-reclaim.test.ts +123 -0
- package/src/elements/signal/optional-emit.test.ts +76 -0
- package/src/elements/signal/order-lifecycle.test.ts +130 -0
- package/src/elements/signal/orphan-messages.test.ts +152 -0
- package/src/elements/signal/runtime.ts +11 -4
- package/src/elements/signal/schema-emit.test.ts +107 -0
- package/src/elements/signal.test.ts +89 -5
- package/src/elements/store/files-fx.ts +104 -0
- package/src/elements/store/files-image.test.ts +251 -0
- package/src/elements/store/files-image.ts +494 -0
- package/src/elements/store/runtime.ts +60 -0
- package/src/elements/store/seed.test.ts +27 -0
- package/src/elements/store/seed.ts +68 -0
- package/src/elements/store/sql-session.test.ts +39 -0
- package/src/elements/store/sql-session.ts +55 -0
- package/src/elements/store/upsert-app.test.ts +103 -0
- package/src/elements/store.ts +29 -0
- package/src/elements/vault/boot-chain.ts +150 -0
- package/src/elements/vault/runtime.ts +8 -0
- package/src/index.ts +15 -0
- package/src/kernel/app.ts +168 -15
- package/src/kernel/boot-bind/channel.test.ts +16 -0
- package/src/kernel/boot-bind/channel.ts +13 -0
- package/src/kernel/boot-bind/clock.ts +63 -7
- package/src/kernel/boot-bind/gate.ts +71 -3
- package/src/kernel/boot-bind/honor-config.test.ts +348 -0
- package/src/kernel/boot-bind/journal.ts +89 -0
- package/src/kernel/boot-bind/signal.ts +64 -7
- package/src/kernel/boot-bind/vault.ts +27 -8
- package/src/kernel/boot.test.ts +6 -4
- package/src/kernel/boot.ts +63 -15
- package/src/kernel/concurrency.ts +1 -1
- package/src/kernel/errors.ts +8 -2
- package/src/kernel/fx.test.ts +19 -0
- package/src/kernel/fx.ts +153 -9
- package/src/kernel/index.ts +6 -0
- package/src/kernel/journal-boot.test.ts +397 -0
- package/src/kernel/journal-suspend.ts +35 -0
- package/src/kernel/journal.test.ts +142 -0
- package/src/kernel/journal.ts +202 -27
- package/src/plugins/auth-methods.security.test.ts +10 -7
- package/src/plugins/phone-number.ts +67 -10
- package/src/plugins/taqnyat.live.test.ts +174 -0
- package/src/runtime/dev-request-log.test.ts +20 -1
- package/src/runtime/dev-request-log.ts +38 -0
- package/src/term.test.ts +15 -0
- package/src/term.ts +8 -3
- package/src/console/ui/dist/assets/index-CjxwRGVv.js +0 -10
package/AGENTS.md
CHANGED
|
@@ -78,9 +78,9 @@ A regression fails the build. Claims we cannot measure, we do not make.
|
|
|
78
78
|
Published packages:
|
|
79
79
|
|
|
80
80
|
- `okengine` — framework. Subpath exports: `.`, `./client`, `./test`, `./config`, `./auth`, `./plugins`, `./drivers/*`. `"sideEffects": false`. CLI binary: `oke`.
|
|
81
|
-
- `create-oke` — scaffold CLI (`bunx create-oke@latest <name>`). Lives in `packages/create-oke` and ships
|
|
81
|
+
- `create-oke` — scaffold CLI (`bunx create-oke@latest <name>`). Lives in `packages/create-oke` and ships Notes starters from `packages/create-oke/templates/{standard,advanced}`.
|
|
82
82
|
|
|
83
|
-
Engine: Bun `>=1.3`.
|
|
83
|
+
Engine: Bun `>=1.3.14`.
|
|
84
84
|
|
|
85
85
|
## Documentation authority
|
|
86
86
|
|
package/README.md
CHANGED
|
@@ -13,66 +13,12 @@
|
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
15
|
<p align="center">
|
|
16
|
-
<a href="https://www.npmjs.com/package/okengine">
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
<a href="https://jsr.io/@omqkhafi/okengine">
|
|
23
|
-
<picture>
|
|
24
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://shieldcn.dev/jsr/@omqkhafi/okengine.svg?variant=outline&mode=dark&size=sm&theme=zinc&font=geist" />
|
|
25
|
-
<img alt="JSR" src="https://shieldcn.dev/jsr/@omqkhafi/okengine.svg?variant=outline&mode=light&size=sm&theme=zinc&font=geist" />
|
|
26
|
-
</picture>
|
|
27
|
-
</a>
|
|
28
|
-
<a href="https://www.npmjs.com/package/create-oke">
|
|
29
|
-
<picture>
|
|
30
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://shieldcn.dev/npm/create-oke.svg?variant=outline&mode=dark&size=sm&theme=zinc&font=geist" />
|
|
31
|
-
<img alt="create-oke" src="https://shieldcn.dev/npm/create-oke.svg?variant=outline&mode=light&size=sm&theme=zinc&font=geist" />
|
|
32
|
-
</picture>
|
|
33
|
-
</a>
|
|
34
|
-
<a href="https://www.npmjs.com/package/okengine">
|
|
35
|
-
<picture>
|
|
36
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://shieldcn.dev/npm/types/okengine.svg?variant=outline&mode=dark&size=sm&theme=zinc&font=geist" />
|
|
37
|
-
<img alt="types" src="https://shieldcn.dev/npm/types/okengine.svg?variant=outline&mode=light&size=sm&theme=zinc&font=geist" />
|
|
38
|
-
</picture>
|
|
39
|
-
</a>
|
|
40
|
-
<a href="https://opensource.org/licenses/MIT">
|
|
41
|
-
<picture>
|
|
42
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://shieldcn.dev/github/license/omqkhafi/okengine.svg?variant=outline&mode=dark&size=sm&theme=zinc&font=geist" />
|
|
43
|
-
<img alt="MIT" src="https://shieldcn.dev/github/license/omqkhafi/okengine.svg?variant=outline&mode=light&size=sm&theme=zinc&font=geist" />
|
|
44
|
-
</picture>
|
|
45
|
-
</a>
|
|
46
|
-
<a href="https://bun.sh">
|
|
47
|
-
<picture>
|
|
48
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://shieldcn.dev/badge/Bun-%3E%3D1.3.svg?logo=bun&variant=outline&mode=dark&size=sm&theme=zinc&font=geist" />
|
|
49
|
-
<img alt="Bun >=1.3" src="https://shieldcn.dev/badge/Bun-%3E%3D1.3.svg?logo=bun&variant=outline&mode=light&size=sm&theme=zinc&font=geist" />
|
|
50
|
-
</picture>
|
|
51
|
-
</a>
|
|
52
|
-
<a href="https://github.com/omqkhafi/okengine/releases">
|
|
53
|
-
<picture>
|
|
54
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://shieldcn.dev/github/release/omqkhafi/okengine.svg?variant=outline&mode=dark&size=sm&theme=zinc&font=geist" />
|
|
55
|
-
<img alt="release" src="https://shieldcn.dev/github/release/omqkhafi/okengine.svg?variant=outline&mode=light&size=sm&theme=zinc&font=geist" />
|
|
56
|
-
</picture>
|
|
57
|
-
</a>
|
|
58
|
-
<a href="https://github.com/omqkhafi/okengine/stargazers">
|
|
59
|
-
<picture>
|
|
60
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://shieldcn.dev/github/stars/omqkhafi/okengine.svg?variant=outline&mode=dark&size=sm&theme=zinc&font=geist" />
|
|
61
|
-
<img alt="stars" src="https://shieldcn.dev/github/stars/omqkhafi/okengine.svg?variant=outline&mode=light&size=sm&theme=zinc&font=geist" />
|
|
62
|
-
</picture>
|
|
63
|
-
</a>
|
|
64
|
-
<a href="https://github.com/omqkhafi/okengine/actions">
|
|
65
|
-
<picture>
|
|
66
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://shieldcn.dev/github/ci/omqkhafi/okengine.svg?variant=outline&mode=dark&size=sm&theme=zinc&font=geist" />
|
|
67
|
-
<img alt="CI" src="https://shieldcn.dev/github/ci/omqkhafi/okengine.svg?variant=outline&mode=light&size=sm&theme=zinc&font=geist" />
|
|
68
|
-
</picture>
|
|
69
|
-
</a>
|
|
70
|
-
<a href="https://github.com/omqkhafi/okengine/commits">
|
|
71
|
-
<picture>
|
|
72
|
-
<source media="(prefers-color-scheme: dark)" srcset="https://shieldcn.dev/github/last-commit/omqkhafi/okengine.svg?variant=outline&mode=dark&size=sm&theme=zinc&font=geist" />
|
|
73
|
-
<img alt="last commit" src="https://shieldcn.dev/github/last-commit/omqkhafi/okengine.svg?variant=outline&mode=light&size=sm&theme=zinc&font=geist" />
|
|
74
|
-
</picture>
|
|
75
|
-
</a>
|
|
16
|
+
<a href="https://www.npmjs.com/package/okengine"><img alt="npm" src="https://shieldcn.dev/npm/okengine.svg?size=sm&variant=outline" /></a>
|
|
17
|
+
<a href="https://jsr.io/@omqkhafi/okengine"><img alt="JSR" src="https://shieldcn.dev/jsr/@omqkhafi/okengine.svg?size=sm&variant=outline" /></a>
|
|
18
|
+
<a href="https://opensource.org/licenses/MIT"><img alt="MIT" src="https://shieldcn.dev/npm/license/okengine.svg?size=sm&variant=outline" /></a>
|
|
19
|
+
<a href="https://bun.sh"><img alt="Bun >=1.3.14" src="https://shieldcn.dev/badge/Bun-%3E%3D1.3.14.svg?logo=bun&size=sm&variant=outline" /></a>
|
|
20
|
+
<a href="https://github.com/omqkhafi/okengine/actions"><img alt="CI" src="https://shieldcn.dev/github/ci/omqkhafi/okengine.svg?size=sm&variant=outline" /></a>
|
|
21
|
+
<a href="https://github.com/omqkhafi/okengine/commits"><img alt="last commit" src="https://shieldcn.dev/github/last-commit/omqkhafi/okengine.svg?size=sm&variant=outline" /></a>
|
|
76
22
|
</p>
|
|
77
23
|
|
|
78
24
|
<p align="center">
|
|
@@ -82,7 +28,7 @@
|
|
|
82
28
|
<p align="center">
|
|
83
29
|
<a href="https://oke.omqkhafi.dev"><strong>Docs</strong></a> ·
|
|
84
30
|
<a href="https://oke.omqkhafi.dev/docs"><strong>Handbook</strong></a> ·
|
|
85
|
-
<code>
|
|
31
|
+
<a href="https://oke.omqkhafi.dev/llms.txt"><code>llms.txt</code></a> ·
|
|
86
32
|
<a href="https://www.npmjs.com/package/create-oke"><code>create-oke</code></a>
|
|
87
33
|
</p>
|
|
88
34
|
|
|
@@ -93,10 +39,13 @@
|
|
|
93
39
|
|
|
94
40
|
## Install
|
|
95
41
|
|
|
42
|
+
Requires [Bun](https://bun.sh) `>=1.3.14`.
|
|
43
|
+
|
|
96
44
|
```bash
|
|
45
|
+
bunx create-oke@latest my-app # scaffold (recommended)
|
|
97
46
|
bun add okengine # framework + `oke` CLI
|
|
47
|
+
bun install -g okengine # global `oke` on PATH
|
|
98
48
|
bunx jsr add @omqkhafi/okengine # JSR — library API only
|
|
99
|
-
bunx create-oke@latest my-app # scaffold
|
|
100
49
|
```
|
|
101
50
|
|
|
102
51
|
## Quick start
|
|
@@ -117,139 +66,82 @@ oke dev
|
|
|
117
66
|
|
|
118
67
|
Mnemonic: **O·K·E = 6·5·3**.
|
|
119
68
|
|
|
120
|
-
|
|
69
|
+
The starter already exports a health Flow — change it and save; App and Console update from the same Manifest:
|
|
121
70
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
71
|
+
```typescript
|
|
72
|
+
import { on, flow, http, gate } from "okengine";
|
|
73
|
+
import { z } from "zod";
|
|
74
|
+
|
|
75
|
+
export const health = on(
|
|
76
|
+
http.get("/health").gate(gate.public),
|
|
77
|
+
flow({
|
|
78
|
+
out: z.object({ ok: z.literal(true) }),
|
|
79
|
+
do: () => ({ ok: true as const }),
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
82
|
+
```
|
|
125
83
|
|
|
126
|
-
|
|
84
|
+
Call it with a typed client (contracts inferred — no separate codegen):
|
|
127
85
|
|
|
128
86
|
```typescript
|
|
129
|
-
import {
|
|
130
|
-
import {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
// Contracts derived from the schema — one source of truth.
|
|
135
|
-
const NewNote = createInsertSchema(notesTable, { title: (s) => s.min(1).max(120) }).omit({
|
|
136
|
-
id: true,
|
|
137
|
-
createdAt: true,
|
|
138
|
-
});
|
|
139
|
-
const Note = createSelectSchema(notesTable);
|
|
140
|
-
|
|
141
|
-
// One declarative resource: list (cursor + search + filters + order +
|
|
142
|
-
// select), create (201), get / update / remove with typed NotFound.
|
|
143
|
-
const notesR = store.resource(db, notesTable, {
|
|
144
|
-
in: NewNote,
|
|
145
|
-
out: Note,
|
|
146
|
-
update: NewNote.partial(),
|
|
147
|
-
list: {
|
|
148
|
-
cursor: [notesTable.createdAt, notesTable.id],
|
|
149
|
-
direction: "desc",
|
|
150
|
-
limit: 20,
|
|
151
|
-
maxLimit: 100,
|
|
152
|
-
search: [notesTable.title],
|
|
153
|
-
filter: "all",
|
|
154
|
-
order: "all",
|
|
155
|
-
},
|
|
156
|
-
unit: "notes",
|
|
157
|
-
breaking: true,
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
// Mount all five verbs: list/create on /notes, get/update/remove on /notes/:id.
|
|
161
|
-
// `on(http.resource(...))` returns the ops bag; spread it into named exports
|
|
162
|
-
// so `adopt({ notes })` registers each flow under the unit.
|
|
163
|
-
const mounted = on(http.resource("/notes", notesR.all()));
|
|
164
|
-
|
|
165
|
-
export const list = mounted.list;
|
|
166
|
-
export const create = mounted.create;
|
|
167
|
-
export const get = mounted.get;
|
|
168
|
-
export const update = mounted.update;
|
|
169
|
-
export const remove = mounted.remove;
|
|
87
|
+
import { createClient } from "okengine/client";
|
|
88
|
+
import type { App } from "./app";
|
|
89
|
+
|
|
90
|
+
const api = createClient<App>("http://localhost:6530");
|
|
91
|
+
const { data, error } = await api.main.health({});
|
|
170
92
|
```
|
|
171
93
|
|
|
172
94
|
Full walkthrough: [Basic usage](https://oke.omqkhafi.dev/docs/get-started/basic-usage).
|
|
173
95
|
|
|
174
|
-
##
|
|
96
|
+
## One law
|
|
175
97
|
|
|
176
|
-
|
|
177
|
-
<a href="https://github.com/omqkhafi/okengine/issues">
|
|
178
|
-
<picture>
|
|
179
|
-
<source
|
|
180
|
-
media="(prefers-color-scheme: dark)"
|
|
181
|
-
srcset="https://shieldcn.dev/chart/github/issues/omqkhafi/okengine.svg?theme=zinc&font=geist&mode=dark"
|
|
182
|
-
/>
|
|
183
|
-
<img
|
|
184
|
-
alt="GitHub issues over time"
|
|
185
|
-
src="https://shieldcn.dev/chart/github/issues/omqkhafi/okengine.svg?theme=zinc&font=geist&mode=light"
|
|
186
|
-
width="750"
|
|
187
|
-
/>
|
|
188
|
-
</picture>
|
|
189
|
-
</a>
|
|
190
|
-
</p>
|
|
98
|
+
Every backend behavior is a Flow: `on(Trigger) → Effects`. Endpoints, jobs, consumers, and webhooks are the same species — only the trigger changes.
|
|
191
99
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
<source
|
|
196
|
-
media="(prefers-color-scheme: dark)"
|
|
197
|
-
srcset="https://shieldcn.dev/chart/github/commits/omqkhafi.svg?theme=zinc&font=geist&mode=dark"
|
|
198
|
-
/>
|
|
199
|
-
<img
|
|
200
|
-
alt="Lifetime commit history"
|
|
201
|
-
src="https://shieldcn.dev/chart/github/commits/omqkhafi.svg?theme=zinc&font=geist&mode=light"
|
|
202
|
-
width="750"
|
|
203
|
-
/>
|
|
204
|
-
</picture>
|
|
205
|
-
</a>
|
|
206
|
-
</p>
|
|
100
|
+
```typescript
|
|
101
|
+
import { on, flow, signal, store, clock, gate, vault, channel, ai, plugin } from "okengine";
|
|
102
|
+
```
|
|
207
103
|
|
|
208
|
-
|
|
209
|
-
<a href="https://www.npmjs.com/package/okengine">
|
|
210
|
-
<picture>
|
|
211
|
-
<source
|
|
212
|
-
media="(prefers-color-scheme: dark)"
|
|
213
|
-
srcset="https://shieldcn.dev/chart/npm/okengine.svg?theme=zinc&font=geist&mode=dark"
|
|
214
|
-
/>
|
|
215
|
-
<img
|
|
216
|
-
alt="okengine npm downloads"
|
|
217
|
-
src="https://shieldcn.dev/chart/npm/okengine.svg?theme=zinc&font=geist&mode=light"
|
|
218
|
-
width="750"
|
|
219
|
-
/>
|
|
220
|
-
</picture>
|
|
221
|
-
</a>
|
|
222
|
-
</p>
|
|
104
|
+
That is the entire public vocabulary. World access goes through `fx` — effects are inferred, so the Manifest can derive the typed client, Console, and MCP without a second source of truth.
|
|
223
105
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
106
|
+
| Element | Essence |
|
|
107
|
+
| ----------- | ----------------------------- |
|
|
108
|
+
| **Flow** | behavior |
|
|
109
|
+
| **Signal** | data in motion |
|
|
110
|
+
| **Store** | data at rest |
|
|
111
|
+
| **Clock** | time |
|
|
112
|
+
| **Gate** | permission to act |
|
|
113
|
+
| **Vault** | protected knowledge |
|
|
114
|
+
| **Channel** | reaching humans |
|
|
115
|
+
| **AI** | reaching machine intelligence |
|
|
116
|
+
|
|
117
|
+
Theory: [Introduction](https://oke.omqkhafi.dev/docs/get-started/introduction) · [Elements](https://oke.omqkhafi.dev/docs/elements) · [Why OKE](https://oke.omqkhafi.dev/docs/get-started/why).
|
|
118
|
+
|
|
119
|
+
## Next
|
|
120
|
+
|
|
121
|
+
| Want… | Go to |
|
|
122
|
+
| --------------------------- | ---------------------------------------------------------------------- |
|
|
123
|
+
| Scaffold details & modes | [Installation](https://oke.omqkhafi.dev/docs/get-started/installation) |
|
|
124
|
+
| Declarative CRUD | [`store.resource`](https://oke.omqkhafi.dev/docs/elements/store) |
|
|
125
|
+
| Auth, rate limits, sessions | [Gate](https://oke.omqkhafi.dev/docs/elements/gate) |
|
|
126
|
+
| Queues & live events | [Signal](https://oke.omqkhafi.dev/docs/elements/signal) |
|
|
127
|
+
| Email / SMS / push | [Channel](https://oke.omqkhafi.dev/docs/elements/channel) |
|
|
128
|
+
| `oke` commands | [CLI](https://oke.omqkhafi.dev/docs/reference/cli) |
|
|
129
|
+
|
|
130
|
+
Store apps sync schema with `oke db push|generate|migrate` (local `oke dev` auto-pushes). Prefer `store.schema.table` + `field.*`, or keep a hand-written Drizzle `schema.ts`.
|
|
239
131
|
|
|
240
132
|
## Documentation
|
|
241
133
|
|
|
242
|
-
|
|
134
|
+
| Resource | Link |
|
|
135
|
+
| ------------ | -------------------------------------------------------------- |
|
|
136
|
+
| Docs site | [oke.omqkhafi.dev](https://oke.omqkhafi.dev) |
|
|
137
|
+
| Get started | [/docs/get-started](https://oke.omqkhafi.dev/docs/get-started) |
|
|
138
|
+
| Handbook | [/docs](https://oke.omqkhafi.dev/docs) |
|
|
139
|
+
| Changelog | [/changelog](https://oke.omqkhafi.dev/changelog) |
|
|
140
|
+
| Agents index | [/llms.txt](https://oke.omqkhafi.dev/llms.txt) |
|
|
141
|
+
| Manifest | [`manifest.v1.schema.json`](manifest.v1.schema.json) |
|
|
142
|
+
| Budgets | [`BUDGETS.md`](BUDGETS.md) |
|
|
143
|
+
| Contributing | [`CONTRIBUTING.md`](CONTRIBUTING.md) |
|
|
243
144
|
|
|
244
|
-
|
|
245
|
-
| ------------- | ------------------------------------------------------------------ |
|
|
246
|
-
| Docs site | [oke.omqkhafi.dev](https://oke.omqkhafi.dev) |
|
|
247
|
-
| Handbook | [/docs](https://oke.omqkhafi.dev/docs) |
|
|
248
|
-
| Changelog | [/changelog](https://oke.omqkhafi.dev/changelog) |
|
|
249
|
-
| Agents index | [/llms.txt](https://oke.omqkhafi.dev/llms.txt) |
|
|
250
|
-
| CLI reference | [/docs/reference/cli](https://oke.omqkhafi.dev/docs/reference/cli) |
|
|
251
|
-
| Manifest | [`manifest.v1.schema.json`](manifest.v1.schema.json) |
|
|
252
|
-
| Budgets | [`BUDGETS.md`](BUDGETS.md) |
|
|
253
|
-
| Contributing | [`CONTRIBUTING.md`](CONTRIBUTING.md) |
|
|
145
|
+
## License
|
|
254
146
|
|
|
255
|
-
Pre-1.0. Published on [npm](https://www.npmjs.com/package/okengine) and [JSR](https://jsr.io/@omqkhafi/okengine). MIT.
|
|
147
|
+
Pre-1.0. Published on [npm](https://www.npmjs.com/package/okengine) and [JSR](https://jsr.io/@omqkhafi/okengine). [MIT](https://opensource.org/licenses/MIT).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "okengine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "One law. Eight elements. Ten exports. One package. One manifest. Every backend need is derived, never added.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"ajv-formats": "^3.0.1",
|
|
74
74
|
"intl-messageformat": "^11.2.13",
|
|
75
75
|
"oxc-parser": "^0.142.0",
|
|
76
|
-
"sently": "1.
|
|
76
|
+
"sently": "1.2.0"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@base-ui/react": "^1.6.0",
|
|
@@ -150,6 +150,6 @@
|
|
|
150
150
|
}
|
|
151
151
|
},
|
|
152
152
|
"engines": {
|
|
153
|
-
"bun": ">=1.3"
|
|
153
|
+
"bun": ">=1.3.14"
|
|
154
154
|
}
|
|
155
155
|
}
|
|
@@ -14,15 +14,35 @@ Answers: **upcoming crons, sleeping durable flows, journal**
|
|
|
14
14
|
## What this panel shows
|
|
15
15
|
|
|
16
16
|
<Cards>
|
|
17
|
+
<Card title="Schedules" description="Drift, overdue, missed runs · catch-up one, lease holder." />
|
|
18
|
+
<Card title="Pending wakes" description="Every sleeping durable flow and when it wakes." />
|
|
17
19
|
<Card
|
|
18
|
-
title="
|
|
19
|
-
description="
|
|
20
|
+
title="DST warnings"
|
|
21
|
+
description="Gap or overlap badge — detect only, no schedule rewrite."
|
|
20
22
|
/>
|
|
21
|
-
<Card title="
|
|
22
|
-
<Card title="DST warnings" description="Only when the schedule falls in an ambiguous window." />
|
|
23
|
-
<Card title="Actions" description="Run now, wake early, pause, edit when overridable." />
|
|
23
|
+
<Card title="Actions" description="Run now, wake early, pause; edit when overridable." />
|
|
24
24
|
</Cards>
|
|
25
25
|
|
|
26
|
+
## Health numbers
|
|
27
|
+
|
|
28
|
+
Each cron row projects four numbers plus the lease holder:
|
|
29
|
+
|
|
30
|
+
| Field | Meaning |
|
|
31
|
+
| --------------- | ----------------------------------------------------------------- |
|
|
32
|
+
| Drift | How far the last run sat from the expected slot |
|
|
33
|
+
| Overdue | Whether the schedule is past due |
|
|
34
|
+
| Missed + policy | Slot count while down · always catch-up `"one"` (one fire, not N) |
|
|
35
|
+
| Lease | Which instance holds the leader lease (empty when none) |
|
|
36
|
+
|
|
37
|
+
## Actions
|
|
38
|
+
|
|
39
|
+
| Action | When |
|
|
40
|
+
| ------------- | ---------------------------------------------------------------------- |
|
|
41
|
+
| Pause | Any cron — not gated by `overridable` |
|
|
42
|
+
| Edit schedule | Only when the declaration has `overridable: true`; else loud refuse |
|
|
43
|
+
| Wake early | Sleeping durable run — advances `wakeAt` and can resume the journal |
|
|
44
|
+
| Run now | Still requires the leader lease across replicas that share a CronStore |
|
|
45
|
+
|
|
26
46
|
## Catalog
|
|
27
47
|
|
|
28
48
|
<Cards>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Signals"
|
|
3
|
-
description: "Queue depth, in-flight, DLQ, live monitors."
|
|
3
|
+
description: "Queue depth, in-flight, DLQ, live monitors, orphaned config."
|
|
4
4
|
icon: "Radio"
|
|
5
5
|
source: "docs/spec/console.md"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
Answers: **queue depth, in-flight, DLQ, live monitors**
|
|
8
|
+
Answers: **queue depth, in-flight, DLQ, live monitors, orphaned config**
|
|
9
9
|
|
|
10
10
|
<Callout title="Governing rule">
|
|
11
11
|
One list grouped by delivery physics — not three tabs that re-split the element.
|
|
@@ -14,15 +14,24 @@ Answers: **queue depth, in-flight, DLQ, live monitors**
|
|
|
14
14
|
## What this panel shows
|
|
15
15
|
|
|
16
16
|
<Cards>
|
|
17
|
-
<Card title="once" description="Pending, in-flight, DLQ, retry policy." />
|
|
17
|
+
<Card title="once" description="Pending, in-flight (leased), DLQ, retry policy." />
|
|
18
18
|
<Card title="broadcast" description="Per-subscriber lag and targeted replay." />
|
|
19
|
-
<Card
|
|
19
|
+
<Card
|
|
20
|
+
title="live"
|
|
21
|
+
description="Connection count, throughput, newest-50 payload monitor (full history still retained)."
|
|
22
|
+
/>
|
|
20
23
|
<Card
|
|
21
24
|
title="DLQ repair"
|
|
22
25
|
description="Schema form, typed errors, causal chain, dry-run bulk replay."
|
|
23
26
|
/>
|
|
27
|
+
<Card
|
|
28
|
+
title="Orphaned"
|
|
29
|
+
description="Config still listed after the declaration left code — messages kept; dry-run refused."
|
|
30
|
+
/>
|
|
24
31
|
</Cards>
|
|
25
32
|
|
|
33
|
+
Dry-run is refused for orphaned signals and for signals with no Manifest consumer — unknown handler shapes are not invoked unsafely. Restore the `signal(…)` declaration (and a consumer) before replaying DLQ rows. Lease / at-least-once physics live on [Signal](/docs/elements/signal).
|
|
34
|
+
|
|
26
35
|
## Catalog
|
|
27
36
|
|
|
28
37
|
<Cards>
|
|
@@ -77,6 +77,8 @@ export const triage = smart.prompt("ticket-triage", {
|
|
|
77
77
|
| `ai.embed(name, opts)` | Embedding pipeline into a `store.index` (searched via `fx.search`) |
|
|
78
78
|
| `ai.agent(name, opts)` | Bounded agent whose tools are **your own flows** |
|
|
79
79
|
|
|
80
|
+
<AiBlocks />
|
|
81
|
+
|
|
80
82
|
### Prompt options
|
|
81
83
|
|
|
82
84
|
| Option | Type | Meaning |
|
|
@@ -86,6 +88,10 @@ export const triage = smart.prompt("ticket-triage", {
|
|
|
86
88
|
| `evals` | string | Path to a `.jsonl` eval set, regression-gated via `oke eval` |
|
|
87
89
|
| `budget` | object | `maxCostPerCall` — cost is a first-class dimension |
|
|
88
90
|
|
|
91
|
+
## Declared guardrails
|
|
92
|
+
|
|
93
|
+
Cost, egress, and model choice are contracts — not runtime hope. Each guardrail is declared up front; violating one fails the build or the run.
|
|
94
|
+
|
|
89
95
|
<AiGuardrails />
|
|
90
96
|
|
|
91
97
|
## Tools are flows (same `fx.call` path)
|
|
@@ -136,6 +142,8 @@ Use `keyBy: "ip"` on public unauthenticated AI edges. Cost caps stay on prompt/a
|
|
|
136
142
|
|
|
137
143
|
At build time, OKE checks which fields each `fx.ask` sends against your store classifications. If a PII field (`.pii()` in your schema) would reach a third-party model, the build fails:
|
|
138
144
|
|
|
145
|
+
<AiPiiEgress />
|
|
146
|
+
|
|
139
147
|
```text
|
|
140
148
|
build failed: flow "ticket-triage" sends pii field(s) [email] to a third-party model without allowPii
|
|
141
149
|
```
|
|
@@ -159,9 +167,28 @@ Because `fx.ask` is nondeterministic, the runtime adjusts around it: journaling
|
|
|
159
167
|
|
|
160
168
|
There is deliberately **no production default**: `prod` must name a driver, which keeps the model choice visible in `oke.config.ts` where review can see it.
|
|
161
169
|
|
|
170
|
+
### Setup wizard
|
|
171
|
+
|
|
172
|
+
```bash title="Terminal"
|
|
173
|
+
oke ai setup
|
|
174
|
+
oke ai setup --provider ollama --yes
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Writes `drivers.ai`, `.env.local`, and `src/ai.ts`.
|
|
178
|
+
|
|
179
|
+
Ollama: short quiz (use case · speed/quality · vision) → detect installed models
|
|
180
|
+
→ picks that fit your **machine tier** (~4GB OS/IDE headroom). Labels say
|
|
181
|
+
**≈16GB-class**, not “download 16GB” — pull size is usually much smaller than RAM.
|
|
182
|
+
|
|
183
|
+
Cloud: short chat list + **Other…** for any model id. **← Back** returns a step.
|
|
184
|
+
`create-oke` runs the same model wizard **before** install when you enable AI
|
|
185
|
+
(customize or `--ai`). Standalone `oke ai setup` remains for existing apps.
|
|
186
|
+
|
|
162
187
|
### Local Ollama
|
|
163
188
|
|
|
164
|
-
`ollama` is a local-model driver — any model
|
|
189
|
+
`ollama` is a local-model driver — any pulled model works. Pin `drivers.ai`;
|
|
190
|
+
optionally pin `images.ai` for compose. Native `complete`, `stream`, and
|
|
191
|
+
`embed` (`POST /api/embeddings`) — no OpenAI shim required for local RAG.
|
|
165
192
|
|
|
166
193
|
```typescript title="oke.config.ts"
|
|
167
194
|
import { defineConfig } from "okengine/config";
|
|
@@ -221,7 +248,7 @@ The one you declared. There is no fallback or guess — if `prod` has no AI driv
|
|
|
221
248
|
</Accordion>
|
|
222
249
|
<Accordion title="oke boot: ollama driver needs OKE_AI_URL">
|
|
223
250
|
|
|
224
|
-
Docker mode expects `OKE_AI_URL` from `docker/.env.docker` (written when `images.ai` is pinned). Locally, the default is `http://127.0.0.1:11434`. If asks fail with `OllamaUnavailableError`, confirm the server answers and that you have pulled the model you named (`ollama pull qwen3.5:9b`, or `qwen3.5:9b-mlx` on Apple Silicon, or your `OKE_AI_MODEL`).
|
|
251
|
+
Docker mode expects `OKE_AI_URL` from `docker/.env.docker` (written when `images.ai` is pinned). Locally, the default is `http://127.0.0.1:11434`. If asks fail with `OllamaUnavailableError`, confirm the server answers and that you have pulled the model you named (`ollama pull qwen3.5:9b`, or `qwen3.5:9b-mlx` on Apple Silicon, or your `OKE_AI_MODEL`). Prefer `oke ai setup` to wire driver, env, and `src/ai.ts` in one pass.
|
|
225
252
|
|
|
226
253
|
</Accordion>
|
|
227
254
|
<Accordion title="A prompt edit made answers worse — how do I catch that pre-merge?">
|
|
@@ -5,7 +5,7 @@ icon: "Mail"
|
|
|
5
5
|
source: "docs/spec/unified-theory.md"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
Channel is how your app **reaches humans**: the order-confirmation email, the OTP text, the WhatsApp notification. Reaching a person is not the same problem as moving data between machines — it needs consent (did they opt out?), locale (which language?), receipts (did it land?), and fallback (
|
|
8
|
+
Channel is how your app **reaches humans**: the order-confirmation email, the OTP text, the WhatsApp notification. Reaching a person is not the same problem as moving data between machines — it needs consent (did they opt out?), locale (which language?), receipts (did it land?), and fallback (first driver failed, try the next). Those physics are built into the element, so every flow gets them for free.
|
|
9
9
|
|
|
10
10
|
<Callout title="The one rule">
|
|
11
11
|
Sends go through `fx.send` and declared templates — never through a raw SMTP or provider client
|
|
@@ -83,29 +83,35 @@ Locally the `console` driver captures mail into an inbox instead of sending; in
|
|
|
83
83
|
|
|
84
84
|
### Consent is checked before sending
|
|
85
85
|
|
|
86
|
-
Opt-out is first-class: a subject who opted out of a medium is **suppressed** — the send resolves without contacting the provider, and the receipt
|
|
86
|
+
Opt-out is first-class: a subject who opted out of a medium is **suppressed** — the send resolves without contacting the provider, and the receipt status is `suppressed/opted-out`. You never hand-roll "did they unsubscribe?" checks.
|
|
87
87
|
|
|
88
|
-
### Locale resolves
|
|
88
|
+
### Locale resolves, then the catalog
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
`fx.send` picks a locale: explicit `locale` → `profileLocale` → first
|
|
91
|
+
`Accept-Language` tag → `defaultLocale`. The body is the catalog entry for
|
|
92
|
+
that exact key, else the default locale, else `en`.
|
|
92
93
|
|
|
93
94
|
Locales come from `oke.config.ts` `i18n`. Catalog bodies use `{{field}}` — not
|
|
94
95
|
ICU ([i18n](/docs/reference/i18n) / `fx.t` owns flow copy). Pass `locale` /
|
|
95
96
|
`profileLocale` / `acceptLanguage` on `fx.send`, or omit them for `fx.locale`.
|
|
96
97
|
|
|
97
|
-
### Fallback chains are
|
|
98
|
+
### Fallback chains are same-medium
|
|
98
99
|
|
|
99
|
-
`via` orders the
|
|
100
|
+
`via` orders **driver ids** for the template's medium — first success wins,
|
|
101
|
+
and **every** attempt is recorded:
|
|
100
102
|
|
|
101
103
|
```typescript
|
|
102
|
-
await fx.send(
|
|
103
|
-
|
|
104
|
+
await fx.send(orderConfirmed, {
|
|
105
|
+
to: user.email,
|
|
106
|
+
data: { name, orderId, total },
|
|
107
|
+
via: ["smtp", "resend"],
|
|
108
|
+
});
|
|
109
|
+
// smtp fails → resend tried → receipt status "fallback" with both attempts
|
|
104
110
|
```
|
|
105
111
|
|
|
106
112
|
### Receipts for everything
|
|
107
113
|
|
|
108
|
-
Each send records its attempts — driver, ok/error, timestamp, message id — so "did the user actually get it?" is a Console query, not a guess. In a dry run,
|
|
114
|
+
Each send records its attempts — driver, ok/error, timestamp, message id — so "did the user actually get it?" is a Console query, not a guess. In a dry run, the kernel records a would-have-fired send and never opens a provider — no Channel receipt is written.
|
|
109
115
|
|
|
110
116
|
## Per-environment drivers
|
|
111
117
|
|
|
@@ -126,23 +132,34 @@ images: {
|
|
|
126
132
|
},
|
|
127
133
|
```
|
|
128
134
|
|
|
129
|
-
| Driver
|
|
130
|
-
|
|
|
131
|
-
| `console`
|
|
132
|
-
| `smtp`
|
|
133
|
-
| `resend`
|
|
134
|
-
| `sndr`
|
|
135
|
-
| `taqnyat`
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
135
|
+
| Driver | Medium | Behavior |
|
|
136
|
+
| -------------- | -------- | ------------------------------------------------------------------- |
|
|
137
|
+
| `console` | any | Captures into a readable inbox — local + tests |
|
|
138
|
+
| `smtp` | email | Real SMTP — Mailpit in docker, your provider in prod |
|
|
139
|
+
| `resend` | email | Resend HTTP API (`RESEND_API_KEY`) |
|
|
140
|
+
| `sndr` | email | SNDR HTTP API (`SNDR_API_KEY`) |
|
|
141
|
+
| `taqnyat-mail` | email | Taqnyat Mail API (`TAQNYAT_MAIL_TOKEN` + `TAQNYAT_CAMPAIGN`) |
|
|
142
|
+
| `taqnyat` | sms | Taqnyat SMS (`TAQNYAT_BEARER_TOKEN` + `TAQNYAT_SENDER`) |
|
|
143
|
+
| `msegat` | sms | Msegat SMS (`MSEGAT_USERNAME` + `MSEGAT_API_KEY` + `MSEGAT_SENDER`) |
|
|
144
|
+
| `unifonic` | sms | Unifonic el.cloud SMS (`UNIFONIC_APPSID`) |
|
|
145
|
+
| `wa-cloud` | whatsapp | Meta WhatsApp Cloud API |
|
|
146
|
+
| `webpush` | push | Web Push + VAPID (`createPushSender` → `WebPushTransport`) |
|
|
147
|
+
| `fcm` | push | Firebase Cloud Messaging HTTP v1 |
|
|
141
148
|
|
|
142
149
|
Email fallback uses ordered email transports; SMS with multiple SMS drivers uses
|
|
143
150
|
the same failover path. Credentials for boot-opened drivers are listed under
|
|
144
151
|
[Environment Variables](/docs/reference/environment-variables).
|
|
145
152
|
|
|
153
|
+
### Provider-managed OTP
|
|
154
|
+
|
|
155
|
+
Flows can also send a provider-generated SMS code with `fx.sendOtp({ to, requestId, lang? })`
|
|
156
|
+
and check it with `fx.verifyOtp({ to, requestId, code })` — Taqnyat's Verify API. Both are
|
|
157
|
+
capability-gated `send` effects and dry-run safe.
|
|
158
|
+
|
|
159
|
+
They dispatch only when the bound SMS driver supports Verify (`taqnyat`); any other SMS
|
|
160
|
+
driver fails loudly instead of silently falling back to a self-generated code. The
|
|
161
|
+
[Phone number](/docs/plugins/phone-number) plugin uses this path automatically.
|
|
162
|
+
|
|
146
163
|
`webpush` needs VAPID keys — open it yourself and include it in
|
|
147
164
|
`BootOptions.channel.drivers` (boot does not open push from env):
|
|
148
165
|
|
|
@@ -171,7 +188,7 @@ Check consent first: if they opted out, sends to them are suppressed by design.
|
|
|
171
188
|
</Accordion>
|
|
172
189
|
<Accordion title="The Arabic version didn't render">
|
|
173
190
|
|
|
174
|
-
|
|
191
|
+
The send still succeeds: the body falls back to your default locale, then `en`, when the catalog misses that exact key. Check that an Arabic entry exists under the same tag you send (`ar` vs `ar-SA`) and that the template lists it in `locales`.
|
|
175
192
|
|
|
176
193
|
</Accordion>
|
|
177
194
|
<Accordion title="How do I ingest SNDR / Unifonic delivery webhooks?">
|
|
@@ -190,9 +207,9 @@ Verify the signature when the provider sends one, parse the body, then call
|
|
|
190
207
|
Console reads the receipt ledger — never raw webhook JSON.
|
|
191
208
|
|
|
192
209
|
</Accordion>
|
|
193
|
-
<Accordion title="How do I know which
|
|
210
|
+
<Accordion title="How do I know which driver finally delivered?">
|
|
194
211
|
|
|
195
|
-
The receipt keeps every attempt in order with its outcome. A send that succeeded on a later
|
|
212
|
+
The receipt keeps every attempt in order with its outcome. A send that succeeded on a later driver reports status `fallback` — you can see the full chain in Console → Channels.
|
|
196
213
|
|
|
197
214
|
</Accordion>
|
|
198
215
|
<Accordion title="How do I verify Web Push against a real browser?">
|