okengine 0.10.1 → 0.10.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 (141) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/site/content/docs/ai/mcp.mdx +0 -1
  4. package/site/content/docs/elements/ai.mdx +0 -6
  5. package/site/content/docs/elements/channel.mdx +0 -6
  6. package/site/content/docs/elements/clock.mdx +2 -8
  7. package/site/content/docs/elements/flow.mdx +10 -17
  8. package/site/content/docs/elements/gate.mdx +4 -10
  9. package/site/content/docs/elements/signal.mdx +4 -13
  10. package/site/content/docs/elements/store.mdx +9 -16
  11. package/site/content/docs/elements/vault.mdx +1 -7
  12. package/site/content/docs/get-started/basic-usage.mdx +1 -9
  13. package/site/content/docs/get-started/introduction.mdx +1 -1
  14. package/site/content/docs/get-started/why.mdx +1 -2
  15. package/site/content/docs/index.mdx +0 -1
  16. package/site/content/docs/meta.json +0 -1
  17. package/site/content/docs/reference/cli.md +12 -12
  18. package/site/content/docs/reference/fx.mdx +6 -7
  19. package/src/auth/auth.test.ts +1 -2
  20. package/src/auth/bindings.ts +5 -15
  21. package/src/auth/gate-auth.test.ts +2 -2
  22. package/src/cli/ai-setup/ai-setup.test.ts +3 -1
  23. package/src/cli/ai-setup/apply.ts +13 -0
  24. package/src/cli/build.test.ts +67 -0
  25. package/src/cli/build.ts +33 -0
  26. package/src/cli/db-seed.ts +1 -1
  27. package/src/cli/dev-controls.test.ts +43 -48
  28. package/src/cli/dev-controls.ts +23 -172
  29. package/src/cli/dev.test.ts +90 -5
  30. package/src/cli/dev.ts +39 -16
  31. package/src/cli/doctor.ts +98 -5
  32. package/src/cli/load-config.images.test.ts +31 -1
  33. package/src/cli/load-config.ts +2 -2
  34. package/src/cli/safe-defaults.test.ts +1 -1
  35. package/src/client/notes-contract.test.ts +3 -4
  36. package/src/compiler/aot.test.ts +1 -2
  37. package/src/compiler/extract.test.ts +76 -21
  38. package/src/compiler/extract.ts +15 -3
  39. package/src/compiler/fixtures/raw/raw-unannotated.ts +2 -4
  40. package/src/compiler/fixtures/skyport/oke.config.ts +4 -2
  41. package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +4 -8
  42. package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +1 -2
  43. package/src/compiler/fixtures/skyport/src/flows/support/index.ts +1 -2
  44. package/src/compiler/fixtures/skyport.expected.json +3 -3
  45. package/src/compiler/fixtures/triggers/five-triggers.ts +5 -10
  46. package/src/compiler/generate-adopt.test.ts +85 -0
  47. package/src/compiler/generate-adopt.ts +111 -0
  48. package/src/config/driver-defaults.test.ts +122 -0
  49. package/src/config/driver-defaults.ts +186 -0
  50. package/src/config/index.ts +97 -7
  51. package/src/console/server/dry-run.audit.test.ts +2 -2
  52. package/src/console/server/flows.ts +50 -150
  53. package/src/docker/compose.ts +13 -3
  54. package/src/docker/images-config.test.ts +168 -0
  55. package/src/docker/recipes/llama-cpp.ts +10 -1
  56. package/src/docker/stack-id.ts +1 -0
  57. package/src/elements/channel/declare.ts +27 -1
  58. package/src/elements/clock/chaos-child.ts +3 -6
  59. package/src/elements/clock/chaos.test.ts +1 -2
  60. package/src/elements/clock.test.ts +2 -4
  61. package/src/elements/signal/declare.ts +25 -1
  62. package/src/elements/store/declare.ts +27 -1
  63. package/src/elements/store/resource.ts +6 -11
  64. package/src/elements/store/upsert-app.test.ts +1 -2
  65. package/src/elements/vault/declare.ts +29 -1
  66. package/src/kernel/adopt-barrel-fresh.test.ts +103 -0
  67. package/src/kernel/app.ts +66 -9
  68. package/src/kernel/auto-registry.test.ts +198 -0
  69. package/src/kernel/boot-bind/channel.ts +12 -4
  70. package/src/kernel/boot-bind/clock.ts +3 -3
  71. package/src/kernel/boot-bind/gate.ts +7 -4
  72. package/src/kernel/boot-bind/honor-config.test.ts +5 -5
  73. package/src/kernel/boot-bind/journal.ts +3 -3
  74. package/src/kernel/boot-bind/signal.ts +3 -1
  75. package/src/kernel/boot-bind/store.test.ts +19 -0
  76. package/src/kernel/boot-bind/store.ts +24 -9
  77. package/src/kernel/boot-bind/vault.ts +3 -3
  78. package/src/kernel/boot.test.ts +19 -15
  79. package/src/kernel/boot.ts +81 -1
  80. package/src/kernel/call.test.ts +5 -10
  81. package/src/kernel/compensate.test.ts +3 -6
  82. package/src/kernel/concurrency.test.ts +3 -5
  83. package/src/kernel/correlation.test.ts +4 -8
  84. package/src/kernel/edge.test.ts +1 -1
  85. package/src/kernel/effects-stamping.test.ts +4 -7
  86. package/src/kernel/element-registries.ts +27 -0
  87. package/src/kernel/errors.ts +11 -0
  88. package/src/kernel/flow.test.ts +8 -15
  89. package/src/kernel/flow.ts +12 -14
  90. package/src/kernel/hook-timing.test.ts +2 -2
  91. package/src/kernel/hooks.test.ts +5 -10
  92. package/src/kernel/horizontal-child.ts +5 -10
  93. package/src/kernel/journal-boot.test.ts +2 -4
  94. package/src/kernel/pipeline.test.ts +6 -12
  95. package/src/kernel/plugin/capabilities.test.ts +1 -1
  96. package/src/kernel/plugin/decorate.test.ts +3 -8
  97. package/src/kernel/plugin/scoping.test.ts +6 -16
  98. package/src/kernel/plugin-elements.test.ts +1 -1
  99. package/src/kernel/plugin-needs.test.ts +1 -1
  100. package/src/kernel/ready.test.ts +1 -2
  101. package/src/kernel/registry-isolation.test.ts +5 -5
  102. package/src/kernel/triggers.ts +1 -1
  103. package/src/plugins/anonymous.ts +1 -3
  104. package/src/plugins/compression.test.ts +6 -8
  105. package/src/plugins/config-source.test.ts +1 -1
  106. package/src/plugins/config-source.ts +1 -2
  107. package/src/plugins/cors.test.ts +10 -10
  108. package/src/plugins/csrf.test.ts +2 -2
  109. package/src/plugins/headers.test.ts +14 -15
  110. package/src/plugins/ip-allowlist.test.ts +9 -9
  111. package/src/plugins/magic-link.ts +2 -6
  112. package/src/plugins/maintenance-mode.test.ts +6 -6
  113. package/src/plugins/otp.ts +5 -15
  114. package/src/plugins/passkey.ts +4 -12
  115. package/src/plugins/two-factor.ts +3 -9
  116. package/src/plugins/username.ts +2 -6
  117. package/src/release/measure.ts +1 -1
  118. package/src/runs/runs.test.ts +3 -5
  119. package/src/runtime/serve.test.ts +2 -4
  120. package/src/test/create-test-app.test.ts +3 -7
  121. package/src/test/provisions.integration.test.ts +4 -9
  122. package/src/test/reset-element-registries.ts +31 -0
  123. package/site/content/docs/console/access.mdx +0 -29
  124. package/site/content/docs/console/ai.mdx +0 -35
  125. package/site/content/docs/console/architecture.mdx +0 -35
  126. package/site/content/docs/console/channels.mdx +0 -37
  127. package/site/content/docs/console/clock.mdx +0 -51
  128. package/site/content/docs/console/flows.mdx +0 -31
  129. package/site/content/docs/console/gates.mdx +0 -73
  130. package/site/content/docs/console/index.mdx +0 -54
  131. package/site/content/docs/console/manifest-diff.mdx +0 -34
  132. package/site/content/docs/console/meta.json +0 -24
  133. package/site/content/docs/console/overview.mdx +0 -40
  134. package/site/content/docs/console/plugins.mdx +0 -41
  135. package/site/content/docs/console/privacy.mdx +0 -32
  136. package/site/content/docs/console/runs.mdx +0 -50
  137. package/site/content/docs/console/signals.mdx +0 -40
  138. package/site/content/docs/console/store.mdx +0 -32
  139. package/site/content/docs/console/tenancy.mdx +0 -32
  140. package/site/content/docs/console/traces.mdx +0 -38
  141. package/site/content/docs/console/vault.mdx +0 -41
@@ -131,16 +131,13 @@ const storeRt = createStoreRuntime({
131
131
  });
132
132
  storeRt.register(db);
133
133
 
134
- const ping = flow({
135
- name: "horizontal.ping",
134
+ const ping = flow("horizontal.ping", {
136
135
  do: () => ({ ok: true as const, instanceId }),
137
136
  });
138
- const rate = flow({
139
- name: "horizontal.rate",
137
+ const rate = flow("horizontal.rate", {
140
138
  do: () => ({ ok: true as const, instanceId }),
141
139
  });
142
- const write = flow({
143
- name: "horizontal.write",
140
+ const write = flow("horizontal.write", {
144
141
  do: async (_input, fx) => {
145
142
  const id = crypto.randomUUID();
146
143
  await fx
@@ -155,15 +152,13 @@ const write = flow({
155
152
  return { id };
156
153
  },
157
154
  });
158
- const emit = flow({
159
- name: "horizontal.emit",
155
+ const emit = flow("horizontal.emit", {
160
156
  do: async (_input, fx) => {
161
157
  await fx.emit(SIG, { from: instanceId, at: Date.now() });
162
158
  return { emitted: true as const };
163
159
  },
164
160
  });
165
- const charge = flow({
166
- name: "horizontal.charge",
161
+ const charge = flow("horizontal.charge", {
167
162
  durable: true,
168
163
  do: async (_input, fx) => {
169
164
  await fx.step("create-intent", async () => {
@@ -73,8 +73,7 @@ async function waitFor(cond: () => Promise<boolean>, timeoutMs = 5_000): Promise
73
73
 
74
74
  /** Flow whose step 2 blocks once per execution; the test unblocks survivors. */
75
75
  function blockingFlow(step1Calls: string[], blockers: Array<() => void>): Binding {
76
- const charge = flow({
77
- name: "charge",
76
+ const charge = flow("charge", {
78
77
  durable: true,
79
78
  do: async (_input, fx) => {
80
79
  await fx.step("create-intent", () => {
@@ -96,8 +95,7 @@ function blockingFlow(step1Calls: string[], blockers: Array<() => void>): Bindin
96
95
 
97
96
  /** Flow that sleeps `wake`, then records one confirm execution. */
98
97
  function sleeperBinding(wake: string, confirms: string[]): Binding {
99
- const sleeper = flow({
100
- name: "sleeper",
98
+ const sleeper = flow("sleeper", {
101
99
  durable: true,
102
100
  do: async (_input, fx) => {
103
101
  await fx.step("create-intent", () => ({ id: "pi_1" }));
@@ -72,8 +72,7 @@ describe("pipeline — Unauthorized for anonymous", () => {
72
72
 
73
73
  on(
74
74
  http.post("/orders").gate(member),
75
- flow({
76
- name: "orders.create",
75
+ flow("orders.create", {
77
76
  in: z.object({ sku: z.string() }),
78
77
  out: z.object({ id: z.string() }),
79
78
  do: (_input, fx) => ({ id: fx.id() }),
@@ -111,8 +110,7 @@ describe("pipeline — Unauthorized for anonymous", () => {
111
110
 
112
111
  on(
113
112
  http.post("/orders").gate(member, canOrder),
114
- flow({
115
- name: "orders.create2",
113
+ flow("orders.create2", {
116
114
  in: z.object({ sku: z.string() }),
117
115
  do: () => ({ ok: true }),
118
116
  }),
@@ -150,8 +148,7 @@ describe("pipeline — evaluated gates on the run", () => {
150
148
 
151
149
  on(
152
150
  http.post("/orders").gate(member, canOrder),
153
- flow({
154
- name: "orders.create3",
151
+ flow("orders.create3", {
155
152
  in: z.object({ sku: z.string() }),
156
153
  do: () => ({ ok: true }),
157
154
  }),
@@ -203,8 +200,7 @@ describe("pipeline — Bearer cryptographic verification", () => {
203
200
 
204
201
  on(
205
202
  http.get("/secure").gate(member),
206
- flow({
207
- name: "secure.get",
203
+ flow("secure.get", {
208
204
  do: () => ({ ok: true }),
209
205
  }),
210
206
  );
@@ -242,8 +238,7 @@ describe("pipeline — Bearer cryptographic verification", () => {
242
238
 
243
239
  on(
244
240
  http.get("/secure").gate(member),
245
- flow({
246
- name: "secure.get2",
241
+ flow("secure.get2", {
247
242
  do: () => ({ ok: true }),
248
243
  }),
249
244
  );
@@ -313,8 +308,7 @@ describe("pipeline — Bearer cryptographic verification", () => {
313
308
 
314
309
  on(
315
310
  http.post("/orders").gate(member),
316
- flow({
317
- name: "orders.inject",
311
+ flow("orders.inject", {
318
312
  in: z.object({ sku: z.string() }),
319
313
  do: () => ({ ok: true }),
320
314
  }),
@@ -25,7 +25,7 @@ describe("plugin capability capture", () => {
25
25
  .table("audit_events")
26
26
  .driver("postgres")
27
27
  .image("store.sql", "postgres:16")
28
- .flow(flow({ name: "audit.flush", do: () => undefined }))
28
+ .flow(flow("audit.flush", { do: () => undefined }))
29
29
  .client("audit", { subscribe: true });
30
30
 
31
31
  const { api, snapshot } = createRecordingApi({
@@ -27,8 +27,7 @@ describe("plugin decorate — types and runtime", () => {
27
27
  let seen: unknown;
28
28
  on(
29
29
  http.get("/x"),
30
- flow({
31
- name: "x",
30
+ flow("x", {
32
31
  do: (_input, _fx) => {
33
32
  return { ok: true };
34
33
  },
@@ -75,9 +74,7 @@ describe("plugin decorate — types and runtime", () => {
75
74
 
76
75
  on(
77
76
  http.get("/orders"),
78
- flow({
79
- name: "orders.list",
80
- unit: "orders",
77
+ flow("orders.list", {
81
78
  do: () => ({}),
82
79
  }).hook("beforeHandle", (ctx) => {
83
80
  seen.orders = ctx.decorations.flag;
@@ -85,9 +82,7 @@ describe("plugin decorate — types and runtime", () => {
85
82
  );
86
83
  on(
87
84
  http.get("/payments"),
88
- flow({
89
- name: "payments.list",
90
- unit: "payments",
85
+ flow("payments.list", {
91
86
  do: () => ({}),
92
87
  }).hook("beforeHandle", (ctx) => {
93
88
  seen.payments = ctx.decorations.flag;
@@ -20,18 +20,14 @@ describe("plugin scoping — attachment point is the scope", () => {
20
20
  test("unit-scoped plugin hook does not fire for another unit's flows", async () => {
21
21
  const fired: string[] = [];
22
22
 
23
- const ordersFlow = flow({
24
- name: "orders.create",
25
- unit: "orders",
23
+ const ordersFlow = flow("orders.create", {
26
24
  do: () => {
27
25
  fired.push("orders:handler");
28
26
  return { ok: true };
29
27
  },
30
28
  });
31
29
 
32
- const paymentsFlow = flow({
33
- name: "payments.charge",
34
- unit: "payments",
30
+ const paymentsFlow = flow("payments.charge", {
35
31
  do: () => {
36
32
  fired.push("payments:handler");
37
33
  return { ok: true };
@@ -62,9 +58,7 @@ describe("plugin scoping — attachment point is the scope", () => {
62
58
 
63
59
  on(
64
60
  http.get("/a"),
65
- flow({
66
- name: "a",
67
- unit: "alpha",
61
+ flow("alpha.a", {
68
62
  do: () => {
69
63
  fired.push("a");
70
64
  return {};
@@ -73,9 +67,7 @@ describe("plugin scoping — attachment point is the scope", () => {
73
67
  );
74
68
  on(
75
69
  http.get("/b"),
76
- flow({
77
- name: "b",
78
- unit: "beta",
70
+ flow("beta.b", {
79
71
  do: () => {
80
72
  fired.push("b");
81
73
  return {};
@@ -98,8 +90,7 @@ describe("plugin scoping — attachment point is the scope", () => {
98
90
  test("flow-scoped plugin hook fires only for that flow", async () => {
99
91
  const fired: string[] = [];
100
92
 
101
- const marked = flow({
102
- name: "marked",
93
+ const marked = flow("marked", {
103
94
  do: () => {
104
95
  fired.push("marked");
105
96
  return {};
@@ -110,8 +101,7 @@ describe("plugin scoping — attachment point is the scope", () => {
110
101
  }),
111
102
  );
112
103
 
113
- const plain = flow({
114
- name: "plain",
104
+ const plain = flow("plain", {
115
105
  do: () => {
116
106
  fired.push("plain");
117
107
  return {};
@@ -32,7 +32,7 @@ describe("plugin element contributions", () => {
32
32
  .table("otp_codes")
33
33
  .needs("store.sql");
34
34
 
35
- on(http.get("/ok").gate(gate.public), flow({ name: "ok", do: () => ({ ok: true }) }));
35
+ on(http.get("/ok").gate(gate.public), flow("ok", { do: () => ({ ok: true }) }));
36
36
 
37
37
  const app = oke({
38
38
  name: "plugin-elements",
@@ -75,7 +75,7 @@ describe("plugin .needs() resolution", () => {
75
75
  test("boot fails when plugin needs missing peer plugin", async () => {
76
76
  resetBindings();
77
77
  resetFlowSeq();
78
- on(http.get("/x").gate(gate.public), flow({ name: "x", do: () => ({ ok: true }) }));
78
+ on(http.get("/x").gate(gate.public), flow("x", { do: () => ({ ok: true }) }));
79
79
  const dependent = plugin("needs-auth", { version: "0.0.1" }).needs("auth");
80
80
  const app = oke({ name: "needs-boot", gate: { policies: [gate.public] } }).plug(dependent);
81
81
  await expect(app.boot({ env: "test" })).rejects.toThrow(PluginNeedsError);
@@ -33,8 +33,7 @@ describe("GET /_/ready", () => {
33
33
  test("orphan_scan then ready when durable journal is bound", async () => {
34
34
  const journalStore = createMemoryJournalStore();
35
35
  expect(hasJournalLease(journalStore)).toBe(true);
36
- const charge = flow({
37
- name: "charge",
36
+ const charge = flow("charge", {
38
37
  durable: true,
39
38
  do: () => ({ ok: true }),
40
39
  });
@@ -21,11 +21,11 @@ afterEach(() => {
21
21
 
22
22
  describe("registry isolation", () => {
23
23
  test("consume (default): App B does not serve App A routes", async () => {
24
- on(http.get("/a"), flow({ name: "app-a.ping", do: () => "a" }));
24
+ on(http.get("/a"), flow("app-a.ping", { do: () => "a" }));
25
25
  const appA = oke({ autoBoot: false, name: "app-a" });
26
26
 
27
27
  // No resetBindings() between the apps — the pre-fix leak scenario.
28
- on(http.get("/b"), flow({ name: "app-b.ping", do: () => "b" }));
28
+ on(http.get("/b"), flow("app-b.ping", { do: () => "b" }));
29
29
  const appB = oke({ autoBoot: false, name: "app-b" });
30
30
 
31
31
  // Pre-fix this was 200: App B adopted App A's leftover binding.
@@ -45,7 +45,7 @@ describe("registry isolation", () => {
45
45
  });
46
46
 
47
47
  test("keep: today's behavior — registry survives construction", async () => {
48
- on(http.get("/a"), flow({ name: "kept.ping", do: () => "a" }));
48
+ on(http.get("/a"), flow("kept.ping", { do: () => "a" }));
49
49
  oke({ autoBoot: false, name: "app-a", registry: "keep" });
50
50
  expect(listBindings()).toHaveLength(1);
51
51
 
@@ -54,11 +54,11 @@ describe("registry isolation", () => {
54
54
  });
55
55
 
56
56
  test("ignore: only options.bindings are served", async () => {
57
- on(http.get("/stray"), flow({ name: "stray.ping", do: () => "stray" }));
57
+ on(http.get("/stray"), flow("stray.ping", { do: () => "stray" }));
58
58
 
59
59
  const onlyB: Binding = {
60
60
  trigger: http.get("/b"),
61
- flow: flow({ name: "only-b.ping", do: () => "b" }),
61
+ flow: flow("only-b.ping", { do: () => "b" }),
62
62
  };
63
63
  const app = oke({ autoBoot: false, name: "app-b", registry: "ignore", bindings: [onlyB] });
64
64
 
@@ -323,7 +323,7 @@ export function table(name: string, store?: string): TableHandle {
323
323
 
324
324
  /**
325
325
  * Explicit internal / call-only trigger.
326
- * Prefer an untriggered `flow({…})` when no trigger value is needed.
326
+ * Prefer an untriggered `flow(name, {…})` when no trigger value is needed.
327
327
  */
328
328
  export const internal: InternalTrigger = { kind: "internal" };
329
329
 
@@ -30,9 +30,7 @@ export interface AnonymousPluginOptions extends AuthMethodOptions {
30
30
  export function anonymous(opts: AnonymousPluginOptions = {}): PluginDef {
31
31
  const runtime = createMethodRuntime(opts);
32
32
 
33
- const signIn = flow({
34
- name: "auth.signInAnonymous",
35
- unit: "auth",
33
+ const signIn = flow("auth.signInAnonymous", {
36
34
  plane: "user",
37
35
  out: SessionTokensOut,
38
36
  errors: { AuthFailed, AuthRateLimited },
@@ -37,7 +37,7 @@ describe("acceptsGzip", () => {
37
37
 
38
38
  describe("compression plugin", () => {
39
39
  test("gzips a large JSON body when the client accepts gzip", async () => {
40
- on(http.get("/big"), flow({ name: "big.get", do: bigPayload }));
40
+ on(http.get("/big"), flow("big.get", { do: bigPayload }));
41
41
  const app = oke({ autoBoot: false, name: "zip" }).plug(compression());
42
42
 
43
43
  const res = await app.fetch(
@@ -58,7 +58,7 @@ describe("compression plugin", () => {
58
58
  });
59
59
 
60
60
  test("passes through untouched without Accept-Encoding: gzip", async () => {
61
- on(http.get("/big"), flow({ name: "big.get", do: bigPayload }));
61
+ on(http.get("/big"), flow("big.get", { do: bigPayload }));
62
62
  const app = oke({ autoBoot: false, name: "zip-plain" }).plug(compression());
63
63
 
64
64
  const res = await app.fetch(new Request("http://localhost/big"));
@@ -69,7 +69,7 @@ describe("compression plugin", () => {
69
69
  });
70
70
 
71
71
  test("skips bodies under minSize", async () => {
72
- on(http.get("/small"), flow({ name: "small.get", do: () => ({ ok: true }) }));
72
+ on(http.get("/small"), flow("small.get", { do: () => ({ ok: true }) }));
73
73
  const app = oke({ autoBoot: false, name: "zip-small" }).plug(compression());
74
74
 
75
75
  const res = await app.fetch(
@@ -80,7 +80,7 @@ describe("compression plugin", () => {
80
80
  });
81
81
 
82
82
  test("minSize: 0 compresses even tiny bodies", async () => {
83
- on(http.get("/small"), flow({ name: "small.get", do: () => ({ ok: true }) }));
83
+ on(http.get("/small"), flow("small.get", { do: () => ({ ok: true }) }));
84
84
  const app = oke({ autoBoot: false, name: "zip-zero" }).plug(compression({ minSize: 0 }));
85
85
 
86
86
  const res = await app.fetch(
@@ -93,8 +93,7 @@ describe("compression plugin", () => {
93
93
  test("skips non-matching content types and no-transform responses", async () => {
94
94
  on(
95
95
  http.get("/img"),
96
- flow({
97
- name: "img.get",
96
+ flow("img.get", {
98
97
  do: () =>
99
98
  new Response(new Uint8Array(4096), {
100
99
  headers: { "content-type": "image/png" },
@@ -103,8 +102,7 @@ describe("compression plugin", () => {
103
102
  );
104
103
  on(
105
104
  http.get("/nt"),
106
- flow({
107
- name: "nt.get",
105
+ flow("nt.get", {
108
106
  do: () =>
109
107
  Response.json(
110
108
  { data: bigPayload(), error: null },
@@ -155,7 +155,7 @@ describe("configSource — plugin wiring", () => {
155
155
  kv: cfg,
156
156
  });
157
157
 
158
- on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
158
+ on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
159
159
  const app = oke({ autoBoot: false, name: "cfg-live" }).plug(maintenanceMode(source));
160
160
 
161
161
  expect((await app.fetch(new Request("http://localhost/x"))).status).toBe(200);
@@ -116,8 +116,7 @@ export function configSource<T extends object>(options: ConfigSourceOptions<T>):
116
116
  }
117
117
  if (options.db !== undefined) reads.push(options.db.store.ref);
118
118
 
119
- return flow({
120
- name: `${options.plugin}.config-sync`,
119
+ return flow(`${options.plugin}.config-sync`, {
121
120
  effects: { reads, writes },
122
121
  do: async (_input, fx) => {
123
122
  if (options.kv !== undefined) {
@@ -28,7 +28,7 @@ function preflight(path = "/x", headers: Record<string, string> = {}): Request {
28
28
 
29
29
  describe("cors plugin — preflight", () => {
30
30
  test("answers preflight for a path bound to another method (edge)", async () => {
31
- on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
31
+ on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
32
32
  const app = oke({ autoBoot: false, name: "cors-pre" }).plug(
33
33
  cors({ origin: "https://app.example.com" }),
34
34
  );
@@ -42,7 +42,7 @@ describe("cors plugin — preflight", () => {
42
42
  });
43
43
 
44
44
  test("denied origins get a 204 without CORS headers — the browser blocks quietly", async () => {
45
- on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
45
+ on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
46
46
  const app = oke({ autoBoot: false, name: "cors-deny" }).plug(
47
47
  cors({ origin: "https://other.example.com" }),
48
48
  );
@@ -54,7 +54,7 @@ describe("cors plugin — preflight", () => {
54
54
  });
55
55
 
56
56
  test("no origin configured answers nothing — cross-origin closed by default", async () => {
57
- on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
57
+ on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
58
58
  const app = oke({ autoBoot: false, name: "cors-closed" }).plug(cors());
59
59
 
60
60
  const res = await app.fetch(preflight());
@@ -62,7 +62,7 @@ describe("cors plugin — preflight", () => {
62
62
  });
63
63
 
64
64
  test("a plain unmatched OPTIONS without preflight headers still 404s", async () => {
65
- on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
65
+ on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
66
66
  const app = oke({ autoBoot: false, name: "cors-404" }).plug(cors({ origin: "*" }));
67
67
 
68
68
  const res = await app.fetch(new Request("http://localhost/x", { method: "OPTIONS" }));
@@ -70,7 +70,7 @@ describe("cors plugin — preflight", () => {
70
70
  });
71
71
 
72
72
  test("reflects request headers by default, honors configured lists and maxAge", async () => {
73
- on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
73
+ on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
74
74
  const app = oke({ autoBoot: false, name: "cors-hdr" }).plug(
75
75
  cors({ origin: "*", maxAge: 600, allowedHeaders: ["x-custom"] }),
76
76
  );
@@ -91,7 +91,7 @@ describe("cors plugin — preflight", () => {
91
91
  });
92
92
 
93
93
  test("allowlist + credentials reflects only listed origins", async () => {
94
- on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
94
+ on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
95
95
  const app = oke({ autoBoot: false, name: "cors-cred" }).plug(
96
96
  cors({ origin: ["https://app.example.com", "https://admin.example.com"], credentials: true }),
97
97
  );
@@ -109,7 +109,7 @@ describe("cors plugin — preflight", () => {
109
109
 
110
110
  describe("cors plugin — actual requests", () => {
111
111
  test("allowed origins get allow-origin (+ exposed headers) on matched responses", async () => {
112
- on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
112
+ on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
113
113
  const app = oke({ autoBoot: false, name: "cors-actual" }).plug(
114
114
  cors({ origin: ["https://app.example.com"], exposedHeaders: ["x-total"] }),
115
115
  );
@@ -124,7 +124,7 @@ describe("cors plugin — actual requests", () => {
124
124
  });
125
125
 
126
126
  test("allowlist + credentials on matched responses reflects only listed origins", async () => {
127
- on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
127
+ on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
128
128
  const app = oke({ autoBoot: false, name: "cors-actual-cred" }).plug(
129
129
  cors({ origin: ["https://app.example.com"], credentials: true }),
130
130
  );
@@ -145,7 +145,7 @@ describe("cors plugin — actual requests", () => {
145
145
  });
146
146
 
147
147
  test("denied origins get no CORS headers on matched responses", async () => {
148
- on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
148
+ on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
149
149
  const app = oke({ autoBoot: false, name: "cors-actual-deny" }).plug(
150
150
  cors({ origin: "https://ok.example.com" }),
151
151
  );
@@ -159,7 +159,7 @@ describe("cors plugin — actual requests", () => {
159
159
  });
160
160
 
161
161
  test("requests without an Origin header are untouched", async () => {
162
- on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
162
+ on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
163
163
  const app = oke({ autoBoot: false, name: "cors-no-origin" }).plug(cors({ origin: "*" }));
164
164
 
165
165
  const res = await app.fetch(new Request("http://localhost/x"));
@@ -20,8 +20,8 @@ function mutating(headers: Record<string, string> = {}): Request {
20
20
  }
21
21
 
22
22
  function appWith(options: Parameters<typeof csrf>[0]) {
23
- on(http.post("/transfer"), flow({ name: "transfer.create", do: () => ({ moved: true }) }));
24
- on(http.get("/balance"), flow({ name: "balance.get", do: () => ({ balance: 5 }) }));
23
+ on(http.post("/transfer"), flow("transfer.create", { do: () => ({ moved: true }) }));
24
+ on(http.get("/balance"), flow("balance.get", { do: () => ({ balance: 5 }) }));
25
25
  return oke({ autoBoot: false, name: `csrf-${crypto.randomUUID()}` }).plug(csrf(options));
26
26
  }
27
27
 
@@ -18,7 +18,7 @@ beforeEach(() => {
18
18
 
19
19
  describe("headers plugin", () => {
20
20
  test("sets the default trio on a successful response", async () => {
21
- on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
21
+ on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
22
22
  const app = oke({ autoBoot: false, name: "sec" }).plug(headers());
23
23
 
24
24
  const res = await app.fetch(new Request("http://localhost/x"));
@@ -31,7 +31,7 @@ describe("headers plugin", () => {
31
31
  });
32
32
 
33
33
  test("helmet-parity defaults: agent cluster, dns prefetch, download, cross-domain, xss filter", async () => {
34
- on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
34
+ on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
35
35
  const app = oke({ autoBoot: false, name: "sec-parity" }).plug(headers());
36
36
 
37
37
  const res = await app.fetch(new Request("http://localhost/x"));
@@ -44,7 +44,7 @@ describe("headers plugin", () => {
44
44
  });
45
45
 
46
46
  test("each parity default can be switched off", async () => {
47
- on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
47
+ on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
48
48
  const app = oke({ autoBoot: false, name: "sec-off" }).plug(
49
49
  headers({
50
50
  originAgentCluster: false,
@@ -64,7 +64,7 @@ describe("headers plugin", () => {
64
64
  });
65
65
 
66
66
  test("dnsPrefetchControl allow, custom cross-domain policy, COEP stamps when configured", async () => {
67
- on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
67
+ on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
68
68
  const app = oke({ autoBoot: false, name: "sec-tuned" }).plug(
69
69
  headers({
70
70
  dnsPrefetchControl: { allow: true },
@@ -84,8 +84,7 @@ describe("headers plugin", () => {
84
84
  const poweredByApp = (options: Parameters<typeof headers>[0], name: string) => {
85
85
  on(
86
86
  http.get("/x"),
87
- flow({
88
- name: "x.get",
87
+ flow("x.get", {
89
88
  do: () =>
90
89
  new Response("{}", {
91
90
  headers: { "content-type": "application/json", "x-powered-by": "Express" },
@@ -111,7 +110,7 @@ describe("headers plugin", () => {
111
110
  });
112
111
 
113
112
  test("structured CSP merges over helmet's defaults, camelCase keys, report-only mode", async () => {
114
- on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
113
+ on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
115
114
  const app = oke({ autoBoot: false, name: "sec-csp-builder" }).plug(
116
115
  headers({
117
116
  contentSecurityPolicy: {
@@ -131,7 +130,7 @@ describe("headers plugin", () => {
131
130
  });
132
131
 
133
132
  test("structured CSP with useDefaults: false emits exactly the given directives", async () => {
134
- on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
133
+ on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
135
134
  const app = oke({ autoBoot: false, name: "sec-csp-bare" }).plug(
136
135
  headers({
137
136
  contentSecurityPolicy: { useDefaults: false, directives: { "default-src": ["'none'"] } },
@@ -151,7 +150,7 @@ describe("headers plugin", () => {
151
150
  test("headers land on failures too (onResponse runs after onError)", async () => {
152
151
  on(
153
152
  http.get("/deny"),
154
- flow({ name: "x.deny", do: () => ({ ok: true }) }).hook("beforeHandle", () =>
153
+ flow("x.deny", { do: () => ({ ok: true }) }).hook("beforeHandle", () =>
155
154
  fail("Forbidden", {}),
156
155
  ),
157
156
  );
@@ -164,7 +163,7 @@ describe("headers plugin", () => {
164
163
  });
165
164
 
166
165
  test("adds CSP only when configured", async () => {
167
- on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
166
+ on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
168
167
  const app = oke({ autoBoot: false, name: "sec-csp" }).plug(
169
168
  headers({ contentSecurityPolicy: "default-src 'self'" }),
170
169
  );
@@ -175,7 +174,7 @@ describe("headers plugin", () => {
175
174
  });
176
175
 
177
176
  test("HSTS is off by default", async () => {
178
- on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
177
+ on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
179
178
  const app = oke({ autoBoot: false, name: "sec-hsts-off" }).plug(headers());
180
179
 
181
180
  const res = await app.fetch(new Request("http://localhost/x"));
@@ -184,7 +183,7 @@ describe("headers plugin", () => {
184
183
  });
185
184
 
186
185
  test("hsts: true stamps a one-year max-age", async () => {
187
- on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
186
+ on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
188
187
  const app = oke({ autoBoot: false, name: "sec-hsts-on" }).plug(headers({ hsts: true }));
189
188
 
190
189
  const res = await app.fetch(new Request("http://localhost/x"));
@@ -193,7 +192,7 @@ describe("headers plugin", () => {
193
192
  });
194
193
 
195
194
  test("hsts object tunes max-age, subdomains, preload", async () => {
196
- on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
195
+ on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
197
196
  const app = oke({ autoBoot: false, name: "sec-hsts-tuned" }).plug(
198
197
  headers({ hsts: { maxAge: 63072000, includeSubDomains: true, preload: true } }),
199
198
  );
@@ -206,7 +205,7 @@ describe("headers plugin", () => {
206
205
  });
207
206
 
208
207
  test("permissions-policy and cross-origin policies stamp when configured", async () => {
209
- on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
208
+ on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
210
209
  const app = oke({ autoBoot: false, name: "sec-extra" }).plug(
211
210
  headers({
212
211
  permissionsPolicy: "camera=(), microphone=()",
@@ -223,7 +222,7 @@ describe("headers plugin", () => {
223
222
  });
224
223
 
225
224
  test("an explicit app-set value wins by default", async () => {
226
- on(http.get("/x"), flow({ name: "x.get", do: () => ({ ok: true }) }));
225
+ on(http.get("/x"), flow("x.get", { do: () => ({ ok: true }) }));
227
226
  const app = oke({ autoBoot: false, name: "sec-keep" });
228
227
  app.hook("onResponse", (ctx) => {
229
228
  if (!ctx.response) return;