okengine 0.19.1 → 0.19.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 (68) hide show
  1. package/package.json +1 -1
  2. package/site/content/docs/elements/clock/index.mdx +162 -11
  3. package/site/content/docs/elements/clock/schedules.mdx +6 -3
  4. package/site/content/docs/elements/flow/consumers.mdx +137 -79
  5. package/site/content/docs/elements/flow/index.mdx +14 -14
  6. package/site/content/docs/elements/flow/routing.mdx +18 -2
  7. package/site/content/docs/elements/signal/broadcast.mdx +9 -13
  8. package/site/content/docs/elements/signal/index.mdx +245 -13
  9. package/site/content/docs/elements/signal/live.mdx +5 -13
  10. package/site/content/docs/elements/signal/once.mdx +83 -28
  11. package/site/content/docs/elements/store/sql.mdx +2 -2
  12. package/site/content/docs/plugins/anonymous.mdx +1 -1
  13. package/site/content/docs/plugins/cors.mdx +1 -1
  14. package/site/content/docs/plugins/csrf.mdx +1 -1
  15. package/site/content/docs/plugins/headers.mdx +1 -1
  16. package/site/content/docs/plugins/ip-allowlist.mdx +1 -1
  17. package/site/content/docs/plugins/maintenance-mode.mdx +1 -1
  18. package/site/content/docs/reference/errors.mdx +34 -26
  19. package/site/content/docs/reference/fx.mdx +6 -6
  20. package/site/content/docs/reference/okid.mdx +1 -1
  21. package/site/content/docs/reference/plugins.mdx +1 -1
  22. package/site/content/docs/understand/the-architecture.mdx +1 -1
  23. package/src/compiler/extract.test.ts +123 -1
  24. package/src/compiler/extract.ts +134 -7
  25. package/src/compiler/search-writer-isolation.test.ts +0 -1
  26. package/src/console/ui-next/dist/assets/{access-page-C_qLDhTq.js → access-page-BpugjHHY.js} +1 -1
  27. package/src/console/ui-next/dist/assets/{agent-disclosure-BHVqr3TN.js → agent-disclosure-qnsmtJhf.js} +1 -1
  28. package/src/console/ui-next/dist/assets/{cache-glyph-CKe92lRQ.js → cache-glyph-BcpWUM98.js} +1 -1
  29. package/src/console/ui-next/dist/assets/{call-pii-button-DEwTl8ZX.js → call-pii-button-Bz5xiNmR.js} +1 -1
  30. package/src/console/ui-next/dist/assets/{collapsible-BCBtDrCt.js → collapsible-Cuxn2WH8.js} +1 -1
  31. package/src/console/ui-next/dist/assets/{duration-tone-JroqeuCp.js → duration-tone-Bjnl3EaM.js} +1 -1
  32. package/src/console/ui-next/dist/assets/{flows-page-CVHa0RTt.js → flows-page-DVujp-T2.js} +1 -1
  33. package/src/console/ui-next/dist/assets/{highlighted-json-DjJW6hqe.js → highlighted-json-Bql0qlqW.js} +1 -1
  34. package/src/console/ui-next/dist/assets/{http-method-DC5HBdLU.js → http-method-BljvrfRg.js} +1 -1
  35. package/src/console/ui-next/dist/assets/{index-CYjiZ3WO.js → index-D1vE656k.js} +3 -3
  36. package/src/console/ui-next/dist/assets/{observability-page-CAYMyKb3.js → observability-page-Bdi0bLJl.js} +1 -1
  37. package/src/console/ui-next/dist/assets/{replica-lag-yAQYLv75.js → replica-lag-CpkPfITG.js} +1 -1
  38. package/src/console/ui-next/dist/assets/{request-meta-D0yusGxJ.js → request-meta-C4ZVNFVt.js} +1 -1
  39. package/src/console/ui-next/dist/assets/{store-page-BTKJeJ02.js → store-page-DAHtnesC.js} +1 -1
  40. package/src/console/ui-next/dist/assets/{trace-detail-sheet-Bp-Yygs5.js → trace-detail-sheet-B2c9QRBw.js} +1 -1
  41. package/src/console/ui-next/dist/assets/{tree-expand-toggle-DoaVDfAM.js → tree-expand-toggle-BP4tRC98.js} +1 -1
  42. package/src/console/ui-next/dist/assets/{units-page-BRz7xyYL.js → units-page-Oi6--n09.js} +1 -1
  43. package/src/console/ui-next/dist/assets/{vault-page-3jQt-bOJ.js → vault-page-Dt-cPkiU.js} +1 -1
  44. package/src/console/ui-next/dist/index.html +1 -1
  45. package/src/elements/store/live-default.test.ts +8 -0
  46. package/src/elements/store/search-embed-flow.ts +2 -2
  47. package/src/full.ts +3 -0
  48. package/src/http.ts +3 -0
  49. package/src/index.ts +3 -0
  50. package/src/kernel/app.ts +94 -10
  51. package/src/kernel/boot.ts +1 -1
  52. package/src/kernel/cdc-payload.test.ts +224 -0
  53. package/src/kernel/cdc-payload.ts +146 -0
  54. package/src/kernel/errors-flow-name.ts +17 -0
  55. package/src/kernel/errors-once-signal.ts +25 -0
  56. package/src/kernel/errors.registry.test.ts +14 -4
  57. package/src/kernel/flow-name.test.ts +104 -0
  58. package/src/kernel/flow.ts +3 -2
  59. package/src/kernel/fx-emit-types.test.ts +31 -0
  60. package/src/kernel/fx.test.ts +2 -1
  61. package/src/kernel/fx.ts +13 -3
  62. package/src/kernel/index.ts +2 -0
  63. package/src/kernel/on.ts +5 -0
  64. package/src/kernel/once-signal.test.ts +71 -0
  65. package/src/kernel/stamp-http.test.ts +13 -0
  66. package/src/kernel/stamp-http.ts +21 -3
  67. package/src/kernel/unit.ts +4 -2
  68. package/src/kernel-entry.ts +3 -0
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Once-signal uniqueness (OKE1071) — two different Flows on `signal.once`.
3
+ */
4
+
5
+ import { beforeEach, describe, expect, test } from "bun:test";
6
+ import { resetClocks } from "../elements/clock/declare.ts";
7
+ import { resetSignals, signal } from "../elements/signal/declare.ts";
8
+ import { oke } from "./app.ts";
9
+ import { OkeError } from "./errors.ts";
10
+ import { ONCE_SIGNAL_MULTI_FLOW } from "./errors-once-signal.ts";
11
+ import { flow, resetFlowSeq } from "./flow.ts";
12
+ import { on, resetBindings } from "./on.ts";
13
+
14
+ beforeEach(() => {
15
+ resetBindings();
16
+ resetFlowSeq();
17
+ resetSignals();
18
+ resetClocks();
19
+ });
20
+
21
+ describe("oke — ONCE_SIGNAL_MULTI_FLOW OKE1071", () => {
22
+ test("two differently-named flows on the same once signal fail OKE1071", () => {
23
+ const orderPlaced = signal.once("orders.placed");
24
+ on(orderPlaced, flow("orders.charge", { do: () => ({ a: true }) }));
25
+ on(orderPlaced, flow("orders.ship", { do: () => ({ b: true }) }));
26
+ try {
27
+ oke({ name: "t", autoBoot: false });
28
+ expect.unreachable("oke() should throw OKE1071");
29
+ } catch (err) {
30
+ expect(err).toBeInstanceOf(OkeError);
31
+ const okeErr = err as OkeError;
32
+ expect(okeErr.code).toBe(1071);
33
+ expect(okeErr.code).toBe(ONCE_SIGNAL_MULTI_FLOW.code);
34
+ expect(okeErr.message).toMatch(/OKE1071/);
35
+ expect(okeErr.causeText).toContain("orders.placed");
36
+ expect(okeErr.causeText).toContain("orders.charge");
37
+ expect(okeErr.causeText).toContain("orders.ship");
38
+ expect(okeErr.fix).toMatch(/signal\.broadcast/);
39
+ }
40
+ });
41
+
42
+ test("the same scenario with signal.broadcast does not fail", () => {
43
+ const catalogChanged = signal.broadcast("catalog.changed");
44
+ on(catalogChanged, flow("cache.invalidate", { do: () => ({ a: true }) }));
45
+ on(catalogChanged, flow("search.reindex", { do: () => ({ b: true }) }));
46
+ expect(() => oke({ name: "t", autoBoot: false })).not.toThrow();
47
+ });
48
+
49
+ test("two differently-named flows on signal.live do not fail OKE1071", () => {
50
+ const status = signal.live("order-status", { optional: true });
51
+ on(status, flow("orders.mirrorA", { do: () => ({ a: true }) }));
52
+ on(status, flow("orders.mirrorB", { do: () => ({ b: true }) }));
53
+ expect(() => oke({ name: "t", autoBoot: false })).not.toThrow();
54
+ });
55
+
56
+ test("a single flow bound to once boots with zero warnings", () => {
57
+ const warnings: string[] = [];
58
+ const orig = console.warn;
59
+ console.warn = (...args: unknown[]) => {
60
+ warnings.push(args.map(String).join(" "));
61
+ };
62
+ try {
63
+ const emailTask = signal.once("tasks.email");
64
+ on(emailTask, flow("workers.email", { do: () => ({ ok: true }) }));
65
+ expect(() => oke({ name: "t", autoBoot: false })).not.toThrow();
66
+ expect(warnings).toEqual([]);
67
+ } finally {
68
+ console.warn = orig;
69
+ }
70
+ });
71
+ });
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import { describe, expect, test, beforeEach } from "bun:test";
6
+ import { resetSignals, signal } from "../elements/signal/declare.ts";
6
7
  import type { RuntimeRouteMap } from "./adopt-routes.ts";
7
8
  import { oke } from "./app.ts";
8
9
  import { flow, resetFlowSeq } from "./flow.ts";
@@ -16,6 +17,7 @@ beforeEach(() => {
16
17
  resetBindings();
17
18
  resetFlowSeq();
18
19
  resetRegisteredFlowUnits();
20
+ resetSignals();
19
21
  });
20
22
 
21
23
  describe("stampHttpPath", () => {
@@ -49,6 +51,17 @@ describe("stampFlowName", () => {
49
51
  stampFlowName(f, "notes.list");
50
52
  expect(f.name).toBe("notes.get");
51
53
  });
54
+
55
+ test("file-tree stamp overwrites a trigger-inherited name", () => {
56
+ const bound = on(
57
+ signal.once("note-created"),
58
+ flow({
59
+ do: () => ({ ok: true }),
60
+ }),
61
+ );
62
+ stampFlowName(bound, "notes.onCreated");
63
+ expect(bound.name).toBe("notes.onCreated");
64
+ });
52
65
  });
53
66
 
54
67
  describe("oke — unresolved sentinel and duplicate routes", () => {
@@ -35,8 +35,25 @@ export function stampHttpPath<F extends AnyFlowDef, P extends string>(
35
35
  return flow as StampHttpPath<F, P>;
36
36
  }
37
37
 
38
+ /** Mutable name fields used by {@link stampFlowName} / {@link on}. */
39
+ type FlowNameCarrier = {
40
+ name: string;
41
+ unit: string | undefined;
42
+ $n?: boolean;
43
+ };
44
+
45
+ /**
46
+ * True when a Flow name is still a placeholder: empty, synthetic `flow_*`,
47
+ * or inherited from a Signal/Clock trigger (`$n`). File-tree / `unit()` may overwrite.
48
+ *
49
+ * @param flow - Flow name carrier
50
+ */
51
+ export function isUnsetFlowName(flow: { readonly name: string; readonly $n?: boolean }): boolean {
52
+ return !flow.name || flow.name.startsWith("flow_") || flow.$n === true;
53
+ }
54
+
38
55
  /**
39
- * Stamp `unit.export` onto a nameless (or `flow_*`) Flow.
56
+ * Stamp `unit.export` onto a nameless (or `flow_*` / trigger-inherited) Flow.
40
57
  *
41
58
  * Explicit `flow("notes.get")` wins. Fills `unit` when missing.
42
59
  *
@@ -45,9 +62,10 @@ export function stampHttpPath<F extends AnyFlowDef, P extends string>(
45
62
  */
46
63
  export function stampFlowName<F extends AnyFlowDef>(flow: F, name: string): F {
47
64
  if (!isFlow(flow)) return flow;
48
- const f = flow as { name: string; unit: string | undefined };
49
- if (!f.name || f.name.startsWith("flow_")) {
65
+ const f = flow as FlowNameCarrier;
66
+ if (isUnsetFlowName(f)) {
50
67
  f.name = name;
68
+ f.$n = false;
51
69
  }
52
70
  if (!f.unit) {
53
71
  const resolved = f.name || name;
@@ -5,6 +5,7 @@
5
5
  * unit. Plugins queue until an app flushes them at boot / adopt time.
6
6
  */
7
7
 
8
+ import { isUnsetFlowName } from "./stamp-http.ts";
8
9
  import type { AnyFlowDef, FlowDef } from "./flow.ts";
9
10
  import type { PluginDef } from "./plugin.ts";
10
11
 
@@ -40,8 +41,9 @@ export function unit<const T extends Record<string, AnyFlowDef>>(
40
41
  const pending: PluginDef[] = [];
41
42
  for (const [exportName, flowDef] of Object.entries(flows)) {
42
43
  const f = flowDef as FlowDef;
43
- if (!f.name || f.name.startsWith("flow_")) {
44
- (f as { name: string }).name = `${name}.${exportName}`;
44
+ if (isUnsetFlowName(f)) {
45
+ (f as { name: string; $n?: boolean }).name = `${name}.${exportName}`;
46
+ (f as { $n?: boolean }).$n = false;
45
47
  }
46
48
  if (!f.unit) {
47
49
  (f as { unit: string }).unit = name;
@@ -27,6 +27,9 @@ export {
27
27
  type OkeApp,
28
28
  type OkeOptions,
29
29
  type ReadyState,
30
+ type CdcAction,
31
+ type CdcPayload,
32
+ type CdcPayloadInput,
30
33
  type FlowDef,
31
34
  type FlowFailure,
32
35
  type FlowErrorValue,