okengine 0.21.0 → 0.22.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 (160) hide show
  1. package/AGENTS.md +4 -4
  2. package/LICENSE +202 -0
  3. package/NOTICE +2 -0
  4. package/README.md +15 -9
  5. package/manifest.v1.schema.json +10 -0
  6. package/package.json +22 -25
  7. package/site/content/docs/ai/skills.mdx +1 -1
  8. package/site/content/docs/client/calling.mdx +27 -19
  9. package/site/content/docs/client/index.mdx +1 -1
  10. package/site/content/docs/elements/channel/email.mdx +1 -1
  11. package/site/content/docs/elements/channel/index.mdx +1 -1
  12. package/site/content/docs/elements/flow/http.mdx +4 -4
  13. package/site/content/docs/elements/flow/index.mdx +3 -3
  14. package/site/content/docs/elements/flow/meta.json +1 -1
  15. package/site/content/docs/elements/gate/tenancy.mdx +1 -1
  16. package/site/content/docs/elements/vault/rotation.mdx +15 -0
  17. package/site/content/docs/plugins/otp.mdx +31 -31
  18. package/site/content/docs/recipes/mailpit.mdx +1 -1
  19. package/site/content/docs/recipes/meilisearch.mdx +1 -1
  20. package/site/content/docs/recipes/pgdog.mdx +1 -1
  21. package/site/content/docs/reference/configuration.mdx +2 -2
  22. package/site/content/docs/reference/errors.mdx +12 -2
  23. package/site/content/docs/reference/fx.mdx +28 -15
  24. package/site/content/docs/reference/idempotency.mdx +192 -0
  25. package/site/content/docs/reference/index.mdx +6 -1
  26. package/site/content/docs/reference/meta.json +2 -1
  27. package/site/content/docs/understand/meta.json +1 -1
  28. package/site/content/docs/{elements/flow → understand}/routing.mdx +11 -10
  29. package/site/content/docs/understand/the-architecture.mdx +14 -12
  30. package/site/content/docs/understand/try-it.mdx +3 -3
  31. package/src/cli/competitor-mention-removal.test.ts +3 -3
  32. package/src/cli/dev.test.ts +105 -2
  33. package/src/cli/dev.ts +37 -2
  34. package/src/cli/docker-cli.test.ts +1 -1
  35. package/src/cli/index.ts +9 -1
  36. package/src/cli/load-config.images.test.ts +10 -10
  37. package/src/cli/load-config.ts +1 -1
  38. package/src/cli/vault-cmd.test.ts +23 -0
  39. package/src/cli/vault-cmd.ts +7 -0
  40. package/src/client/create.ts +31 -27
  41. package/src/client/live.ts +44 -101
  42. package/src/client/sse.ts +20 -66
  43. package/src/client/stream.ts +25 -67
  44. package/src/client/transport.test.ts +225 -0
  45. package/src/client/transport.ts +158 -90
  46. package/src/client/types.ts +29 -3
  47. package/src/client/wire.ts +119 -0
  48. package/src/compiler/aot.ts +3 -32
  49. package/src/compiler/dynamic.ts +13 -11
  50. package/src/compiler/effects-infer.ts +726 -16
  51. package/src/compiler/extract.test.ts +37 -0
  52. package/src/compiler/extract.ts +113 -12
  53. package/src/compiler/fixtures/skyport.expected.json +24 -0
  54. package/src/compiler/fx-follow.test.ts +164 -0
  55. package/src/compiler/fx-index.ts +399 -0
  56. package/src/compiler/interpret.ts +45 -0
  57. package/src/console/server/flows-invoke.test.ts +2 -2
  58. package/src/console/ui-next/dist/assets/{access-page-tIbsiphz.js → access-page-Ze6ckjdd.js} +2 -2
  59. package/src/console/ui-next/dist/assets/agent-disclosure-Bkohc_8X.js +1 -0
  60. package/src/console/ui-next/dist/assets/{cache-glyph-BCC-DxKT.js → cache-glyph-rL0lHxat.js} +1 -1
  61. package/src/console/ui-next/dist/assets/{call-pii-button-jOmYISdc.js → call-pii-button-Deo4PP18.js} +1 -1
  62. package/src/console/ui-next/dist/assets/{collapsible-DC2xNaAb.js → collapsible-CoJ6amHf.js} +1 -1
  63. package/src/console/ui-next/dist/assets/{copy-inline-button-CAYD18cr.js → copy-inline-button-CWG5_ZOh.js} +1 -1
  64. package/src/console/ui-next/dist/assets/{detail-header-DVWNjWjg.js → detail-header-b_ZzIsmU.js} +1 -1
  65. package/src/console/ui-next/dist/assets/{dropdown-menu-4h2LOVXM.js → dropdown-menu-nwXEO1Ac.js} +1 -1
  66. package/src/console/ui-next/dist/assets/duration-tone-BbQ_8z50.js +9 -0
  67. package/src/console/ui-next/dist/assets/element-icons-CwyLpVXz.js +1 -0
  68. package/src/console/ui-next/dist/assets/{explorer-empty-CJs5A-wm.js → explorer-empty-C8sSCqYT.js} +1 -1
  69. package/src/console/ui-next/dist/assets/{flows-page-gT1lsWQK.js → flows-page-D2E7BtKK.js} +1 -1
  70. package/src/console/ui-next/dist/assets/{highlighted-json-C2GZEJNI.js → highlighted-json-CPYBzDh0.js} +1 -1
  71. package/src/console/ui-next/dist/assets/{http-method-BdYjcIrD.js → http-method-DTPevKmM.js} +1 -1
  72. package/src/console/ui-next/dist/assets/index-vTuwmeQz.js +58 -0
  73. package/src/console/ui-next/dist/assets/observability-page-SJTSzHFJ.js +4 -0
  74. package/src/console/ui-next/dist/assets/{replica-lag-C4QdAF7J.js → replica-lag-BM27lXld.js} +3 -3
  75. package/src/console/ui-next/dist/assets/{request-meta-C43DHyld.js → request-meta-BjFT7DNl.js} +1 -1
  76. package/src/console/ui-next/dist/assets/shortcut-keys-CUNegEV4.js +1 -0
  77. package/src/console/ui-next/dist/assets/store-page-Qj9ThRE-.js +41 -0
  78. package/src/console/ui-next/dist/assets/{trace-detail-sheet-Dcpo4Us_.js → trace-detail-sheet-Cmitq3da.js} +2 -2
  79. package/src/console/ui-next/dist/assets/{tree-expand-toggle-CKJTuv43.js → tree-expand-toggle-CbDIB-7x.js} +2 -2
  80. package/src/console/ui-next/dist/assets/units-page-_PuYqFty.js +1 -0
  81. package/src/console/ui-next/dist/assets/{use-vault-list-CT4-gajj.js → use-vault-list-Cs45Czkt.js} +1 -1
  82. package/src/console/ui-next/dist/assets/{vault-page-BwZ9YjTW.js → vault-page-BOMr0go9.js} +2 -2
  83. package/src/console/ui-next/dist/index.html +2 -2
  84. package/src/console/ui-next/src/features/units/detail/flow-contract-panel.tsx +9 -0
  85. package/src/docker/docker.test.ts +13 -13
  86. package/src/docker/images-config.test.ts +12 -12
  87. package/src/docker/stack-id.test.ts +1 -1
  88. package/src/drivers/journal-postgres.ts +168 -3
  89. package/src/drivers/meilisearch.ts +2 -0
  90. package/src/drivers/vault-builtin.test.ts +10 -0
  91. package/src/drivers/vault-builtin.ts +13 -1
  92. package/src/drivers/vault-remote-bag.ts +5 -1
  93. package/src/drivers/vault-types.ts +6 -1
  94. package/src/elements/store/emit-drizzle.ts +2 -2
  95. package/src/elements/store/live-default.test.ts +2 -0
  96. package/src/elements/store/live-http.test.ts +1 -0
  97. package/src/elements/store/resource-list-docs.test.ts +2 -2
  98. package/src/elements/store/resource.test.ts +3 -3
  99. package/src/elements/store/schema-decl.test.ts +1 -0
  100. package/src/elements/store/sql-condition.ts +24 -7
  101. package/src/elements/store/sql-session.ts +20 -4
  102. package/src/elements/store/upsert-app.test.ts +1 -1
  103. package/src/elements/vault/audit.test.ts +137 -0
  104. package/src/elements/vault/audit.ts +106 -13
  105. package/src/elements/vault/boot-chain.ts +9 -1
  106. package/src/elements/vault/builtin-adapter.ts +33 -3
  107. package/src/i18n/catalogs/ar.ts +4 -0
  108. package/src/i18n/catalogs/en.ts +4 -0
  109. package/src/kernel/abort-scope.ts +33 -2
  110. package/src/kernel/app.ts +200 -20
  111. package/src/kernel/auto-cache.test.ts +6 -6
  112. package/src/kernel/boot-bind/vault.ts +1 -0
  113. package/src/kernel/boot.ts +19 -1
  114. package/src/kernel/builtin-errors.ts +8 -0
  115. package/src/kernel/client-descriptor.test.ts +78 -0
  116. package/src/kernel/client-descriptor.ts +24 -0
  117. package/src/kernel/concurrency.test.ts +33 -0
  118. package/src/kernel/effects-stamping.test.ts +2 -2
  119. package/src/kernel/errors-compiler.ts +20 -0
  120. package/src/kernel/errors-text.ts +99 -0
  121. package/src/kernel/errors.ts +90 -138
  122. package/src/kernel/external-effects.test.ts +36 -0
  123. package/src/kernel/flow.ts +24 -0
  124. package/src/kernel/fx-fetch.ts +5 -1
  125. package/src/kernel/fx-sql-handle.ts +305 -0
  126. package/src/kernel/fx.ts +42 -330
  127. package/src/kernel/idempotency-store.ts +578 -0
  128. package/src/kernel/idempotency.test.ts +556 -0
  129. package/src/kernel/idempotency.ts +304 -0
  130. package/src/kernel/index.ts +1 -0
  131. package/src/kernel/journal.ts +56 -0
  132. package/src/kernel/json-result.ts +59 -0
  133. package/src/kernel/project-out.ts +6 -1
  134. package/src/manifest/types.ts +8 -0
  135. package/src/okid-extended.ts +175 -0
  136. package/src/okid-shared.ts +103 -0
  137. package/src/okid.ts +30 -213
  138. package/src/plugins/auth-delivery.mailpit.integration.test.ts +1 -1
  139. package/src/plugins/otp.test.ts +65 -1
  140. package/src/plugins/otp.ts +59 -7
  141. package/src/release/absolute-regression.test.ts +63 -0
  142. package/src/release/build-lib.ts +9 -0
  143. package/src/release/http-graph.test.ts +25 -0
  144. package/src/release/http-graph.ts +90 -0
  145. package/src/release/index.ts +3 -0
  146. package/src/release/limits.ts +17 -2
  147. package/src/release/measure.ts +105 -14
  148. package/src/test/create-test-app.test.ts +3 -1
  149. package/src/test/create-test-app.ts +40 -0
  150. package/src/test/live-signals.test.ts +1 -0
  151. package/src/test/provisions.integration.test.ts +1 -0
  152. package/src/test/tenant-isolation.test.ts +1 -0
  153. package/src/console/ui-next/dist/assets/agent-disclosure-CSKumwS2.js +0 -1
  154. package/src/console/ui-next/dist/assets/duration-tone-CwoV56jn.js +0 -9
  155. package/src/console/ui-next/dist/assets/element-icons-BI8cJgdh.js +0 -1
  156. package/src/console/ui-next/dist/assets/index-Cul17AcV.js +0 -63
  157. package/src/console/ui-next/dist/assets/observability-page-oY9vdYBk.js +0 -4
  158. package/src/console/ui-next/dist/assets/shortcut-keys-3ILd8oGn.js +0 -1
  159. package/src/console/ui-next/dist/assets/store-page-CL0D9dOq.js +0 -41
  160. package/src/console/ui-next/dist/assets/units-page-1PlT19ft.js +0 -1
@@ -146,6 +146,8 @@ export type ClientListMeta = {
146
146
  readonly prev?: ClientPagerLink | null;
147
147
  readonly total?: number;
148
148
  readonly offset?: number;
149
+ /** Set when the server replayed a completed idempotent call. */
150
+ readonly idempotentReplayed?: true;
149
151
  };
150
152
 
151
153
  /**
@@ -163,7 +165,11 @@ export type ClientEnvelope<
163
165
  readonly error: null;
164
166
  readonly meta?: ClientListMeta;
165
167
  }
166
- | { readonly data: null; readonly error: ClientError<E> | TransportError };
168
+ | {
169
+ readonly data: null;
170
+ readonly error: ClientError<E> | TransportError;
171
+ readonly meta?: ClientListMeta;
172
+ };
167
173
 
168
174
  /**
169
175
  * Client call result — envelope plus always-callable `next` / `prev`.
@@ -251,7 +257,15 @@ export interface ClientOptions {
251
257
  readonly headers?: ClientHeaders | (() => ClientHeaders | Promise<ClientHeaders>);
252
258
  /** Abort the request after this many milliseconds. */
253
259
  readonly timeout?: number;
254
- /** Retry transient failures (network / 5xx). */
260
+ /**
261
+ * Retry transient failures (network errors, client timeouts, and
262
+ * non-envelope 5xx).
263
+ *
264
+ * A call retries when it is `GET` or `QUERY`, or when it carries an
265
+ * idempotency key. Keys are sent automatically on every non-GET call.
266
+ * `idempotencyKey: false` runs once unless that call passes `{ retry: true }`.
267
+ * `409 IdempotencyInProgress` waits for `Retry-After` and counts as an attempt.
268
+ */
255
269
  readonly retry?: {
256
270
  /** Extra attempts after the first (default 0). */
257
271
  readonly retries?: number;
@@ -296,11 +310,22 @@ export interface ClientOptions {
296
310
  export type ClientThenableIterable<T> = PromiseLike<T> & AsyncIterable<T>;
297
311
 
298
312
  /**
299
- * Per-call options on a Flow invoke (binary decode / abort).
313
+ * Per-call options on a Flow invoke (binary decode, abort, retry opt-in).
300
314
  */
301
315
  export interface ClientCallOpts {
302
316
  readonly response?: "json" | "blob" | "arrayBuffer";
303
317
  readonly signal?: AbortSignal;
318
+ /**
319
+ * Repeat this call under the client `retry` policy even when it sends
320
+ * no idempotency key.
321
+ */
322
+ readonly retry?: boolean;
323
+ /**
324
+ * Stable `Idempotency-Key` for this logical call. A string is sent as-is
325
+ * (one key per form render dedupes double submits). `false` sends no key.
326
+ * Omitted on a non-GET call generates one key and reuses it across retries.
327
+ */
328
+ readonly idempotencyKey?: string | false;
304
329
  }
305
330
 
306
331
  /**
@@ -503,6 +528,7 @@ export interface ClientDescriptor {
503
528
  readonly stream?: true;
504
529
  readonly matchKey?: readonly string[];
505
530
  readonly gates?: readonly string[];
531
+ readonly idempotency?: { readonly mode: "off" | "auto" | "required"; readonly ttl: string };
506
532
  };
507
533
  };
508
534
  };
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Shared REST wire helpers for transport, live subscribe, and streams.
3
+ */
4
+
5
+ import type { ClientHeaders, ClientOptions, ClientRouteMap, FlowContract } from "./types.ts";
6
+
7
+ /**
8
+ * Visit every flow contract in `app.$routes`.
9
+ *
10
+ * @param $routes - Runtime route map
11
+ * @param visit - Called with unit, flow, and contract
12
+ */
13
+ export function walkContracts(
14
+ $routes: ClientRouteMap | undefined,
15
+ visit: (unit: string, flow: string, contract: FlowContract) => void,
16
+ ): void {
17
+ if (!$routes) return;
18
+ for (const [unit, flows] of Object.entries($routes)) {
19
+ if (!flows || typeof flows !== "object") continue;
20
+ for (const [flow, contract] of Object.entries(flows)) {
21
+ if (!contract || typeof contract !== "object") continue;
22
+ visit(unit, flow, contract);
23
+ }
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Read `method` and `path` when both are strings.
29
+ *
30
+ * @param contract - One flow contract
31
+ */
32
+ export function methodAndPath(
33
+ contract: object,
34
+ ): { readonly method: string; readonly path: string } | undefined {
35
+ const method = "method" in contract ? contract.method : undefined;
36
+ const path = "path" in contract ? contract.path : undefined;
37
+ if (typeof method === "string" && typeof path === "string") return { method, path };
38
+ return undefined;
39
+ }
40
+
41
+ /**
42
+ * Replace `:param` tokens in `path`. Unmatched keys land in `rest`.
43
+ *
44
+ * @param path - Path template
45
+ * @param input - JSON object, or anything else (ignored)
46
+ */
47
+ export function interpolatePath(
48
+ path: string,
49
+ input: unknown,
50
+ ): { readonly path: string; readonly rest: Record<string, unknown> } {
51
+ const params =
52
+ input !== null && typeof input === "object" ? (input as Record<string, unknown>) : {};
53
+ let pathOut = path;
54
+ const rest: Record<string, unknown> = {};
55
+ for (const [k, v] of Object.entries(params)) {
56
+ const token = `:${k}`;
57
+ if (pathOut.includes(token)) {
58
+ pathOut = pathOut.replaceAll(token, encodeURIComponent(String(v)));
59
+ } else {
60
+ rest[k] = v;
61
+ }
62
+ }
63
+ return { path: pathOut, rest };
64
+ }
65
+
66
+ /**
67
+ * Encode a query string, skipping `undefined` values.
68
+ *
69
+ * @param rest - Keys that were not path params
70
+ */
71
+ export function toQuery(rest: Record<string, unknown>): string {
72
+ const query: string[] = [];
73
+ for (const [k, v] of Object.entries(rest)) {
74
+ if (v !== undefined) {
75
+ query.push(`${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`);
76
+ }
77
+ }
78
+ return query.length > 0 ? `?${query.join("&")}` : "";
79
+ }
80
+
81
+ /**
82
+ * Copy a header bag onto `headers` (later keys win within the bag).
83
+ *
84
+ * @param headers - Target
85
+ * @param extra - Record or tuple list
86
+ */
87
+ export function applyHeaderBag(headers: Headers, extra: ClientHeaders | undefined): void {
88
+ if (!extra) return;
89
+ if (Array.isArray(extra)) {
90
+ for (const [k, v] of extra) headers.set(k, v);
91
+ } else {
92
+ for (const [k, v] of Object.entries(extra)) headers.set(k, v);
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Resolve `opts.headers` when it is a function.
98
+ *
99
+ * @param opts - Client options
100
+ */
101
+ export async function resolveHeaders(opts: ClientOptions): Promise<ClientHeaders | undefined> {
102
+ return typeof opts.headers === "function" ? await opts.headers() : opts.headers;
103
+ }
104
+
105
+ /**
106
+ * Set `Authorization` from `opts.auth.getToken` when the caller did not.
107
+ *
108
+ * @param headers - Target
109
+ * @param opts - Client options
110
+ */
111
+ export async function applyAuthHeader(headers: Headers, opts: ClientOptions): Promise<void> {
112
+ const token =
113
+ opts.auth && "getToken" in opts.auth && typeof opts.auth.getToken === "function"
114
+ ? await opts.auth.getToken()
115
+ : undefined;
116
+ if (token && !headers.has("authorization")) {
117
+ headers.set("authorization", `Bearer ${token}`);
118
+ }
119
+ }
@@ -6,30 +6,20 @@
6
6
  * {@link compileDynamic} / `aot: false` on edge runtimes that ban `eval`.
7
7
  */
8
8
 
9
- import type { FlowFailure } from "../kernel/errors.ts";
10
9
  import { validate, type SchemaInput } from "../validation/standard-schema.ts";
11
10
  import {
12
11
  assembleInput,
13
- extractParts,
14
12
  parseBody,
15
13
  parseCookie,
16
14
  parseHeaders,
17
15
  parseQuery,
18
16
  type ContextInference,
19
- type InputParts,
20
17
  } from "./http-parse.ts";
18
+ import { createInterpretedParseValidate, type CompiledParseValidate } from "./interpret.ts";
21
19
  import { sucrose } from "./sucrose.ts";
22
20
 
23
- /** Result of parse + validate for one request. */
24
- export type ParseValidateResult =
25
- | { readonly ok: true; readonly input: unknown }
26
- | { readonly ok: false; readonly failure: FlowFailure };
27
-
28
- /** Compiled parse/validate function. */
29
- export type CompiledParseValidate = (
30
- request: Request,
31
- params: Readonly<Record<string, string>>,
32
- ) => Promise<ParseValidateResult>;
21
+ export type { CompiledParseValidate, ParseValidateResult } from "./interpret.ts";
22
+ export { createInterpretedParseValidate };
33
23
 
34
24
  /** Options for {@link compileAot}. */
35
25
  export interface CompileRouteOptions {
@@ -152,25 +142,6 @@ function generateParseValidate(
152
142
  return factory(helpers);
153
143
  }
154
144
 
155
- /**
156
- * Interpreted parse/validate using the same helpers (AoT fallback).
157
- *
158
- * @param inference - Context flags
159
- * @param schema - Input schema
160
- */
161
- export function createInterpretedParseValidate(
162
- inference: ContextInference,
163
- schema: SchemaInput | undefined,
164
- ): CompiledParseValidate {
165
- return async (request, params) => {
166
- const parts: InputParts = await extractParts(request, params, inference);
167
- const raw = assembleInput(parts);
168
- const result = await validate(schema, raw);
169
- if (!result.ok) return { ok: false, failure: result.failure };
170
- return { ok: true, input: result.value };
171
- };
172
- }
173
-
174
145
  /**
175
146
  * Re-export sucrose for tests and tooling.
176
147
  */
@@ -7,15 +7,18 @@
7
7
  * a requirement). Produces byte-identical responses to {@link compileAot}.
8
8
  */
9
9
 
10
+ import { lazyRequire } from "../kernel/lazy-require.ts";
10
11
  import type { SchemaInput } from "../validation/standard-schema.ts";
11
- import {
12
- compileAot,
13
- createInterpretedParseValidate,
14
- type CompiledRoute,
15
- type CompileRouteOptions,
16
- } from "./aot.ts";
12
+ import type { CompiledRoute, CompileRouteOptions } from "./aot.ts";
17
13
  import { FULL_INFERENCE } from "./http-parse.ts";
18
- import { dynamicInference } from "./sucrose.ts";
14
+ import { createInterpretedParseValidate } from "./interpret.ts";
15
+
16
+ /**
17
+ * Load AoT codegen. Computed stem so the edge profile keeps the interpreted path only.
18
+ */
19
+ function loadAot(): typeof import("./aot.ts") {
20
+ return lazyRequire(import.meta.dir, ["ao", "t"].join(""));
21
+ }
19
22
 
20
23
  /**
21
24
  * Compile a route with the dynamic fallback (full context every request).
@@ -23,10 +26,9 @@ import { dynamicInference } from "./sucrose.ts";
23
26
  * @param options - Route metadata
24
27
  */
25
28
  export function compileDynamic(options: CompileRouteOptions): CompiledRoute {
26
- const inference = dynamicInference();
27
29
  return {
28
- inference,
29
- parseValidate: createInterpretedParseValidate(inference, options.schema),
30
+ inference: FULL_INFERENCE,
31
+ parseValidate: createInterpretedParseValidate(FULL_INFERENCE, options.schema),
30
32
  aot: false,
31
33
  };
32
34
  }
@@ -39,7 +41,7 @@ export function compileDynamic(options: CompileRouteOptions): CompiledRoute {
39
41
  */
40
42
  export function compileRoute(options: CompileRouteOptions, aot: boolean = true): CompiledRoute {
41
43
  if (!aot) return compileDynamic(options);
42
- return compileAot(options);
44
+ return loadAot().compileAot(options);
43
45
  }
44
46
 
45
47
  /**