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.
Files changed (167) hide show
  1. package/AGENTS.md +2 -2
  2. package/README.md +73 -181
  3. package/package.json +3 -3
  4. package/site/content/docs/console/clock.mdx +25 -5
  5. package/site/content/docs/console/signals.mdx +13 -4
  6. package/site/content/docs/elements/ai.mdx +29 -2
  7. package/site/content/docs/elements/channel.mdx +42 -25
  8. package/site/content/docs/elements/clock.mdx +59 -25
  9. package/site/content/docs/elements/flow.mdx +8 -2
  10. package/site/content/docs/elements/gate.mdx +8 -1
  11. package/site/content/docs/elements/signal.mdx +173 -29
  12. package/site/content/docs/elements/store.mdx +595 -73
  13. package/site/content/docs/elements/vault.mdx +15 -5
  14. package/site/content/docs/get-started/basic-usage.mdx +13 -6
  15. package/site/content/docs/get-started/installation.mdx +52 -14
  16. package/site/content/docs/get-started/introduction.mdx +1 -1
  17. package/site/content/docs/plugins/magic-link.mdx +42 -0
  18. package/site/content/docs/plugins/phone-number.mdx +78 -17
  19. package/site/content/docs/plugins/two-factor.mdx +1 -0
  20. package/site/content/docs/reference/cli.md +32 -2
  21. package/site/content/docs/reference/configuration.mdx +15 -13
  22. package/site/content/docs/reference/environment-variables.mdx +32 -18
  23. package/site/content/docs/reference/errors.mdx +12 -11
  24. package/site/content/docs/reference/fx.mdx +15 -13
  25. package/site/content/docs/reference/security.md +3 -1
  26. package/src/cli/ai-setup/ai-setup.test.ts +144 -0
  27. package/src/cli/ai-setup/apply.ts +214 -0
  28. package/src/cli/ai-setup/catalog.ts +263 -0
  29. package/src/cli/ai-setup/detect-ollama.ts +166 -0
  30. package/src/cli/ai-setup/index.ts +228 -0
  31. package/src/cli/ai-setup/prompts.ts +649 -0
  32. package/src/cli/ai-setup/recommend.test.ts +100 -0
  33. package/src/cli/ai-setup/recommend.ts +203 -0
  34. package/src/cli/ai.ts +38 -0
  35. package/src/cli/db-auto-push.test.ts +6 -1
  36. package/src/cli/db-auto-push.ts +8 -3
  37. package/src/cli/db-seed.ts +359 -0
  38. package/src/cli/db.test.ts +341 -3
  39. package/src/cli/db.ts +75 -8
  40. package/src/cli/dev-db-push.test.ts +59 -0
  41. package/src/cli/dev.test.ts +40 -0
  42. package/src/cli/dev.ts +55 -1
  43. package/src/cli/docker-clean.test.ts +179 -0
  44. package/src/cli/docker-clean.ts +406 -0
  45. package/src/cli/docker.ts +16 -4
  46. package/src/cli/ensure-drizzle-config.ts +1 -1
  47. package/src/cli/hero-meta.test.ts +6 -5
  48. package/src/cli/hero-meta.ts +7 -1
  49. package/src/cli/index.ts +5 -0
  50. package/src/cli/load-config.images.test.ts +22 -0
  51. package/src/cli/load-config.ts +7 -2
  52. package/src/cli/openbao-bootstrap.test.ts +1 -0
  53. package/src/cli/openbao-bootstrap.ts +9 -2
  54. package/src/cli/registry.ts +116 -2
  55. package/src/client/live-gap.test.ts +35 -0
  56. package/src/client/transport.test.ts +21 -0
  57. package/src/client/transport.ts +27 -5
  58. package/src/compiler/effects-infer.ts +1 -0
  59. package/src/compiler/fixtures/skyport/oke.config.ts +1 -1
  60. package/src/config/define-config.test.ts +6 -6
  61. package/src/config/index.ts +5 -1
  62. package/src/console/server/console.test.ts +45 -0
  63. package/src/console/server/flows.ts +39 -9
  64. package/src/console/server/i18n.ts +41 -0
  65. package/src/console/server/vault.ts +12 -38
  66. package/src/console/ui/dist/assets/index-ClX0ZCe5.js +10 -0
  67. package/src/console/ui/dist/assets/{panel-access-BGv45snf.js → panel-access-ri1X9Otj.js} +1 -1
  68. package/src/console/ui/dist/assets/{panel-ai-B2S7LEii.js → panel-ai-DLHjRoJk.js} +1 -1
  69. package/src/console/ui/dist/assets/{panel-architecture-D7UJh91v.js → panel-architecture-DI3bQF3J.js} +1 -1
  70. package/src/console/ui/dist/assets/{panel-channels-9T3ybqRu.js → panel-channels-BDYWX29p.js} +1 -1
  71. package/src/console/ui/dist/assets/{panel-clock-Cb1UXGRQ.js → panel-clock-i-JpTjVB.js} +1 -1
  72. package/src/console/ui/dist/assets/{panel-diff-DmYbKWmN.js → panel-diff-CFuWxiXy.js} +1 -1
  73. package/src/console/ui/dist/assets/{panel-flows-PiHwT55z.js → panel-flows-B8TRhcoe.js} +1 -1
  74. package/src/console/ui/dist/assets/{panel-gates-BQGYXvjT.js → panel-gates-CfkBgco7.js} +1 -1
  75. package/src/console/ui/dist/assets/{panel-overview-BBnRO18l.js → panel-overview-DONDzBd2.js} +1 -1
  76. package/src/console/ui/dist/assets/{panel-plugins-D0PsmVw2.js → panel-plugins-DDJwTehL.js} +1 -1
  77. package/src/console/ui/dist/assets/{panel-runs-CWuRDe0r.js → panel-runs-D5zf-D9c.js} +1 -1
  78. package/src/console/ui/dist/assets/{panel-signals-Bbg4ewpP.js → panel-signals-Dsxdu_AR.js} +1 -1
  79. package/src/console/ui/dist/assets/{panel-store-CPCbsDRa.js → panel-store-hy7O8HOs.js} +1 -1
  80. package/src/console/ui/dist/assets/{panel-traces-DVAzuA_S.js → panel-traces-CBiAe5go.js} +1 -1
  81. package/src/console/ui/dist/assets/{panel-vault-D1_MvOmo.js → panel-vault-Za1GyJfM.js} +1 -1
  82. package/src/console/ui/dist/index.html +1 -1
  83. package/src/console/ui/shell/components/ui.tsx +5 -1
  84. package/src/console/ui/shell/setup/Wizard.tsx +26 -5
  85. package/src/docker/cleanup.test.ts +193 -0
  86. package/src/docker/cleanup.ts +355 -0
  87. package/src/docker/docker.test.ts +4 -0
  88. package/src/docker/index.ts +19 -0
  89. package/src/docker/recipes/ollama.ts +10 -5
  90. package/src/drivers/ai-ollama.ts +45 -2
  91. package/src/drivers/ai-providers.test.ts +31 -0
  92. package/src/drivers/channel-sently.test.ts +8 -0
  93. package/src/drivers/channel-taqnyat-mail.ts +34 -0
  94. package/src/drivers/channel-types.ts +71 -0
  95. package/src/drivers/clock-postgres.test.ts +258 -0
  96. package/src/drivers/clock-postgres.ts +410 -0
  97. package/src/drivers/index.ts +21 -0
  98. package/src/drivers/journal-postgres.test.ts +175 -0
  99. package/src/drivers/journal-postgres.ts +492 -0
  100. package/src/drivers/signal-engine.ts +98 -25
  101. package/src/drivers/signal-nats.ts +4 -4
  102. package/src/drivers/signal-postgres.ts +158 -64
  103. package/src/drivers/signal-redis.ts +4 -4
  104. package/src/drivers/signal-types.ts +61 -2
  105. package/src/drivers/vault-driver-removal.test.ts +5 -1
  106. package/src/elements/channel/runtime.ts +51 -0
  107. package/src/elements/channel.test.ts +71 -0
  108. package/src/elements/clock/chaos-child.ts +389 -0
  109. package/src/elements/clock/chaos.test.ts +204 -0
  110. package/src/elements/clock/durable.ts +7 -0
  111. package/src/elements/clock/reconcile.ts +110 -0
  112. package/src/elements/clock/runtime.ts +13 -0
  113. package/src/elements/clock.test.ts +164 -2
  114. package/src/elements/clock.ts +2 -1
  115. package/src/elements/gate/runtime.ts +6 -0
  116. package/src/elements/signal/chaos-child.ts +41 -4
  117. package/src/elements/signal/declare.ts +4 -1
  118. package/src/elements/signal/delivery-modes.test.ts +179 -0
  119. package/src/elements/signal/key-ordering.test.ts +306 -0
  120. package/src/elements/signal/lease-reclaim.test.ts +123 -0
  121. package/src/elements/signal/optional-emit.test.ts +76 -0
  122. package/src/elements/signal/order-lifecycle.test.ts +130 -0
  123. package/src/elements/signal/orphan-messages.test.ts +152 -0
  124. package/src/elements/signal/runtime.ts +11 -4
  125. package/src/elements/signal/schema-emit.test.ts +107 -0
  126. package/src/elements/signal.test.ts +89 -5
  127. package/src/elements/store/files-fx.ts +104 -0
  128. package/src/elements/store/files-image.test.ts +251 -0
  129. package/src/elements/store/files-image.ts +494 -0
  130. package/src/elements/store/runtime.ts +60 -0
  131. package/src/elements/store/seed.test.ts +27 -0
  132. package/src/elements/store/seed.ts +68 -0
  133. package/src/elements/store/sql-session.test.ts +39 -0
  134. package/src/elements/store/sql-session.ts +55 -0
  135. package/src/elements/store/upsert-app.test.ts +103 -0
  136. package/src/elements/store.ts +29 -0
  137. package/src/elements/vault/boot-chain.ts +150 -0
  138. package/src/elements/vault/runtime.ts +8 -0
  139. package/src/index.ts +15 -0
  140. package/src/kernel/app.ts +168 -15
  141. package/src/kernel/boot-bind/channel.test.ts +16 -0
  142. package/src/kernel/boot-bind/channel.ts +13 -0
  143. package/src/kernel/boot-bind/clock.ts +63 -7
  144. package/src/kernel/boot-bind/gate.ts +71 -3
  145. package/src/kernel/boot-bind/honor-config.test.ts +348 -0
  146. package/src/kernel/boot-bind/journal.ts +89 -0
  147. package/src/kernel/boot-bind/signal.ts +64 -7
  148. package/src/kernel/boot-bind/vault.ts +27 -8
  149. package/src/kernel/boot.test.ts +6 -4
  150. package/src/kernel/boot.ts +63 -15
  151. package/src/kernel/concurrency.ts +1 -1
  152. package/src/kernel/errors.ts +8 -2
  153. package/src/kernel/fx.test.ts +19 -0
  154. package/src/kernel/fx.ts +153 -9
  155. package/src/kernel/index.ts +6 -0
  156. package/src/kernel/journal-boot.test.ts +397 -0
  157. package/src/kernel/journal-suspend.ts +35 -0
  158. package/src/kernel/journal.test.ts +142 -0
  159. package/src/kernel/journal.ts +202 -27
  160. package/src/plugins/auth-methods.security.test.ts +10 -7
  161. package/src/plugins/phone-number.ts +67 -10
  162. package/src/plugins/taqnyat.live.test.ts +174 -0
  163. package/src/runtime/dev-request-log.test.ts +20 -1
  164. package/src/runtime/dev-request-log.ts +38 -0
  165. package/src/term.test.ts +15 -0
  166. package/src/term.ts +8 -3
  167. 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 one standard starter from `packages/create-oke/template`.
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
- <picture>
18
- <source media="(prefers-color-scheme: dark)" srcset="https://shieldcn.dev/npm/okengine.svg?variant=outline&mode=dark&size=sm&theme=zinc&font=geist" />
19
- <img alt="npm" src="https://shieldcn.dev/npm/okengine.svg?variant=outline&mode=light&size=sm&theme=zinc&font=geist" />
20
- </picture>
21
- </a>
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>oke</code> CLI ·
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
- Every backend behavior is a Flow: `on(Trigger) Effects`. World access goes through `fx`.
69
+ The starter already exports a health Flow change it and save; App and Console update from the same Manifest:
121
70
 
122
- Store apps sync schema with `oke db push|generate|migrate` (local `oke dev`
123
- auto-pushes). Prefer `store.schema.table` + `field.*` or keep a
124
- hand-written Drizzle `schema.ts`.
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
- ### Resource Flow
84
+ Call it with a typed client (contracts inferred — no separate codegen):
127
85
 
128
86
  ```typescript
129
- import { on, http, store } from "okengine";
130
- import { createInsertSchema, createSelectSchema } from "drizzle-zod";
131
- import { db } from "../../core";
132
- import { notes as notesTable } from "../../schema";
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
- ## Charts
96
+ ## One law
175
97
 
176
- <p align="center">
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
- <p align="center">
193
- <a href="https://github.com/omqkhafi">
194
- <picture>
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
- <p align="center">
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
- <p align="center">
225
- <a href="https://www.npmjs.com/package/create-oke">
226
- <picture>
227
- <source
228
- media="(prefers-color-scheme: dark)"
229
- srcset="https://shieldcn.dev/chart/npm/create-oke.svg?theme=zinc&font=geist&mode=dark"
230
- />
231
- <img
232
- alt="create-oke npm downloads"
233
- src="https://shieldcn.dev/chart/npm/create-oke.svg?theme=zinc&font=geist&mode=light"
234
- width="750"
235
- />
236
- </picture>
237
- </a>
238
- </p>
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
- Everything beyond this page lives in the site handbook:
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
- | Resource | Link |
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.6.1",
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.0.0"
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="Schedules"
19
- description="Recurring crons with drift, overdue, catch-up, lease holder."
20
+ title="DST warnings"
21
+ description="Gap or overlap badge detect only, no schedule rewrite."
20
22
  />
21
- <Card title="Pending wakes" description="Every sleeping durable flow and when it wakes." />
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 title="live" description="Connection count, throughput, payload monitor." />
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 you have pulled works. Pin it in `drivers.ai`; optionally pin `images.ai` for compose.
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 (email failed, try SMS). Those physics are built into the element, so every flow gets them for free.
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 says so. You never hand-roll "did they unsubscribe?" checks.
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 through a chain
88
+ ### Locale resolves, then the catalog
89
89
 
90
- Templates render per recipient locale, falling back through your configured chain
91
- (`ar` → default `en`) instead of failing when a translation is missing.
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 explicit
98
+ ### Fallback chains are same-medium
98
99
 
99
- `via` orders the mediums to try — first success wins, and **every** attempt is recorded:
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(otpCode, { to: user.phone, data: { code }, via: [sms, wa] });
103
- // sms fails → whatsapp tried → receipt status "fallback" with both attempts
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, sends are recorded as _would have fired_ and never contact a real provider.
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 | Medium | Behavior |
130
- | ---------- | -------- | ------------------------------------------------------------------- |
131
- | `console` | any | Captures into a readable inbox — local + tests |
132
- | `smtp` | email | Real SMTP — Mailpit in docker, your provider in prod |
133
- | `resend` | email | Resend HTTP API (`RESEND_API_KEY`) |
134
- | `sndr` | email | SNDR HTTP API (`SNDR_API_KEY`) |
135
- | `taqnyat` | sms | Taqnyat SMS (`TAQNYAT_BEARER_TOKEN` + `TAQNYAT_SENDER`) |
136
- | `msegat` | sms | Msegat SMS (`MSEGAT_USERNAME` + `MSEGAT_API_KEY` + `MSEGAT_SENDER`) |
137
- | `unifonic` | sms | Unifonic el.cloud SMS (`UNIFONIC_APPSID`) |
138
- | `wa-cloud` | whatsapp | Meta WhatsApp Cloud API |
139
- | `webpush` | push | Web Push + VAPID (`createPushSender` → `WebPushTransport`) |
140
- | `fcm` | push | Firebase Cloud Messaging HTTP v1 |
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
- Locale resolution falls back through the chain to your default locale when a translation is missing the send still succeeds with the fallback body. Check that the template declares `locales: ["en", "ar"]` and that the Arabic body exists in the catalog.
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 medium finally delivered?">
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 medium reports status `fallback` — you can see the full chain in Console → Channels.
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?">