okengine 0.20.0 → 0.21.1

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 (104) hide show
  1. package/package.json +3 -7
  2. package/site/content/docs/client/calling.mdx +117 -29
  3. package/site/content/docs/client/index.mdx +3 -3
  4. package/site/content/docs/elements/flow/http.mdx +18 -15
  5. package/site/content/docs/elements/flow/index.mdx +26 -18
  6. package/site/content/docs/elements/gate/tenancy.mdx +1 -1
  7. package/site/content/docs/elements/store/files.mdx +11 -5
  8. package/site/content/docs/elements/store/index.mdx +2 -3
  9. package/site/content/docs/elements/store/kv.mdx +8 -2
  10. package/site/content/docs/elements/store/sql.mdx +9 -6
  11. package/site/content/docs/elements/vault/index.mdx +11 -8
  12. package/site/content/docs/elements/vault/secrets.mdx +6 -3
  13. package/site/content/docs/index.mdx +1 -1
  14. package/site/content/docs/recipes/rustfs.mdx +1 -1
  15. package/site/content/docs/reference/configuration.mdx +1 -1
  16. package/site/content/docs/reference/errors.mdx +199 -25
  17. package/site/content/docs/reference/fx.mdx +6 -1
  18. package/site/content/docs/understand/the-architecture.mdx +2 -2
  19. package/site/content/docs/understand/try-it.mdx +758 -25
  20. package/src/cli/dev-app-runner.ts +2 -1
  21. package/src/cli/dev.test.ts +105 -2
  22. package/src/cli/dev.ts +37 -2
  23. package/src/cli/start.ts +2 -1
  24. package/src/client/create.ts +14 -27
  25. package/src/client/explain.test.ts +252 -0
  26. package/src/client/explain.ts +272 -0
  27. package/src/client/live.test.ts +44 -0
  28. package/src/client/live.ts +44 -101
  29. package/src/client/notes-contract.test.ts +10 -0
  30. package/src/client/sse.ts +26 -68
  31. package/src/client/stream.ts +25 -67
  32. package/src/client/transport.test.ts +67 -0
  33. package/src/client/transport.ts +51 -112
  34. package/src/client/types.ts +17 -6
  35. package/src/client/wire.ts +119 -0
  36. package/src/client-react/live-resource.ts +6 -2
  37. package/src/compiler/aot.ts +3 -32
  38. package/src/compiler/dynamic.ts +13 -11
  39. package/src/compiler/interpret.ts +45 -0
  40. package/src/compiler/response.ts +17 -27
  41. package/src/console/server/invoke-user-flow.test.ts +8 -2
  42. package/src/console/server/invoke-user-flow.ts +12 -18
  43. package/src/console/server/security.gate.test.ts +1 -1
  44. package/src/console/ui-next/dist/assets/{access-page-DFLu0wTA.js → access-page-Bgt9bq2r.js} +1 -1
  45. package/src/console/ui-next/dist/assets/{agent-disclosure-DGscxaF5.js → agent-disclosure-B43CXZZR.js} +1 -1
  46. package/src/console/ui-next/dist/assets/{cache-glyph-BGmRZk7d.js → cache-glyph-92uM5MO7.js} +1 -1
  47. package/src/console/ui-next/dist/assets/{call-pii-button--feUYxvG.js → call-pii-button-DtGVPtcs.js} +1 -1
  48. package/src/console/ui-next/dist/assets/{collapsible-JWvpaiGY.js → collapsible-DN7l6zmC.js} +1 -1
  49. package/src/console/ui-next/dist/assets/{duration-tone-D9yCJG4n.js → duration-tone-BmIR9FV8.js} +1 -1
  50. package/src/console/ui-next/dist/assets/{flows-page-Bs6MD9GB.js → flows-page-BMHs-IzK.js} +1 -1
  51. package/src/console/ui-next/dist/assets/{highlighted-json-xH8MrEnv.js → highlighted-json-BlAEVgNW.js} +1 -1
  52. package/src/console/ui-next/dist/assets/{http-method-C4vB6ZIw.js → http-method-BDf7OAHv.js} +1 -1
  53. package/src/console/ui-next/dist/assets/{index-yTCY4AcS.js → index-BKpaes3n.js} +3 -3
  54. package/src/console/ui-next/dist/assets/{observability-page-BxJ3R6dU.js → observability-page-WnVLI-0j.js} +1 -1
  55. package/src/console/ui-next/dist/assets/{replica-lag-QRKB_IE8.js → replica-lag-B3GLNVfF.js} +1 -1
  56. package/src/console/ui-next/dist/assets/{request-meta-DqZ-fMu5.js → request-meta-DMbnAe3f.js} +1 -1
  57. package/src/console/ui-next/dist/assets/{store-page-Dixb6L7a.js → store-page-KvFDingJ.js} +1 -1
  58. package/src/console/ui-next/dist/assets/{trace-detail-sheet-CazhjtiU.js → trace-detail-sheet-Htk8Cm9t.js} +1 -1
  59. package/src/console/ui-next/dist/assets/{tree-expand-toggle-DlnqYKfr.js → tree-expand-toggle-CFMPWX4f.js} +1 -1
  60. package/src/console/ui-next/dist/assets/{units-page-BXTLjU2-.js → units-page-C4NdNuxP.js} +1 -1
  61. package/src/console/ui-next/dist/assets/{vault-page-39KR__bc.js → vault-page-CBYl0LW_.js} +1 -1
  62. package/src/console/ui-next/dist/index.html +1 -1
  63. package/src/docker/docker.test.ts +3 -3
  64. package/src/docker/images-config.test.ts +4 -4
  65. package/src/docker/stack-id.test.ts +1 -1
  66. package/src/elements/store/files-errors.test.ts +149 -0
  67. package/src/elements/store/files-errors.ts +189 -0
  68. package/src/elements/store/kv-errors.test.ts +98 -0
  69. package/src/elements/store/kv-errors.ts +139 -0
  70. package/src/elements/store/resource.ts +11 -7
  71. package/src/elements/store/runtime.ts +18 -13
  72. package/src/elements/store/sql-errors.test.ts +197 -0
  73. package/src/elements/store/sql-errors.ts +294 -0
  74. package/src/elements/store/sql-session.test.ts +52 -0
  75. package/src/elements/store/sql-session.ts +26 -4
  76. package/src/elements/store/store-errors.ts +47 -0
  77. package/src/http.ts +9 -1
  78. package/src/i18n/catalogs/ar.ts +18 -0
  79. package/src/i18n/catalogs/en.ts +18 -0
  80. package/src/index.ts +9 -1
  81. package/src/kernel/app.ts +23 -4
  82. package/src/kernel/builtin-errors.test.ts +117 -0
  83. package/src/kernel/builtin-errors.ts +129 -0
  84. package/src/kernel/call.test.ts +182 -0
  85. package/src/kernel/client-descriptor.test.ts +78 -0
  86. package/src/kernel/client-descriptor.ts +23 -0
  87. package/src/kernel/errors-text.ts +99 -0
  88. package/src/kernel/errors-vault.ts +16 -0
  89. package/src/kernel/errors.registry.test.ts +7 -0
  90. package/src/kernel/errors.ts +184 -159
  91. package/src/kernel/fail-helpers.ts +34 -0
  92. package/src/kernel/fx-sql-handle.ts +305 -0
  93. package/src/kernel/fx.test.ts +8 -0
  94. package/src/kernel/fx.ts +49 -335
  95. package/src/kernel/index.ts +12 -1
  96. package/src/kernel/json-result.ts +59 -0
  97. package/src/kernel/project-out.ts +6 -1
  98. package/src/okid-extended.ts +175 -0
  99. package/src/okid-shared.ts +103 -0
  100. package/src/okid.ts +30 -213
  101. package/src/release/build-lib.ts +9 -0
  102. package/src/runtime/dev-request-log.ts +29 -11
  103. package/src/term.test.ts +76 -0
  104. package/src/term.ts +166 -3
package/src/kernel/fx.ts CHANGED
@@ -9,10 +9,16 @@
9
9
  * for deterministic tests (§7.6).
10
10
  */
11
11
 
12
+ import {
13
+ isJsonResult,
14
+ isJsonStreamResult,
15
+ jsonResultBrand,
16
+ type JsonResult,
17
+ type JsonStreamResult,
18
+ } from "./json-result.ts";
12
19
  import type { Effects, ResourceRef, SignalResourceRef } from "../manifest/types.ts";
13
20
  import { isMcpToolRef } from "../manifest/mcp-ref.ts";
14
21
  import type { QueryPageSpec } from "./list-page.ts";
15
- import { schemaTableName, sqlTableRef } from "../manifest/sql-resource.ts";
16
22
  import type {
17
23
  FilesStoreDecl,
18
24
  FilesStoreFxHandle,
@@ -20,8 +26,6 @@ import type {
20
26
  IndexStoreFxHandle,
21
27
  KvStoreDecl,
22
28
  KvStoreFxHandle,
23
- SelectFromBuilder,
24
- SelectOrderBuilder,
25
29
  SqlStoreDecl,
26
30
  StoreDecl,
27
31
  StoreHandle,
@@ -30,7 +34,6 @@ import type {
30
34
  } from "../elements/store.ts";
31
35
  import { rlsIdentityFromAuth } from "../elements/store.ts";
32
36
  import type { RlsIdentity } from "../drivers/pg-rls.ts";
33
- import type { SqlRow } from "../drivers/types.ts";
34
37
  import type { SignalDecl, SignalRuntime } from "../elements/signal.ts";
35
38
  import type { DeadLetter, SignalEmitOptions } from "../drivers/signal-types.ts";
36
39
  import type { VaultActor, VaultAdapter, VaultRuntime } from "../elements/vault.ts";
@@ -58,16 +61,9 @@ import {
58
61
  recordWouldHaveFired,
59
62
  touchDryRunStore,
60
63
  } from "./dry-run.ts";
61
- import { fail, type FailOptions, type FlowFailure } from "./errors.ts";
64
+ import type { FailFn } from "./errors.ts";
62
65
  import { currentAbortSignal, linkAbort } from "./abort-scope.ts";
63
- import {
64
- fxAll,
65
- fxRace,
66
- fxRetry,
67
- fxUsing,
68
- type FxRetryOptions,
69
- type FxThunk,
70
- } from "./concurrency.ts";
66
+ import type { FxRetryOptions, FxThunk } from "./concurrency.ts";
71
67
  import { maskRedactedDeep, Redacted } from "./redacted.ts";
72
68
  import type { JournalSession, JournalStepOptions } from "./journal.ts";
73
69
 
@@ -82,9 +78,31 @@ import { okid } from "../okid.ts";
82
78
  import { lazyRequire } from "./lazy-require.ts";
83
79
  import type { AppMessageKey, MessageValues } from "../i18n/types.ts";
84
80
 
81
+ function loadFail(): FailFn {
82
+ return lazyRequire<typeof import("./fail-helpers.ts")>(
83
+ import.meta.dir,
84
+ ["fail", "helpers"].join("-"),
85
+ ).fail;
86
+ }
87
+
85
88
  /** Lazy runs/window helpers — kept off the cold `oke` static graph. */
86
- async function loadRunsWindow(): Promise<typeof import("../runs/window.ts")> {
87
- return import("../runs/window.ts");
89
+ function loadRunsWindow(): typeof import("../runs/window.ts") {
90
+ const stem = ["win", "dow"].join("");
91
+ try {
92
+ return lazyRequire(`${import.meta.dir}/../runs`, stem);
93
+ } catch {
94
+ return lazyRequire(import.meta.dir, stem);
95
+ }
96
+ }
97
+
98
+ /** Driver-backed SQL query builder — off the edge profile until `fx.store` opens SQL. */
99
+ function loadFxSql(): typeof import("./fx-sql-handle.ts") {
100
+ return lazyRequire(import.meta.dir, ["fx", "sql", "handle"].join("-"));
101
+ }
102
+
103
+ /** `fx.all` / `race` / `retry` / `using` — off the edge profile until called. */
104
+ function loadConcurrency(): typeof import("./concurrency.ts") {
105
+ return lazyRequire(import.meta.dir, ["concur", "rency"].join(""));
88
106
  }
89
107
 
90
108
  /**
@@ -383,29 +401,8 @@ export interface FxSearchOptions {
383
401
  readonly topK?: number;
384
402
  }
385
403
 
386
- /** Brand for {@link JsonResult} (kept internal — flows never construct it). */
387
- export const jsonResultBrand: unique symbol = Symbol.for("oke.json");
388
-
389
- /** Carrier from {@link FxJson} — status + body read by the response encoder. */
390
- export interface JsonResult<T = unknown> {
391
- readonly [jsonResultBrand]: true;
392
- readonly status: number;
393
- readonly value?: T;
394
- readonly meta?: Record<string, unknown>;
395
- readonly kind?: undefined;
396
- }
397
-
398
- /** SSE carrier from {@link FxJson.stream} / {@link Fx.live}. */
399
- export interface JsonStreamResult {
400
- readonly [jsonResultBrand]: true;
401
- readonly kind: "stream";
402
- readonly status: 200;
403
- readonly chunks: AsyncIterable<unknown>;
404
- /** Awaited before the 200 SSE body; throws OKE1210 on a missing resume cursor. */
405
- ready?: () => Promise<void>;
406
- /** Set by the kernel to commit journal / Runs after the stream settles. */
407
- finalize?: () => Promise<void>;
408
- }
404
+ export { isJsonResult, isJsonStreamResult, jsonResultBrand };
405
+ export type { JsonResult, JsonStreamResult };
409
406
 
410
407
  const sseFrameBrand: unique symbol = Symbol.for("oke.sse.frame");
411
408
 
@@ -435,26 +432,6 @@ export function isSseFrame(value: unknown): value is SseFrame {
435
432
  return typeof value === "object" && value !== null && (value as SseFrame)[sseFrameBrand] === true;
436
433
  }
437
434
 
438
- /** True when `value` is an {@link FxJson} JSON-envelope carrier. */
439
- export function isJsonResult(value: unknown): value is JsonResult {
440
- return (
441
- typeof value === "object" &&
442
- value !== null &&
443
- (value as JsonResult)[jsonResultBrand] === true &&
444
- (value as JsonStreamResult).kind !== "stream"
445
- );
446
- }
447
-
448
- /** True when `value` is an SSE stream carrier from {@link FxJson.stream}. */
449
- export function isJsonStreamResult(value: unknown): value is JsonStreamResult {
450
- return (
451
- typeof value === "object" &&
452
- value !== null &&
453
- (value as JsonStreamResult)[jsonResultBrand] === true &&
454
- (value as JsonStreamResult).kind === "stream"
455
- );
456
- }
457
-
458
435
  /**
459
436
  * Paginated JSON page — `data` is Flow `out`; `meta` is the HTTP pager.
460
437
  *
@@ -884,11 +861,10 @@ export interface Fx {
884
861
  /**
885
862
  * Flow-boundary failure value (does not throw).
886
863
  *
887
- * @param code - Declared error code (narrowed by clients via `error.code`)
888
- * @param data - Error payload
889
- * @param opts - Optional message
864
+ * Built-in helpers (`fx.fail.notFound`, `fx.fail.forbidden`, …) need no
865
+ * `errors:` declaration. Domain codes still use `fx.fail("OutOfStock", data)`.
890
866
  */
891
- fail<E>(code: string, data: E, opts?: FailOptions): FlowFailure<E>;
867
+ readonly fail: FailFn;
892
868
  /** JSON response helpers (status + Stripe-style envelope). */
893
869
  readonly json: FxJson;
894
870
  /**
@@ -1287,271 +1263,6 @@ export function createFxContext(options: CreateFxOptions): FxContext {
1287
1263
  };
1288
1264
  }
1289
1265
 
1290
- /**
1291
- * Lazy, capability-gated proxy over a driver-backed {@link SqlStoreHandle}.
1292
- *
1293
- * @param decl - Store declaration
1294
- * @param open - Opens (and caches) the runtime handle
1295
- */
1296
- function gatedSqlHandle(decl: StoreDecl, open: () => Promise<SqlStoreHandle>): SqlStoreHandle {
1297
- const ref = decl.ref as `sql:${string}`;
1298
- let cached: SqlStoreHandle | undefined;
1299
- const ensure = async (): Promise<SqlStoreHandle> => {
1300
- if (!cached) cached = await open();
1301
- return cached;
1302
- };
1303
- /** Driver-backed SQL has no dry-run transaction — refuse writes. */
1304
- const refuseDryRunWrite = (): void => {
1305
- if (isDryRun()) {
1306
- throw new DryRunWriteIsolationError(
1307
- `Driver-backed store "${ref}" cannot isolate writes during dry-run; dry-run refused rather than risk a double-write.`,
1308
- );
1309
- }
1310
- };
1311
- /**
1312
- * Gate a table-scoped SQL operation. Prefers the precise `sql:<table>`
1313
- * ref (matches what the compiler's AST inference derives from the same
1314
- * call site — {@link "../manifest/sql-resource.ts"}); falls back to the
1315
- * store-level ref when the table ref isn't declared — every flow that
1316
- * hand-declared the older `effects: { writes: ["sql:<store>"] }`
1317
- * convention (every existing template, `upsert-app.test.ts`, …) must
1318
- * keep working unchanged. Ledger / journal record whichever ref the
1319
- * capability check actually matched, not always the coarser one.
1320
- *
1321
- * @param kind - Effect kind
1322
- * @param table - Table argument passed to a `SqlStoreHandle` method
1323
- * @param body - Work to run under the gate
1324
- */
1325
- const gatedTable = <T>(
1326
- kind: Parameters<CapabilityToken["assert"]>[0],
1327
- table: unknown,
1328
- body: () => T | Promise<T>,
1329
- ): Promise<T> => {
1330
- const externalOf = (): EffectExternal | undefined => cached?.external;
1331
- const name = schemaTableName(table);
1332
- if (name !== undefined) {
1333
- const perTable = sqlTableRef(name);
1334
- if (perTable !== ref && capability.allows(kind, perTable)) {
1335
- return gated(kind, perTable, body, externalOf);
1336
- }
1337
- }
1338
- return gated(kind, ref, body, externalOf);
1339
- };
1340
-
1341
- return {
1342
- ref,
1343
- get routedRole() {
1344
- return cached?.routedRole ?? "primary";
1345
- },
1346
- get driverId() {
1347
- return cached?.driverId ?? "memory";
1348
- },
1349
- select: ((columns?: unknown) => {
1350
- return {
1351
- from(table: unknown) {
1352
- const run = (plan: {
1353
- where?: unknown;
1354
- orders?: readonly unknown[];
1355
- limit?: number;
1356
- offset?: number;
1357
- }): Promise<SqlRow[]> =>
1358
- gatedTable("read", table, async () => {
1359
- const h = await ensure();
1360
- const from = h.select(columns).from(table) as SelectFromBuilder;
1361
- const filtered = plan.where === undefined ? from : from.where(plan.where);
1362
- const ordered =
1363
- plan.orders === undefined ? filtered : filtered.orderBy(...plan.orders);
1364
- if (plan.offset !== undefined) return ordered.offset(plan.offset);
1365
- return plan.limit === undefined ? ordered : ordered.limit(plan.limit);
1366
- });
1367
-
1368
- const tail = (plan: {
1369
- where?: unknown;
1370
- orders?: readonly unknown[];
1371
- }): SelectOrderBuilder => ({
1372
- limit(n) {
1373
- return run({ ...plan, limit: n });
1374
- },
1375
- offset(n) {
1376
- return run({ ...plan, offset: n });
1377
- },
1378
- then(onfulfilled, onrejected) {
1379
- return run(plan).then(onfulfilled, onrejected);
1380
- },
1381
- });
1382
-
1383
- return {
1384
- where(where: unknown) {
1385
- return {
1386
- ...tail({ where }),
1387
- orderBy: (...orders: readonly unknown[]) => tail({ where, orders }),
1388
- };
1389
- },
1390
- orderBy: (...orders: readonly unknown[]) => tail({ orders }),
1391
- limit(n: number) {
1392
- return run({ limit: n });
1393
- },
1394
- offset(n: number) {
1395
- return run({ offset: n });
1396
- },
1397
- then(
1398
- onfulfilled: (value: SqlRow[]) => unknown,
1399
- onrejected?: (reason: unknown) => unknown,
1400
- ) {
1401
- return run({}).then(onfulfilled, onrejected);
1402
- },
1403
- };
1404
- },
1405
- };
1406
- }) as SqlStoreHandle["select"],
1407
- insert(table) {
1408
- return {
1409
- values(row) {
1410
- const runExecute = () =>
1411
- gatedTable("write", table, async () => {
1412
- refuseDryRunWrite();
1413
- const h = await ensure();
1414
- await h.insert(table).values(row).execute();
1415
- });
1416
- return {
1417
- returning() {
1418
- return gatedTable("write", table, async () => {
1419
- refuseDryRunWrite();
1420
- const h = await ensure();
1421
- return h.insert(table).values(row).returning();
1422
- });
1423
- },
1424
- execute: runExecute,
1425
- then(onfulfilled, onrejected) {
1426
- return runExecute().then(onfulfilled, onrejected);
1427
- },
1428
- };
1429
- },
1430
- };
1431
- },
1432
- update(table) {
1433
- return {
1434
- set(row) {
1435
- return {
1436
- where(where) {
1437
- return gatedTable("write", table, async () => {
1438
- refuseDryRunWrite();
1439
- const h = await ensure();
1440
- return h.update(table).set(row).where(where);
1441
- });
1442
- },
1443
- };
1444
- },
1445
- };
1446
- },
1447
- findById(table, id) {
1448
- return gatedTable("read", table, async () => {
1449
- const h = await ensure();
1450
- return h.findById(table, id);
1451
- });
1452
- },
1453
- delete(table: Parameters<SqlStoreHandle["delete"]>[0], id?: string) {
1454
- if (id !== undefined) {
1455
- return gatedTable("write", table, async () => {
1456
- refuseDryRunWrite();
1457
- const h = await ensure();
1458
- return h.delete(table, id);
1459
- });
1460
- }
1461
- return {
1462
- where(where: unknown) {
1463
- return gatedTable("write", table, async () => {
1464
- refuseDryRunWrite();
1465
- const h = await ensure();
1466
- return h.delete(table).where(where);
1467
- });
1468
- },
1469
- };
1470
- },
1471
- exists(table, idOrWhere) {
1472
- return gatedTable("read", table, async () => {
1473
- const h = await ensure();
1474
- return h.exists(table, idOrWhere);
1475
- });
1476
- },
1477
- upsert(table, matchOn, values, upsertOptions) {
1478
- return gatedTable("write", table, async () => {
1479
- refuseDryRunWrite();
1480
- const h = await ensure();
1481
- return h.upsert(table, matchOn, values, upsertOptions);
1482
- });
1483
- },
1484
- increment(table, id, column, by) {
1485
- return gatedTable("write", table, async () => {
1486
- refuseDryRunWrite();
1487
- const h = await ensure();
1488
- return h.increment(table, id, column, by);
1489
- });
1490
- },
1491
- raw(sql, params) {
1492
- return gated("read", ref, async () => {
1493
- const h = await ensure();
1494
- return h.raw(sql, params);
1495
- });
1496
- },
1497
- count(table, where) {
1498
- return gatedTable("read", table, async () => {
1499
- const h = await ensure();
1500
- return h.count(table, where);
1501
- });
1502
- },
1503
- page(table, pageOptions) {
1504
- return gatedTable("read", table, async () => {
1505
- const h = await ensure();
1506
- return h.page(table, pageOptions);
1507
- });
1508
- },
1509
- search(table, searchOptions) {
1510
- return gatedTable("read", table, async () => {
1511
- const h = await ensure();
1512
- const result = await h.search(table, searchOptions);
1513
- // Optional rerank via fx.ask — only when explicitly requested.
1514
- if (
1515
- searchOptions.rerank &&
1516
- typeof searchOptions.rerank === "object" &&
1517
- searchOptions.rerank.model
1518
- ) {
1519
- const model = searchOptions.rerank.model;
1520
- const pk = "id";
1521
- const docs = result.data.map((row) => ({
1522
- id: String(row[pk] ?? ""),
1523
- text: Object.values(row)
1524
- .filter((v) => typeof v === "string")
1525
- .join("\n"),
1526
- score: 0,
1527
- }));
1528
- const out = (await fx.ask(model, {
1529
- query: searchOptions.query,
1530
- docs,
1531
- })) as { rankedIds?: string[] };
1532
- if (out.rankedIds && out.rankedIds.length > 0) {
1533
- const byId = new Map(result.data.map((r) => [String(r[pk] ?? ""), r]));
1534
- return {
1535
- ...result,
1536
- data: out.rankedIds
1537
- .map((id) => byId.get(id))
1538
- .filter((r): r is NonNullable<typeof r> => r !== undefined),
1539
- };
1540
- }
1541
- }
1542
- return result;
1543
- });
1544
- },
1545
- ensureTable(table) {
1546
- return gatedTable("write", table, async () => {
1547
- refuseDryRunWrite();
1548
- const h = await ensure();
1549
- return h.ensureTable(table);
1550
- });
1551
- },
1552
- } as SqlStoreHandle;
1553
- }
1554
-
1555
1266
  function loadFxTenantStore(): {
1556
1267
  kv: (
1557
1268
  mode: "in" | "out",
@@ -1611,9 +1322,12 @@ export function createFxContext(options: CreateFxOptions): FxContext {
1611
1322
  };
1612
1323
 
1613
1324
  if (decl.facet === "sql") {
1614
- return gatedSqlHandle(decl, async () => {
1615
- const h = await open();
1616
- return h as SqlStoreHandle;
1325
+ return loadFxSql().createGatedSqlHandle({
1326
+ decl,
1327
+ open: async () => (await open()) as SqlStoreHandle,
1328
+ gated,
1329
+ capability,
1330
+ ask: (model, input) => fx.ask(model, input),
1617
1331
  });
1618
1332
  }
1619
1333
 
@@ -2246,7 +1960,7 @@ export function createFxContext(options: CreateFxOptions): FxContext {
2246
1960
  operator,
2247
1961
  principal,
2248
1962
  tenant,
2249
- fail,
1963
+ fail: loadFail(),
2250
1964
  json: {
2251
1965
  ok<T>(value: T, opts?: { readonly meta?: Record<string, unknown> }): JsonResult<T> {
2252
1966
  return {
@@ -2286,16 +2000,16 @@ export function createFxContext(options: CreateFxOptions): FxContext {
2286
2000
  return rlsInvokeContext().rls;
2287
2001
  },
2288
2002
  all(thunks) {
2289
- return fxAll(thunks);
2003
+ return loadConcurrency().fxAll(thunks);
2290
2004
  },
2291
2005
  race(thunks) {
2292
- return fxRace(thunks);
2006
+ return loadConcurrency().fxRace(thunks);
2293
2007
  },
2294
2008
  retry(fn, opts) {
2295
- return fxRetry(fn, opts);
2009
+ return loadConcurrency().fxRetry(fn, opts);
2296
2010
  },
2297
2011
  using(acquire, release, use) {
2298
- return fxUsing(acquire, release, use);
2012
+ return loadConcurrency().fxUsing(acquire, release, use);
2299
2013
  },
2300
2014
  };
2301
2015
 
@@ -106,14 +106,15 @@ export {
106
106
  type RunTelemetry,
107
107
  } from "./run-telemetry.ts";
108
108
 
109
+ export { fail } from "./fail-helpers.ts";
109
110
  export {
110
- fail,
111
111
  formatOkeMessage,
112
112
  lookupOkeError,
113
113
  OKE_ERROR_RANGES,
114
114
  OKE_ERRORS,
115
115
  OkeError,
116
116
  throwOke,
117
+ type FailFn,
117
118
  type FailOptions,
118
119
  type FlowErrorValue,
119
120
  type FlowFailure,
@@ -122,6 +123,16 @@ export {
122
123
  type OkeErrorDomain,
123
124
  type OkeErrorParams,
124
125
  } from "./errors.ts";
126
+ export {
127
+ BUILTIN_ERROR_STATUS,
128
+ httpStatusForFailure,
129
+ statusForBuiltinError,
130
+ type BuiltinErrorBag,
131
+ type BuiltinErrorCode,
132
+ type BuiltinErrorMap,
133
+ type BuiltinValidationIssue,
134
+ type DatabaseErrorReason,
135
+ } from "./builtin-errors.ts";
125
136
 
126
137
  export {
127
138
  flow,
@@ -0,0 +1,59 @@
1
+ /**
2
+ * `fx.json` carriers — brand and guards, with no `fx` / Store graph.
3
+ *
4
+ * `project-out` and the HTTP encoder need these on the cold-start path.
5
+ * Importing them from `fx.ts` would evaluate the Store barrel (Zod, hybrid
6
+ * search) before the server listens.
7
+ */
8
+
9
+ /** Brand for {@link JsonResult} (kept internal — flows never construct it). */
10
+ export const jsonResultBrand: unique symbol = Symbol.for("oke.json");
11
+
12
+ /** Carrier from `fx.json` — status + body read by the response encoder. */
13
+ export interface JsonResult<T = unknown> {
14
+ readonly [jsonResultBrand]: true;
15
+ readonly status: number;
16
+ readonly value?: T;
17
+ readonly meta?: Record<string, unknown>;
18
+ readonly kind?: undefined;
19
+ }
20
+
21
+ /** SSE carrier from `fx.json.stream` / `fx.live`. */
22
+ export interface JsonStreamResult {
23
+ readonly [jsonResultBrand]: true;
24
+ readonly kind: "stream";
25
+ readonly status: 200;
26
+ readonly chunks: AsyncIterable<unknown>;
27
+ /** Awaited before the 200 SSE body; throws OKE1210 on a missing resume cursor. */
28
+ ready?: () => Promise<void>;
29
+ /** Set by the kernel to commit journal / Runs after the stream settles. */
30
+ finalize?: () => Promise<void>;
31
+ }
32
+
33
+ /**
34
+ * True when `value` is an `fx.json` envelope carrier.
35
+ *
36
+ * @param value - Unknown handler output
37
+ */
38
+ export function isJsonResult(value: unknown): value is JsonResult {
39
+ return (
40
+ typeof value === "object" &&
41
+ value !== null &&
42
+ (value as JsonResult)[jsonResultBrand] === true &&
43
+ (value as JsonStreamResult).kind !== "stream"
44
+ );
45
+ }
46
+
47
+ /**
48
+ * True when `value` is an SSE stream carrier from `fx.json.stream`.
49
+ *
50
+ * @param value - Unknown handler output
51
+ */
52
+ export function isJsonStreamResult(value: unknown): value is JsonStreamResult {
53
+ return (
54
+ typeof value === "object" &&
55
+ value !== null &&
56
+ (value as JsonStreamResult)[jsonResultBrand] === true &&
57
+ (value as JsonStreamResult).kind === "stream"
58
+ );
59
+ }
@@ -12,7 +12,12 @@
12
12
 
13
13
  import type { SchemaInput } from "../validation/standard-schema.ts";
14
14
  import { validate } from "../validation/standard-schema.ts";
15
- import { isJsonResult, isJsonStreamResult, jsonResultBrand, type JsonResult } from "./fx.ts";
15
+ import {
16
+ isJsonResult,
17
+ isJsonStreamResult,
18
+ jsonResultBrand,
19
+ type JsonResult,
20
+ } from "./json-result.ts";
16
21
 
17
22
  /**
18
23
  * Project `output` through `schema` when the exposure declared `out`.