okengine 0.3.5 → 0.4.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.
Files changed (144) hide show
  1. package/AGENTS.md +6 -0
  2. package/manifest.v1.schema.json +43 -1
  3. package/package.json +27 -12
  4. package/site/content/docs/ai/skills.mdx +11 -7
  5. package/site/content/docs/console/vault.mdx +4 -0
  6. package/site/content/docs/elements/ai.mdx +2 -0
  7. package/site/content/docs/elements/channel.mdx +7 -4
  8. package/site/content/docs/elements/clock.mdx +2 -4
  9. package/site/content/docs/elements/flow.mdx +19 -10
  10. package/site/content/docs/elements/gate.mdx +18 -11
  11. package/site/content/docs/elements/signal.mdx +9 -12
  12. package/site/content/docs/elements/store.mdx +69 -8
  13. package/site/content/docs/elements/vault.mdx +11 -12
  14. package/site/content/docs/get-started/basic-usage.mdx +76 -41
  15. package/site/content/docs/get-started/installation.mdx +95 -43
  16. package/site/content/docs/get-started/introduction.mdx +128 -75
  17. package/site/content/docs/get-started/meta.json +1 -1
  18. package/site/content/docs/get-started/why.mdx +141 -0
  19. package/site/content/docs/plugins/ip-allowlist.mdx +1 -2
  20. package/site/content/docs/plugins/security-headers.mdx +1 -1
  21. package/site/content/docs/reference/configuration.mdx +2 -2
  22. package/site/content/docs/reference/environment-variables.mdx +10 -0
  23. package/site/content/docs/reference/fx.mdx +56 -3
  24. package/site/content/docs/reference/plugins.mdx +18 -18
  25. package/src/cli/competitor-mention-removal.test.ts +117 -0
  26. package/src/cli/dev.ts +20 -0
  27. package/src/cli/meilisearch-local.test.ts +69 -0
  28. package/src/cli/meilisearch-local.ts +188 -0
  29. package/src/compiler/extract.test.ts +63 -0
  30. package/src/compiler/extract.ts +36 -15
  31. package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +5 -1
  32. package/src/console/server/channels.ts +2 -0
  33. package/src/console/server/clock.ts +3 -0
  34. package/src/console/server/flows.ts +13 -0
  35. package/src/console/server/gates.ts +2 -0
  36. package/src/console/server/plugins.ts +20 -1
  37. package/src/console/server/signals.ts +5 -0
  38. package/src/console/server/store.test.ts +17 -0
  39. package/src/console/server/store.ts +35 -1
  40. package/src/console/ui/channels/types.ts +1 -0
  41. package/src/console/ui/clock/types.ts +1 -0
  42. package/src/console/ui/display.test.ts +14 -0
  43. package/src/console/ui/display.ts +9 -0
  44. package/src/console/ui/dist/assets/index-CjxwRGVv.js +10 -0
  45. package/src/console/ui/dist/assets/panel-access-BGv45snf.js +64 -0
  46. package/src/console/ui/dist/assets/{panel-ai-D_m6WQI8.js → panel-ai-B2S7LEii.js} +1 -1
  47. package/src/console/ui/dist/assets/{panel-architecture-CKnXFyUx.js → panel-architecture-D7UJh91v.js} +1 -1
  48. package/src/console/ui/dist/assets/panel-channels-9T3ybqRu.js +1 -0
  49. package/src/console/ui/dist/assets/panel-clock-Cb1UXGRQ.js +1 -0
  50. package/src/console/ui/dist/assets/{panel-diff-cdonmH8c.js → panel-diff-DmYbKWmN.js} +1 -1
  51. package/src/console/ui/dist/assets/panel-flows-PiHwT55z.js +48 -0
  52. package/src/console/ui/dist/assets/panel-gates-BQGYXvjT.js +1 -0
  53. package/src/console/ui/dist/assets/panel-overview-BBnRO18l.js +1 -0
  54. package/src/console/ui/dist/assets/panel-plugins-D0PsmVw2.js +1 -0
  55. package/src/console/ui/dist/assets/panel-runs-CWuRDe0r.js +1 -0
  56. package/src/console/ui/dist/assets/panel-signals-Bbg4ewpP.js +1 -0
  57. package/src/console/ui/dist/assets/panel-store-CPCbsDRa.js +1 -0
  58. package/src/console/ui/dist/assets/panel-traces-DVAzuA_S.js +1 -0
  59. package/src/console/ui/dist/assets/panel-vault-D1_MvOmo.js +1 -0
  60. package/src/console/ui/dist/assets/{rolldown-runtime-CNC7AqOf.js → rolldown-runtime-B0Z9INg1.js} +1 -1
  61. package/src/console/ui/dist/index.html +2 -2
  62. package/src/console/ui/gates/types.ts +1 -0
  63. package/src/console/ui/plugins/fixture.ts +7 -0
  64. package/src/console/ui/plugins/types.ts +3 -0
  65. package/src/console/ui/shell/client.ts +3 -0
  66. package/src/console/ui/shell/panels/channels/ChannelsPanel.tsx +7 -2
  67. package/src/console/ui/shell/panels/clock/ClockPanel.tsx +9 -2
  68. package/src/console/ui/shell/panels/gates/GatesPanel.tsx +12 -5
  69. package/src/console/ui/shell/panels/plugins/PluginsPanel.tsx +18 -4
  70. package/src/console/ui/shell/panels/signals/SignalsPanel.tsx +13 -2
  71. package/src/console/ui/shell/panels/store/StorePanel.tsx +11 -4
  72. package/src/console/ui/shell/panels/vault/VaultPanel.tsx +9 -3
  73. package/src/console/ui/signals/types.ts +1 -0
  74. package/src/console/ui/store/fixture.ts +5 -0
  75. package/src/console/ui/store/types.ts +2 -0
  76. package/src/docker/compose.ts +5 -0
  77. package/src/docker/docker.test.ts +41 -0
  78. package/src/docker/recipes/index.ts +10 -2
  79. package/src/docker/recipes/meilisearch.ts +31 -0
  80. package/src/drivers/conformance.test.ts +26 -0
  81. package/src/drivers/conformance.ts +40 -3
  82. package/src/drivers/drizzle-dialect.test.ts +4 -0
  83. package/src/drivers/drizzle-dialect.ts +8 -4
  84. package/src/drivers/index.ts +18 -2
  85. package/src/drivers/libsql.ts +179 -0
  86. package/src/drivers/meilisearch.integration.test.ts +77 -0
  87. package/src/drivers/meilisearch.test.ts +181 -0
  88. package/src/drivers/meilisearch.ts +208 -0
  89. package/src/drivers/memory.ts +4 -4
  90. package/src/drivers/pglite.ts +79 -0
  91. package/src/drivers/pgvector.ts +60 -25
  92. package/src/drivers/types.ts +106 -16
  93. package/src/drivers/vault-driver-removal.test.ts +6 -0
  94. package/src/drivers/vault-types.ts +4 -4
  95. package/src/elements/ai/runtime.ts +6 -0
  96. package/src/elements/ai.test.ts +22 -0
  97. package/src/elements/channel/declare.ts +5 -0
  98. package/src/elements/clock/declare.ts +5 -0
  99. package/src/elements/clock/durable.ts +7 -1
  100. package/src/elements/gate/declare.ts +28 -5
  101. package/src/elements/gate.ts +1 -0
  102. package/src/elements/signal/declare.ts +5 -0
  103. package/src/elements/store/declare.ts +10 -2
  104. package/src/elements/store/index-boot.test.ts +299 -0
  105. package/src/elements/store/runtime.ts +110 -17
  106. package/src/elements/store/schema-decl.ts +7 -0
  107. package/src/elements/store.ts +2 -0
  108. package/src/elements/vault.test.ts +27 -4
  109. package/src/elements/vault.ts +1 -1
  110. package/src/index.ts +4 -0
  111. package/src/kernel/abort-scope.ts +116 -0
  112. package/src/kernel/app.ts +12 -2
  113. package/src/kernel/boot-bind/store.test.ts +68 -1
  114. package/src/kernel/boot-bind/store.ts +92 -2
  115. package/src/kernel/concurrency.test.ts +294 -0
  116. package/src/kernel/concurrency.ts +220 -0
  117. package/src/kernel/flow.ts +9 -0
  118. package/src/kernel/fx.test.ts +23 -2
  119. package/src/kernel/fx.ts +80 -4
  120. package/src/kernel/index.ts +23 -0
  121. package/src/kernel/journal.ts +9 -0
  122. package/src/kernel/plugin/capabilities.test.ts +18 -0
  123. package/src/kernel/plugin.ts +11 -3
  124. package/src/kernel/redacted.ts +74 -0
  125. package/src/kernel/registry.ts +29 -6
  126. package/src/kernel/router.ts +3 -3
  127. package/src/manifest/types.ts +21 -0
  128. package/src/release/measure.ts +4 -0
  129. package/src/test/provisions.integration.test.ts +1 -1
  130. package/site/content/docs/get-started/comparison.mdx +0 -65
  131. package/src/console/ui/dist/assets/index-BWo8R7NR.js +0 -10
  132. package/src/console/ui/dist/assets/panel-access-C0J2D-a2.js +0 -64
  133. package/src/console/ui/dist/assets/panel-channels-BOmQ-onL.js +0 -1
  134. package/src/console/ui/dist/assets/panel-clock-giAq0Ccv.js +0 -1
  135. package/src/console/ui/dist/assets/panel-flows-DlCU5zjA.js +0 -45
  136. package/src/console/ui/dist/assets/panel-gates-XclZxWD5.js +0 -1
  137. package/src/console/ui/dist/assets/panel-overview-BznEOTnb.js +0 -1
  138. package/src/console/ui/dist/assets/panel-plugins-CcGM1g64.js +0 -1
  139. package/src/console/ui/dist/assets/panel-runs-CGWNHLR4.js +0 -1
  140. package/src/console/ui/dist/assets/panel-signals-CNywkdak.js +0 -1
  141. package/src/console/ui/dist/assets/panel-store-KmTbFHMH.js +0 -1
  142. package/src/console/ui/dist/assets/panel-traces-DBLx2ilD.js +0 -1
  143. package/src/console/ui/dist/assets/panel-vault-CEnFc0dk.js +0 -1
  144. package/src/drivers/vault-infisical.ts +0 -57
package/src/kernel/fx.ts CHANGED
@@ -32,9 +32,21 @@ import {
32
32
  touchDryRunStore,
33
33
  } from "./dry-run.ts";
34
34
  import { fail, type FailOptions, type FlowFailure } from "./errors.ts";
35
+ import { currentAbortSignal } from "./abort-scope.ts";
36
+ import {
37
+ fxAll,
38
+ fxRace,
39
+ fxRetry,
40
+ fxUsing,
41
+ type FxRetryOptions,
42
+ type FxThunk,
43
+ } from "./concurrency.ts";
44
+ import { maskRedactedDeep, Redacted } from "./redacted.ts";
35
45
  import type { JournalSession } from "./journal.ts";
36
46
  import type { RunTelemetry } from "./run-telemetry.ts";
37
47
 
48
+ export type { FxRetryOptions, FxThunk } from "./concurrency.ts";
49
+
38
50
  /** Named ref: plain string or `{ name }` element handle. */
39
51
  export type NamedRef = string | { readonly name: string };
40
52
 
@@ -258,9 +270,13 @@ export interface Fx {
258
270
  /**
259
271
  * Read a vault secret (records `secret`).
260
272
  *
273
+ * Returns a {@link Redacted} — printing / logging / serializing it yields a
274
+ * placeholder, never the value. Call `.reveal()` at the one boundary that
275
+ * needs the real value (e.g. passing a credential to a driver).
276
+ *
261
277
  * @param secret - Secret name or handle
262
278
  */
263
- vault(secret: NamedRef): string;
279
+ vault(secret: NamedRef): Redacted<string>;
264
280
  /** Cache surface. */
265
281
  readonly cache: FxCache;
266
282
  /**
@@ -337,6 +353,48 @@ export interface Fx {
337
353
  * @param fn - Step body
338
354
  */
339
355
  step<T>(name: string, fn: () => T | Promise<T>): Promise<T>;
356
+ /**
357
+ * Ambient abort signal for the current structured-concurrency branch.
358
+ * Outside `all` / `race`, a never-aborted signal.
359
+ */
360
+ readonly signal: AbortSignal;
361
+ /**
362
+ * Run thunks in parallel. On first rejection, abort sibling branches and
363
+ * rethrow. Pass thunks (not started Promises) so abort scopes exist first.
364
+ *
365
+ * @param thunks - Parallel work units
366
+ */
367
+ all<const T extends readonly unknown[]>(thunks: {
368
+ readonly [K in keyof T]: FxThunk<T[K]>;
369
+ }): Promise<{ -readonly [K in keyof T]: Awaited<T[K]> }>;
370
+ /**
371
+ * Race thunks. The first settle wins; losers are aborted.
372
+ *
373
+ * @param thunks - Competing work units
374
+ */
375
+ race<T>(thunks: ReadonlyArray<FxThunk<T>>): Promise<T>;
376
+ /**
377
+ * Retry a thunk with exponential backoff and optional full jitter.
378
+ * Prefer wrapping inside {@link Fx.step} so durable replay skips completed work.
379
+ *
380
+ * @param fn - Operation
381
+ * @param opts - Retry policy
382
+ */
383
+ retry<T>(fn: FxThunk<T>, opts?: FxRetryOptions): Promise<T>;
384
+ /**
385
+ * Scope a resource to `use` — `release` runs exactly once when `use`
386
+ * settles or the ambient abort signal fires (e.g. a sibling `fx.race`
387
+ * winner). Same-attempt cleanup; not journaled.
388
+ *
389
+ * @param acquire - Open the resource
390
+ * @param release - Cleanup, always run
391
+ * @param use - Work with the resource
392
+ */
393
+ using<A, T>(
394
+ acquire: () => A | Promise<A>,
395
+ release: (resource: A) => void | Promise<void>,
396
+ use: (resource: A) => T | Promise<T>,
397
+ ): Promise<T>;
340
398
  }
341
399
 
342
400
  /**
@@ -818,9 +876,12 @@ export function createFxContext(options: CreateFxOptions): FxContext {
818
876
  data?: Record<string, unknown>,
819
877
  ): { message: string; data?: Record<string, unknown> } {
820
878
  const vault = options.vaultRuntime;
821
- if (!vault) return { message, data };
879
+ // Redacted<T> never yields the real value, but replace instances with a
880
+ // placeholder so payloads stay plain JSON (and never re-wrap on replay).
881
+ const maskedData = data ? maskRedactedDeep(data) : undefined;
882
+ if (!vault) return { message, data: maskedData };
822
883
  const safeMessage = vault.redactString(message);
823
- const safeData = data ? (vault.redact(data) as Record<string, unknown>) : undefined;
884
+ const safeData = maskedData ? (vault.redact(maskedData) as Record<string, unknown>) : undefined;
824
885
  return { message: safeMessage, data: safeData };
825
886
  }
826
887
 
@@ -893,7 +954,7 @@ export function createFxContext(options: CreateFxOptions): FxContext {
893
954
  duration: Math.max(0, now() - timestamp),
894
955
  reversibility: reversibilityOf("secret"),
895
956
  });
896
- return value;
957
+ return new Redacted(value);
897
958
  },
898
959
  cache,
899
960
  send(template, opts) {
@@ -1035,6 +1096,21 @@ export function createFxContext(options: CreateFxOptions): FxContext {
1035
1096
  }
1036
1097
  return await fn();
1037
1098
  },
1099
+ get signal(): AbortSignal {
1100
+ return currentAbortSignal();
1101
+ },
1102
+ all(thunks) {
1103
+ return fxAll(thunks);
1104
+ },
1105
+ race(thunks) {
1106
+ return fxRace(thunks);
1107
+ },
1108
+ retry(fn, opts) {
1109
+ return fxRetry(fn, opts);
1110
+ },
1111
+ using(acquire, release, use) {
1112
+ return fxUsing(acquire, release, use);
1113
+ },
1038
1114
  };
1039
1115
 
1040
1116
  return { fx, ledger, capability };
@@ -118,14 +118,36 @@ export {
118
118
  type FxJson,
119
119
  type FxLog,
120
120
  type FxOperator,
121
+ type FxRetryOptions,
121
122
  type FxSearchOptions,
122
123
  type FxSendOptions,
123
124
  type FxStoreHandle,
124
125
  type FxTenant,
126
+ type FxThunk,
125
127
  type JsonResult,
126
128
  type NamedRef,
127
129
  } from "./fx.ts";
128
130
 
131
+ export {
132
+ abortError,
133
+ abortableSleep,
134
+ currentAbortSignal,
135
+ isAbortError,
136
+ linkAbort,
137
+ withAbortSignal,
138
+ } from "./abort-scope.ts";
139
+
140
+ export {
141
+ defaultRetryWhen,
142
+ fxAll,
143
+ fxRace,
144
+ fxRetry,
145
+ fxUsing,
146
+ resolveRetryDelayMs,
147
+ } from "./concurrency.ts";
148
+
149
+ export { isRedacted, maskRedactedDeep, REDACTED_PLACEHOLDER, Redacted } from "./redacted.ts";
150
+
129
151
  export {
130
152
  DryRunWriteIsolationError,
131
153
  dryRunWouldHaveFired,
@@ -200,6 +222,7 @@ export {
200
222
  type PluginOptions,
201
223
  type PluginRegistration,
202
224
  type TableContribution,
225
+ type PluginTableMeta,
203
226
  type PluginTableOptions,
204
227
  } from "./plugin.ts";
205
228
 
@@ -201,6 +201,12 @@ export interface JournalSession {
201
201
  * @param execute - Side-effecting body
202
202
  */
203
203
  effect<T>(effectKind: string, resource: string, execute: () => T | Promise<T>): Promise<T>;
204
+ /**
205
+ * Rewind the replay cursor to the start of the entry list.
206
+ * Used by flow-level retry so a re-entered `do` replays completed steps
207
+ * instead of treating the cursor as past them.
208
+ */
209
+ rewind(): void;
204
210
  /** Persist current run status / output. */
205
211
  commit(
206
212
  status: JournalRunStatus,
@@ -325,6 +331,9 @@ export function createJournal(options: CreateJournalOptions): Journal {
325
331
  await persist();
326
332
  return value;
327
333
  },
334
+ rewind() {
335
+ cursor = 0;
336
+ },
328
337
  async commit(status, patch) {
329
338
  run.status = status;
330
339
  if (patch?.wakeAt !== undefined) run.wakeAt = patch.wakeAt;
@@ -81,4 +81,22 @@ describe("plugin capability capture", () => {
81
81
  },
82
82
  });
83
83
  });
84
+
85
+ test("table description lands on capabilities.tables", () => {
86
+ const audit = plugin("audit", { version: "1.0.0" }).table("audit_events", undefined, {
87
+ description: "Immutable audit log",
88
+ plane: "operator",
89
+ });
90
+ const { api, snapshot } = createRecordingApi({
91
+ name: "audit",
92
+ version: "1.0.0",
93
+ });
94
+ audit.register(api);
95
+ expect(snapshot().capabilities.tables).toEqual({
96
+ audit_events: {
97
+ description: "Immutable audit log",
98
+ plane: "operator",
99
+ },
100
+ });
101
+ });
84
102
  });
@@ -111,9 +111,7 @@ export interface TableContribution {
111
111
  * Non-column metadata (e.g. data plane). Prefer this over stuffing `plane`
112
112
  * into the column map.
113
113
  */
114
- readonly options?: {
115
- readonly plane?: "operator" | "user" | "shared" | string;
116
- };
114
+ readonly options?: PluginTableOptions;
117
115
  /**
118
116
  * Opaque schema descriptor (legacy). Prefer {@link columns} + {@link options}.
119
117
  * Still set for plane-only contributions so older readers keep working.
@@ -124,6 +122,14 @@ export interface TableContribution {
124
122
  /** Options for {@link PluginApi.table} / {@link PluginDef.table}. */
125
123
  export interface PluginTableOptions {
126
124
  readonly plane?: "operator" | "user" | "shared" | string;
125
+ /** Optional human description for Console / docs (falls back to the table name). */
126
+ readonly description?: string;
127
+ }
128
+
129
+ /** Per-table metadata captured on {@link PluginCapabilities} / Manifest Plugin. */
130
+ export interface PluginTableMeta {
131
+ readonly plane?: string;
132
+ readonly description?: string;
127
133
  }
128
134
 
129
135
  /** Driver contribution. */
@@ -253,6 +259,8 @@ export interface PluginCapabilities {
253
259
  readonly intercepts: readonly string[];
254
260
  /** Declared dependencies from `.needs()`. */
255
261
  readonly needs: readonly string[];
262
+ /** Optional metadata for contributed tables (description / plane). */
263
+ readonly tables?: Readonly<Record<string, PluginTableMeta>>;
256
264
  }
257
265
 
258
266
  /** Snapshot of everything a registration requested. */
@@ -0,0 +1,74 @@
1
+ /**
2
+ * `Redacted<T>` — a plain value wrapper that keeps a secret value out of
3
+ * logs, traces, and accidental serialization.
4
+ *
5
+ * Not an effect type: effect tracking only records which secret *names* a
6
+ * flow touches. This is value hygiene inside a flow body.
7
+ */
8
+
9
+ /** Placeholder used for any representation of a {@link Redacted}. */
10
+ export const REDACTED_PLACEHOLDER = "[redacted]";
11
+
12
+ /**
13
+ * Wrap a value so printing / logging / JSON serialization never yields the
14
+ * real value. The only way to read the wrapped value is {@link reveal}.
15
+ */
16
+ export class Redacted<T> {
17
+ readonly #value: T;
18
+
19
+ constructor(value: T) {
20
+ this.#value = value;
21
+ }
22
+
23
+ /** The one explicit way to get the real value. */
24
+ reveal(): T {
25
+ return this.#value;
26
+ }
27
+
28
+ toString(): string {
29
+ return REDACTED_PLACEHOLDER;
30
+ }
31
+
32
+ toJSON(): string {
33
+ return REDACTED_PLACEHOLDER;
34
+ }
35
+
36
+ [Symbol.toPrimitive](): string {
37
+ return REDACTED_PLACEHOLDER;
38
+ }
39
+
40
+ /** `util.inspect` / `console.log` — never the real value. */
41
+ [Symbol.for("nodejs.util.inspect.custom")](): string {
42
+ return REDACTED_PLACEHOLDER;
43
+ }
44
+ }
45
+
46
+ /** Type guard for {@link Redacted}. */
47
+ export function isRedacted(value: unknown): value is Redacted<unknown> {
48
+ return value instanceof Redacted;
49
+ }
50
+
51
+ /**
52
+ * Deep-walk a log/trace payload: replace every {@link Redacted} with the
53
+ * placeholder (never the wrapped value); leave everything else untouched.
54
+ *
55
+ * @param value - Arbitrary structured data
56
+ */
57
+ export function maskRedactedDeep<T>(value: T): T {
58
+ return maskRedactedValue(value) as T;
59
+ }
60
+
61
+ function maskRedactedValue(value: unknown): unknown {
62
+ if (isRedacted(value)) return REDACTED_PLACEHOLDER;
63
+ if (value === null || value === undefined) return value;
64
+ if (Array.isArray(value)) return value.map(maskRedactedValue);
65
+ if (typeof value === "object") {
66
+ const obj = value as Record<string, unknown>;
67
+ const out: Record<string, unknown> = {};
68
+ for (const [k, v] of Object.entries(obj)) {
69
+ out[k] = maskRedactedValue(v);
70
+ }
71
+ return out;
72
+ }
73
+ return value;
74
+ }
@@ -166,6 +166,7 @@ export function createRecordingApi(identity: { readonly name: string; readonly v
166
166
  return {
167
167
  api,
168
168
  snapshot(): PluginRegistration {
169
+ const tableMeta = tablesMetaFromContributions(tables);
169
170
  return {
170
171
  capabilities: {
171
172
  name: identity.name,
@@ -173,6 +174,7 @@ export function createRecordingApi(identity: { readonly name: string; readonly v
173
174
  declares: declares.slice(),
174
175
  intercepts: intercepts.slice(),
175
176
  needs: needs.slice(),
177
+ ...(tableMeta ? { tables: tableMeta } : {}),
176
178
  },
177
179
  hooks: { ...hooks },
178
180
  edges: edges.slice(),
@@ -440,9 +442,12 @@ function normalizeTableContribution(
440
442
  };
441
443
  }
442
444
 
443
- if (columnsOrOptions && isPlaneOnlyOptions(columnsOrOptions)) {
445
+ if (columnsOrOptions && isLegacyTableOptions(columnsOrOptions)) {
444
446
  const planeOptions: PluginTableOptions = {
445
- plane: columnsOrOptions.plane as string,
447
+ ...(typeof columnsOrOptions.plane === "string" ? { plane: columnsOrOptions.plane } : {}),
448
+ ...(typeof columnsOrOptions.description === "string"
449
+ ? { description: columnsOrOptions.description }
450
+ : {}),
446
451
  };
447
452
  return {
448
453
  name,
@@ -462,9 +467,27 @@ function normalizeTableContribution(
462
467
  return { name };
463
468
  }
464
469
 
465
- function isPlaneOnlyOptions(
466
- value: Readonly<Record<string, unknown>>,
467
- ): value is { readonly plane: string } {
470
+ function isLegacyTableOptions(value: Readonly<Record<string, unknown>>): boolean {
468
471
  const keys = Object.keys(value);
469
- return keys.length === 1 && keys[0] === "plane" && typeof value.plane === "string";
472
+ if (keys.length === 0) return false;
473
+ return keys.every((k) => k === "plane" || k === "description");
474
+ }
475
+
476
+ function tablesMetaFromContributions(
477
+ contributions: readonly TableContribution[],
478
+ ): Record<string, { readonly plane?: string; readonly description?: string }> | undefined {
479
+ if (contributions.length === 0) return undefined;
480
+ const out: Record<string, { readonly plane?: string; readonly description?: string }> = {};
481
+ let any = false;
482
+ for (const t of contributions) {
483
+ const plane = t.options?.plane;
484
+ const description = t.options?.description;
485
+ if (plane === undefined && description === undefined) continue;
486
+ any = true;
487
+ out[t.name] = {
488
+ ...(plane !== undefined ? { plane } : {}),
489
+ ...(description !== undefined ? { description } : {}),
490
+ };
491
+ }
492
+ return any ? out : undefined;
470
493
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
- * HTTP router — compiled RegExp matching with a Trie fallback
3
- * (Hono's SmartRouter idea), plus a linear preset for cold-start edge builds.
2
+ * HTTP router — compiled RegExp matching with a Trie fallback, plus a linear
3
+ * preset for cold-start edge builds.
4
4
  *
5
5
  * Chosen at build/startup: try RegExp first; on unsupported patterns fall
6
6
  * back to Trie. The `edge` preset uses LinearRouter for fast registration.
@@ -141,7 +141,7 @@ function matchCompiled<T>(dyn: CompiledDynamic<T>, path: string): RouteMatch<T>
141
141
 
142
142
  /**
143
143
  * Compiled RegExp router — O(1) static map, plus per-bucket compiled
144
- * regular expressions for parametric routes (Hono RegExpRouter idea).
144
+ * regular expressions for parametric routes.
145
145
  */
146
146
  export class RegExpRouter<T> implements Router<T> {
147
147
  readonly name = "RegExpRouter";
@@ -165,6 +165,8 @@ export interface Flow {
165
165
  /** One Signal declaration. */
166
166
  export interface Signal {
167
167
  delivery: SignalDelivery;
168
+ /** Optional human description (falls back to the signal map key). */
169
+ description?: string;
168
170
  retries?: number;
169
171
  deadLetter?: boolean;
170
172
  schema?: JsonSchema;
@@ -201,6 +203,8 @@ export interface DeclaredColumn extends ColumnClassification {
201
203
  default?: string | number | boolean | null;
202
204
  /** Database column name (snake_case by default). */
203
205
  sqlName?: string;
206
+ /** Optional human description (falls back to the column map key). */
207
+ description?: string;
204
208
  /** Foreign key when `.references()` was declared. */
205
209
  references?: DeclaredColumnReference;
206
210
  }
@@ -217,6 +221,8 @@ export interface Table {
217
221
  /** One Store declaration. */
218
222
  export interface Store {
219
223
  facet: StoreFacet;
224
+ /** Optional human description (falls back to the store map key). */
225
+ description?: string;
220
226
  tables?: Record<string, Table>;
221
227
  namespaces?: string[];
222
228
  buckets?: string[];
@@ -230,6 +236,8 @@ export interface Clock {
230
236
  every?: string;
231
237
  timezone?: string;
232
238
  overridable?: boolean;
239
+ /** Optional human description (falls back to the clock map key). */
240
+ description?: string;
233
241
  }
234
242
 
235
243
  /** Named gate — policy or rate strategy. */
@@ -242,6 +250,8 @@ export interface Gate {
242
250
  keyBy?: string;
243
251
  scopes?: string[];
244
252
  roles?: string[];
253
+ /** Optional human description (falls back to the gate map key). */
254
+ description?: string;
245
255
  }
246
256
 
247
257
  /** Vault secret / config contract (never a secret value). */
@@ -258,6 +268,8 @@ export interface SecretContract {
258
268
 
259
269
  /** Channel template. */
260
270
  export interface Channel {
271
+ /** Optional human description (falls back to the channel map key). */
272
+ description?: string;
261
273
  medium?: ChannelMedium;
262
274
  locales?: string[];
263
275
  schema?: JsonSchema;
@@ -302,12 +314,21 @@ export interface Ai {
302
314
  agents?: Record<string, AiAgent>;
303
315
  }
304
316
 
317
+ /** Per-table metadata on a Manifest {@link Plugin}. */
318
+ export interface PluginTable {
319
+ plane?: string;
320
+ /** Optional human description (falls back to the table name). */
321
+ description?: string;
322
+ }
323
+
305
324
  /** Plugin capability declaration. */
306
325
  export interface Plugin {
307
326
  origin?: PluginOrigin;
308
327
  version?: string;
309
328
  declares?: string[];
310
329
  intercepts?: string[];
330
+ /** Optional metadata for `table:*` contributions. */
331
+ tables?: Record<string, PluginTable>;
311
332
  }
312
333
 
313
334
  /** Tenancy configuration (resolver is code; isolation is data). */
@@ -36,6 +36,10 @@ const EXPORT_BUILD_EXTERNALS = [
36
36
  "ajv-formats",
37
37
  "oxc-parser",
38
38
  "zod",
39
+ "@libsql/client",
40
+ "@libsql/*",
41
+ "@electric-sql/pglite",
42
+ "@electric-sql/*",
39
43
  ] as const;
40
44
 
41
45
  /** How a sample is gated. */
@@ -56,7 +56,7 @@ describe("Provisions integration", () => {
56
56
  effects: { secrets: ["STRIPE_KEY"], emits: ["order-news"] },
57
57
  do: async ({ orderId }, fx) => {
58
58
  const key = fx.vault(stripeKey);
59
- expect(key.startsWith("sk_")).toBe(true);
59
+ expect(key.reveal().startsWith("sk_")).toBe(true);
60
60
 
61
61
  await fx.step("create-intent", async () => ({ id: `pi_${orderId}` }));
62
62
  await fx.clock.sleep("verify-window", "2m");
@@ -1,65 +0,0 @@
1
- ---
2
- title: Comparison
3
- description: How OKE compares to Hono, Elysia, Encore.ts, and iii — and when to pick it.
4
- source: docs/spec/unified-theory.md
5
- icon: Scale
6
- ---
7
-
8
- > **OKE is the batteries-included TypeScript backend for the Bun era:** contract-first APIs with end-to-end type safety, declarative infrastructure primitives, an OpenTelemetry-native Console, secure-by-default auth and ABAC — pure TypeScript, Web-Standards portable, MIT-licensed, self-hostable with zero cloud lock-in.
9
-
10
- _"Encore's batteries and dashboard, Elysia's speed and DX, Hono's portability — without the Rust lock-in, the cloud gravity, or the source-available license."_
11
-
12
- ## When to pick OKE
13
-
14
- Choose OKE if you want:
15
-
16
- - **One mental model** for HTTP, jobs, consumers, and durable work (`on(Trigger) → Effects`)
17
- - **Batteries included** — store, signals, clock, gates, vault, channels, and AI as first-class elements
18
- - **A local Console** derived from your code (not a separate SaaS product you must adopt)
19
- - **MIT + self-host** with drivers named after protocols (`postgres`, `redis`, `s3`), not vendors
20
- - Effects that power cache invalidation, live queries, and least privilege **without hand annotations**
21
-
22
- Prefer a thinner router (Hono / Elysia alone) if you only need HTTP and will assemble queues, cron, and auth yourself.
23
-
24
- ## Matrix
25
-
26
- | | Hono | Elysia | Encore.ts | iii | **OKE** |
27
- | ----------------------------------------------- | --------------- | ----------- | ------------------- | --------------------- | --------------------------------- |
28
- | Primary runtime | Multi (Web Std) | Bun-first | Node + Rust core | Rust engine, polyglot | **Bun-first, Web-Std portable** |
29
- | License | MIT | MIT | MPL-2.0 | ELv2 engine | **MIT** |
30
- | Typed client | hc RPC | Eden Treaty | generated | SDK | **contract-first + live queries** |
31
- | DB · queue · cron · storage | ❌ | ❌ | ✅ | ✅ | **✅ (7 elements)** |
32
- | Durable workflows | ❌ | ❌ | ❌ | partial | **✅ (a flag)** |
33
- | Local Console | ❌ | ❌ | ✅ | ✅ | **✅ (dev + prod)** |
34
- | Auto cache invalidation | ❌ | ❌ | ❌ | ❌ | **✅ (from effects)** |
35
- | Least-privilege by compiler | ❌ | ❌ | ❌ | ❌ | **✅** |
36
- | Human channels (email/SMS/WA) | ❌ | ❌ | ❌ | ❌ | **✅ (7th element)** |
37
- | AI in the application (models, prompts, agents) | ❌ | ❌ | ❌ | ❌ | **✅ (8th element)** |
38
- | Self-host, no lock-in | ✅ | ✅ | ✅ (Cloud optional) | ⚠️ | **✅ first-class** |
39
-
40
- ## vs Collections of libraries
41
-
42
- Frameworks that ship a router plus a queue library plus a cron library plus websockets share one flaw: the concepts do not derive from one another. Documentation sprawls; each new fashion adds a concept that never gets removed.
43
-
44
- OKE starts from one law so docs, traces, hooks, and agent surfaces stay **one shape**.
45
-
46
- ## vs Cloud-gravity platforms
47
-
48
- OKE is MIT, self-hostable, and names drivers after **protocols** — not vendors. Vendor choice lives in images keyed by role. There is no separate “deploy to our cloud” product you must adopt to get the Console.
49
-
50
- ## vs Rolling your own effect system
51
-
52
- Effects are inferred from what a Flow touches through `fx`. Cache invalidation, live queries, least privilege, deterministic tests, and Manifest Diff fall out of that one decision — without hand-written annotations for each capability.
53
-
54
- <Cards>
55
- <Card
56
- title="Installation"
57
- description="Install and scaffold in minutes."
58
- href="/docs/get-started/installation"
59
- />
60
- <Card
61
- title="Introduction"
62
- description="The one law, eight elements, ten exports."
63
- href="/docs/get-started/introduction"
64
- />
65
- </Cards>