okengine 0.13.0 → 0.15.2

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 (277) hide show
  1. package/manifest.v1.schema.json +7 -1
  2. package/package.json +4 -5
  3. package/site/content/docs/ai/mcp.mdx +27 -2
  4. package/site/content/docs/elements/ai.mdx +59 -11
  5. package/site/content/docs/elements/channel.mdx +1 -0
  6. package/site/content/docs/elements/clock.mdx +30 -12
  7. package/site/content/docs/elements/flow.mdx +9 -3
  8. package/site/content/docs/elements/gate.mdx +25 -22
  9. package/site/content/docs/elements/signal.mdx +71 -25
  10. package/site/content/docs/elements/store.mdx +87 -28
  11. package/site/content/docs/elements/vault.mdx +1 -0
  12. package/site/content/docs/get-started/basic-usage.mdx +22 -13
  13. package/site/content/docs/get-started/index.mdx +5 -0
  14. package/site/content/docs/get-started/installation.mdx +2 -2
  15. package/site/content/docs/get-started/introduction.mdx +11 -3
  16. package/site/content/docs/get-started/meta.json +1 -1
  17. package/site/content/docs/get-started/project-structure.mdx +932 -0
  18. package/site/content/docs/reference/cli.md +13 -10
  19. package/site/content/docs/reference/client.mdx +25 -21
  20. package/site/content/docs/reference/fx.mdx +27 -22
  21. package/site/content/docs/reference/security.md +1 -1
  22. package/src/auth/bindings.ts +14 -5
  23. package/src/auth/cross-plane.ts +10 -3
  24. package/src/cli/ai-setup/ai-setup.test.ts +62 -0
  25. package/src/cli/ai-setup/apply.ts +32 -1
  26. package/src/cli/ai-setup/recommend.test.ts +25 -0
  27. package/src/cli/ai-setup/recommend.ts +8 -3
  28. package/src/cli/attach-host-console.test.ts +3 -2
  29. package/src/cli/attach-host-console.ts +3 -0
  30. package/src/cli/build.test.ts +4 -2
  31. package/src/cli/db-seed.ts +48 -15
  32. package/src/cli/db.test.ts +104 -0
  33. package/src/cli/db.ts +122 -10
  34. package/src/cli/dev-controls.test.ts +20 -0
  35. package/src/cli/dev-controls.ts +13 -0
  36. package/src/cli/dev.test.ts +15 -4
  37. package/src/cli/dev.ts +102 -29
  38. package/src/cli/ensure-drizzle-config.ts +2 -1
  39. package/src/cli/tui/DevLive.tsx +12 -2
  40. package/src/cli/tui/keys.test.ts +2 -1
  41. package/src/cli/tui/keys.ts +3 -2
  42. package/src/compiler/aot.test.ts +1 -1
  43. package/src/compiler/effects-infer.ts +58 -3
  44. package/src/compiler/extract.test.ts +458 -29
  45. package/src/compiler/extract.ts +387 -69
  46. package/src/compiler/flow-path.test.ts +96 -0
  47. package/src/compiler/flow-path.ts +173 -0
  48. package/src/compiler/generate-adopt.test.ts +112 -3
  49. package/src/compiler/generate-adopt.ts +280 -26
  50. package/src/compiler/response.ts +45 -1
  51. package/src/compiler/schema-from-ast.ts +609 -0
  52. package/src/console/server/ai.ts +5 -2
  53. package/src/console/server/bind.ts +2 -2
  54. package/src/console/server/console.test.ts +3 -0
  55. package/src/console/server/flows.ts +121 -40
  56. package/src/console/server/invoke-user-flow.test.ts +3 -1
  57. package/src/console/server/invoke-user-flow.ts +17 -2
  58. package/src/console/server/iso-at.test.ts +9 -0
  59. package/src/console/server/iso-at.ts +11 -0
  60. package/src/console/server/security-headers.ts +6 -3
  61. package/src/console/server/serve.ts +17 -2
  62. package/src/console/server/spa-proxy.test.ts +1 -0
  63. package/src/console/server/sql-catalog.ts +147 -7
  64. package/src/console/server/sql-rls.test.ts +16 -0
  65. package/src/console/server/state.ts +8 -0
  66. package/src/console/server/store.test.ts +7 -4
  67. package/src/console/server/store.ts +142 -58
  68. package/src/console/ui-next/dist/assets/agent-disclosure-C0X1fbWF.js +1 -0
  69. package/src/console/ui-next/dist/assets/cache-glyph-F1FI122b.js +1 -0
  70. package/src/console/ui-next/dist/assets/{call-pii-button-bqkxMrJH.js → call-pii-button-DEDCl_j3.js} +1 -1
  71. package/src/console/ui-next/dist/assets/collapsible-LPqGvfoz.js +1 -0
  72. package/src/console/ui-next/dist/assets/{confirm-sheet-DDCRmG62.js → confirm-sheet-3ptDzXbz.js} +1 -1
  73. package/src/console/ui-next/dist/assets/{duration-tone-oiRxPVsZ.js → duration-tone-sC3lGABz.js} +1 -1
  74. package/src/console/ui-next/dist/assets/{explorer-empty-HjTnVQoR.js → explorer-empty-BvYrygyO.js} +1 -1
  75. package/src/console/ui-next/dist/assets/flows-page-Dluo1Bd7.js +1 -0
  76. package/src/console/ui-next/dist/assets/{highlighted-json-CvDPvveV.js → highlighted-json-CS_O8L-r.js} +1 -1
  77. package/src/console/ui-next/dist/assets/http-method--sWDdXSB.js +1 -0
  78. package/src/console/ui-next/dist/assets/index-Ca3HZMVq.js +66 -0
  79. package/src/console/ui-next/dist/assets/index-UtTDRQpS.css +2 -0
  80. package/src/console/ui-next/dist/assets/{link-DF7SZ9Ek.js → link-COeyggt-.js} +1 -1
  81. package/src/console/ui-next/dist/assets/observability-page-HK9-BO8z.js +4 -0
  82. package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +1 -0
  83. package/src/console/ui-next/dist/assets/replica-lag-DBIFFf7d.js +18 -0
  84. package/src/console/ui-next/dist/assets/sheet-form-Dnwa5oOR.js +1 -0
  85. package/src/console/ui-next/dist/assets/shortcut-keys-CoqwEIi0.js +1 -0
  86. package/src/console/ui-next/dist/assets/skeleton-BHcfDYcb.js +1 -0
  87. package/src/console/ui-next/dist/assets/store-page-BSohH3wM.js +41 -0
  88. package/src/console/ui-next/dist/assets/trace-detail-sheet-CFIGRnnA.js +2 -0
  89. package/src/console/ui-next/dist/assets/tree-expand-toggle-BgLl34w7.js +54 -0
  90. package/src/console/ui-next/dist/assets/units-page-B0cFE76A.js +1 -0
  91. package/src/console/ui-next/dist/assets/{use-vault-list-Cl79j_ku.js → use-vault-list-BLbZhzyF.js} +1 -1
  92. package/src/console/ui-next/dist/assets/vault-page-B1SbYe10.js +2 -0
  93. package/src/console/ui-next/dist/index.html +6 -6
  94. package/src/console/ui-next/seed-invoke-host.ts +2 -0
  95. package/src/console/ui-next/src/client.ts +35 -7
  96. package/src/console/ui-next/src/components/motion/table/index.tsx +16 -0
  97. package/src/console/ui-next/src/components/motion/table/types.ts +4 -0
  98. package/src/console/ui-next/src/components/ui/kbd.tsx +2 -2
  99. package/src/console/ui-next/src/features/flows/graph/build-flow-graph.test.ts +18 -0
  100. package/src/console/ui-next/src/features/flows/graph/build-flow-graph.ts +14 -1
  101. package/src/console/ui-next/src/features/flows/graph/neighborhood.test.ts +17 -0
  102. package/src/console/ui-next/src/features/flows/graph/neighborhood.ts +16 -3
  103. package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +3 -1
  104. package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +24 -0
  105. package/src/console/ui-next/src/features/flows/traces/http-method.ts +19 -0
  106. package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +9 -3
  107. package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +29 -2
  108. package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
  109. package/src/console/ui-next/src/features/observability/lib/ask-count.test.ts +25 -0
  110. package/src/console/ui-next/src/features/observability/lib/ask-count.ts +4 -1
  111. package/src/console/ui-next/src/features/store/detail/browse-section.tsx +45 -1
  112. package/src/console/ui-next/src/features/store/detail/resource-panel.tsx +78 -16
  113. package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +155 -14
  114. package/src/console/ui-next/src/features/store/explorer/store-tree.tsx +33 -133
  115. package/src/console/ui-next/src/features/store/files/file-preview.tsx +4 -1
  116. package/src/console/ui-next/src/features/store/grid/rls-policy-sheet.tsx +326 -165
  117. package/src/console/ui-next/src/features/store/grid/sql-insert-sheet.tsx +6 -0
  118. package/src/console/ui-next/src/features/store/grid/store-data-grid.tsx +50 -3
  119. package/src/console/ui-next/src/features/store/lib/files-meta.test.ts +3 -0
  120. package/src/console/ui-next/src/features/store/lib/files-meta.ts +12 -0
  121. package/src/console/ui-next/src/features/store/lib/query-defaults.test.ts +25 -0
  122. package/src/console/ui-next/src/features/store/lib/query-defaults.ts +85 -3
  123. package/src/console/ui-next/src/features/store/lib/query-gate.ts +30 -4
  124. package/src/console/ui-next/src/features/store/lib/rls-policy.test.ts +139 -0
  125. package/src/console/ui-next/src/features/store/lib/rls-policy.ts +223 -21
  126. package/src/console/ui-next/src/features/store/lib/schema-graph.ts +4 -0
  127. package/src/console/ui-next/src/features/store/lib/sql-catalog.test.ts +24 -0
  128. package/src/console/ui-next/src/features/store/lib/sql-catalog.ts +19 -0
  129. package/src/console/ui-next/src/features/store/lib/store-tree.test.ts +6 -0
  130. package/src/console/ui-next/src/features/store/lib/store-tree.ts +13 -0
  131. package/src/console/ui-next/src/features/store/query/query-console.tsx +48 -14
  132. package/src/console/ui-next/src/features/store/query/query-gate-menu.tsx +19 -20
  133. package/src/console/ui-next/src/features/store/query/query-gate-parts.tsx +2 -1
  134. package/src/console/ui-next/src/features/store/query/query-results.tsx +110 -83
  135. package/src/console/ui-next/src/features/store/schema/schema-table-node.tsx +17 -0
  136. package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +112 -72
  137. package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +12 -4
  138. package/src/console/ui-next/src/features/units/lib/contract-input.test.ts +206 -1
  139. package/src/console/ui-next/src/features/units/lib/contract-input.ts +55 -0
  140. package/src/console/ui-next/src/features/vault/lib/types.ts +1 -1
  141. package/src/docker/compose-health.test.ts +27 -0
  142. package/src/docker/compose-health.ts +19 -3
  143. package/src/docker/docker.test.ts +4 -1
  144. package/src/docker/dockerfile.ts +1 -1
  145. package/src/docker/recipes/pgdog.ts +2 -0
  146. package/src/drivers/ai-anthropic.ts +5 -0
  147. package/src/drivers/ai-ollama.ts +49 -30
  148. package/src/drivers/ai-openai-compatible.ts +57 -46
  149. package/src/drivers/ai-providers.test.ts +3 -0
  150. package/src/drivers/bun-native-completeness.test.ts +7 -9
  151. package/src/drivers/clock-postgres.ts +13 -9
  152. package/src/drivers/instances-postgres.ts +4 -6
  153. package/src/drivers/journal-postgres.ts +13 -9
  154. package/src/drivers/pg-rls.test.ts +87 -0
  155. package/src/drivers/pg-rls.ts +233 -7
  156. package/src/drivers/pglite.ts +26 -3
  157. package/src/drivers/postgres.test.ts +23 -0
  158. package/src/drivers/postgres.ts +133 -9
  159. package/src/drivers/redis.ts +11 -4
  160. package/src/drivers/signal-redis.ts +24 -14
  161. package/src/drivers/signal-types.ts +2 -1
  162. package/src/drivers/types.ts +10 -0
  163. package/src/elements/ai/declare.ts +109 -0
  164. package/src/elements/ai/errors.test.ts +5 -1
  165. package/src/elements/ai/errors.ts +30 -2
  166. package/src/elements/ai/eval.ts +4 -6
  167. package/src/elements/ai/mcp-client.test.ts +206 -0
  168. package/src/elements/ai/mcp-client.ts +362 -0
  169. package/src/elements/ai/mcp-http.ts +159 -0
  170. package/src/elements/ai/mcp-mock.ts +134 -0
  171. package/src/elements/ai/mcp-protocol.ts +234 -0
  172. package/src/elements/ai/mcp-stdio.test.ts +50 -0
  173. package/src/elements/ai/mcp-stdio.ts +212 -0
  174. package/src/elements/ai/mcp-transport.ts +70 -0
  175. package/src/elements/ai/runtime.ts +159 -29
  176. package/src/elements/ai.test.ts +139 -0
  177. package/src/elements/ai.ts +16 -0
  178. package/src/elements/clock/declare.ts +26 -1
  179. package/src/elements/clock/health.test.ts +43 -0
  180. package/src/elements/clock/runtime.ts +55 -2
  181. package/src/elements/clock/schedule.ts +71 -142
  182. package/src/elements/clock.test.ts +9 -40
  183. package/src/elements/clock.ts +6 -1
  184. package/src/elements/gate/boot.ts +5 -5
  185. package/src/elements/gate/declare.ts +36 -4
  186. package/src/elements/gate/flatten.ts +1 -1
  187. package/src/elements/gate/runtime.ts +1 -1
  188. package/src/elements/index.ts +2 -0
  189. package/src/elements/signal/declare.ts +2 -1
  190. package/src/elements/signal/runtime.ts +16 -1
  191. package/src/elements/signal.ts +1 -0
  192. package/src/elements/store/cache.test.ts +2 -0
  193. package/src/elements/store/cache.ts +3 -3
  194. package/src/elements/store/declare.ts +9 -6
  195. package/src/elements/store/emit-drizzle.ts +44 -8
  196. package/src/elements/store/resource.test.ts +1 -1
  197. package/src/elements/store/rls-identity.test.ts +95 -0
  198. package/src/elements/store/rls-identity.ts +116 -0
  199. package/src/elements/store/runtime.ts +4 -0
  200. package/src/elements/store/schema-decl.test.ts +37 -0
  201. package/src/elements/store/schema-decl.ts +161 -0
  202. package/src/elements/store/seed.ts +1 -1
  203. package/src/elements/store/sql-condition.test.ts +12 -0
  204. package/src/elements/store/sql-condition.ts +26 -2
  205. package/src/elements/store/sql-rls-isolation.test.ts +165 -0
  206. package/src/elements/store/sql-rls-stamp.test.ts +117 -0
  207. package/src/elements/store/sql-session.ts +81 -3
  208. package/src/elements/store/upsert-app.test.ts +1 -1
  209. package/src/elements/store.ts +14 -0
  210. package/src/elements/vault/builtin-adapter.ts +17 -0
  211. package/src/full.ts +2 -0
  212. package/src/http.ts +20 -3
  213. package/src/index.ts +14 -1
  214. package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
  215. package/src/kernel/adopt-routes.ts +30 -2
  216. package/src/kernel/app.ts +227 -77
  217. package/src/kernel/auto-cache.test.ts +8 -8
  218. package/src/kernel/auto-registry.test.ts +54 -9
  219. package/src/kernel/boot-bind/ai.ts +24 -0
  220. package/src/kernel/boot-bind/clock.ts +2 -0
  221. package/src/kernel/boot.test.ts +4 -4
  222. package/src/kernel/boot.ts +5 -3
  223. package/src/kernel/effects-stamping.test.ts +3 -3
  224. package/src/kernel/element-registries.ts +15 -5
  225. package/src/kernel/errors.ts +24 -0
  226. package/src/kernel/flow-units.ts +64 -0
  227. package/src/kernel/flow.ts +29 -17
  228. package/src/kernel/fx-dead-letters.test.ts +77 -0
  229. package/src/kernel/fx.test.ts +22 -0
  230. package/src/kernel/fx.ts +135 -6
  231. package/src/kernel/horizontal-child.ts +4 -4
  232. package/src/kernel/http-path-pending.ts +20 -0
  233. package/src/kernel/http-resource.ts +4 -0
  234. package/src/kernel/http-stream.test.ts +174 -0
  235. package/src/kernel/index.ts +2 -0
  236. package/src/kernel/pipeline.test.ts +2 -2
  237. package/src/kernel/plugin-elements.test.ts +1 -1
  238. package/src/kernel/plugin-needs.test.ts +1 -1
  239. package/src/kernel/ready.test.ts +2 -2
  240. package/src/kernel/resource-mount.test.ts +14 -0
  241. package/src/kernel/run-duration.test.ts +2 -2
  242. package/src/kernel/stamp-http.test.ts +79 -0
  243. package/src/kernel/stamp-http.ts +75 -0
  244. package/src/kernel/triggers.ts +52 -27
  245. package/src/manifest/mcp-ref.ts +88 -0
  246. package/src/manifest/types.ts +41 -2
  247. package/src/manifest/validate.test.ts +20 -0
  248. package/src/mcp/docs-server.ts +1 -1
  249. package/src/mcp/server.ts +1 -1
  250. package/src/plugins/compression.test.ts +21 -0
  251. package/src/plugins/compression.ts +1 -0
  252. package/src/release/measure.ts +2 -2
  253. package/src/runtime/bun.ts +41 -4
  254. package/src/runtime/json-code-block.test.ts +2 -2
  255. package/src/runtime/serve.test.ts +4 -4
  256. package/src/term.test.ts +52 -4
  257. package/src/term.ts +125 -17
  258. package/src/test/reset-element-registries.ts +10 -3
  259. package/src/cli/ask-seed.test.ts +0 -96
  260. package/src/cli/ask-seed.ts +0 -82
  261. package/src/console/ui-next/dist/assets/cache-glyph-CLPBqZeb.js +0 -1
  262. package/src/console/ui-next/dist/assets/collapsible-DYb0xU8C.js +0 -1
  263. package/src/console/ui-next/dist/assets/flows-page-Dg8CTE29.js +0 -1
  264. package/src/console/ui-next/dist/assets/http-method-BJ92Z_ke.js +0 -1
  265. package/src/console/ui-next/dist/assets/index-Bp-R7jtM.js +0 -66
  266. package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
  267. package/src/console/ui-next/dist/assets/observability-page-DAnpEaq1.js +0 -4
  268. package/src/console/ui-next/dist/assets/query-gate-parts-1m8m1iNp.js +0 -2
  269. package/src/console/ui-next/dist/assets/react-B1ML8gxg.js +0 -1
  270. package/src/console/ui-next/dist/assets/replica-lag-C8_BRt2x.js +0 -18
  271. package/src/console/ui-next/dist/assets/sheet-form-D-ata7jy.js +0 -1
  272. package/src/console/ui-next/dist/assets/shortcut-keys-DO4IsVqv.js +0 -1
  273. package/src/console/ui-next/dist/assets/skeleton-CL_X0GCj.js +0 -1
  274. package/src/console/ui-next/dist/assets/store-page-v3LXdYpr.js +0 -45
  275. package/src/console/ui-next/dist/assets/trace-detail-sheet-D16lWQMt.js +0 -2
  276. package/src/console/ui-next/dist/assets/units-page-BtQ0bqMe.js +0 -1
  277. package/src/console/ui-next/dist/assets/vault-page-Ca-MvcmJ.js +0 -2
@@ -11,6 +11,7 @@ import type { ClockDecl } from "./declare.ts";
11
11
  import { detectDstAmbiguity } from "./dst.ts";
12
12
  import { parseDurationMs } from "./duration.ts";
13
13
  import { tryAcquireLease } from "./leader.ts";
14
+ import { nextCronFireAt } from "./schedule.ts";
14
15
  import {
15
16
  createMemoryCronStore,
16
17
  effectiveSchedule,
@@ -98,6 +99,13 @@ export interface ClockRuntime {
98
99
  * @param name - Cron name
99
100
  */
100
101
  dstAmbiguity(name: string): Promise<ReturnType<typeof detectDstAmbiguity>>;
102
+ /**
103
+ * Register in-process {@link Bun.cron} wakes for declared cron expressions.
104
+ * No-op on a frozen / time-travel runtime (tests drive {@link tick}).
105
+ */
106
+ startWakes(): void;
107
+ /** Stop every in-process {@link Bun.cron} wake registered by {@link startWakes}. */
108
+ stopWakes(): void;
101
109
  }
102
110
 
103
111
  /**
@@ -115,6 +123,8 @@ export function createClockRuntime(options: CreateClockRuntimeOptions = {}): Clo
115
123
  const handlers = new Map<string, CronHandler>();
116
124
  /** The 1s boot scheduler must not stack ticks while a handler is still running. */
117
125
  let ticking = false;
126
+ /** In-process {@link Bun.cron} jobs — precise wake; {@link tick} still owns the lease. */
127
+ const wakes: Array<{ stop(): void }> = [];
118
128
  const driverId: ClockRuntime["driverId"] = timeTravel
119
129
  ? "frozen"
120
130
  : store.kind === "file"
@@ -154,11 +164,18 @@ export function createClockRuntime(options: CreateClockRuntimeOptions = {}): Clo
154
164
 
155
165
  // Claim the slot before the handler so a stacked 1s tick cannot re-enter.
156
166
  const ranAt = now();
157
- const interval = fresh.effectiveEvery ? parseDurationMs(fresh.effectiveEvery) : 0;
167
+ const sched = effectiveSchedule(fresh);
168
+ let nextRunAt = fresh.nextRunAt;
169
+ if (sched.every) {
170
+ const interval = parseDurationMs(sched.every);
171
+ if (interval > 0) nextRunAt = ranAt + interval;
172
+ } else if (sched.cron) {
173
+ nextRunAt = nextCronFireAt(sched.cron, ranAt, fresh.timezone) ?? fresh.nextRunAt;
174
+ }
158
175
  await store.put({
159
176
  ...fresh,
160
177
  lastRunAt: ranAt,
161
- nextRunAt: interval > 0 ? ranAt + interval : fresh.nextRunAt,
178
+ nextRunAt,
162
179
  });
163
180
 
164
181
  const handler = handlers.get(name);
@@ -250,6 +267,42 @@ export function createClockRuntime(options: CreateClockRuntimeOptions = {}): Clo
250
267
  if (!row?.effectiveCron) return null;
251
268
  return detectDstAmbiguity(row.effectiveCron, row.timezone, now());
252
269
  },
270
+ startWakes() {
271
+ runtime.stopWakes();
272
+ if (timeTravel) return;
273
+ for (const decl of declarations.values()) {
274
+ if (!decl.cron) continue;
275
+ try {
276
+ const job = (
277
+ Bun.cron as (
278
+ schedule: string,
279
+ handler: () => unknown,
280
+ options?: { readonly tz?: string },
281
+ ) => { stop(): void; unref(): void }
282
+ )(
283
+ decl.cron,
284
+ () => {
285
+ void runtime.tick();
286
+ },
287
+ { tz: decl.timezone },
288
+ );
289
+ job.unref();
290
+ wakes.push(job);
291
+ } catch {
292
+ /* invalid expression — 1s tick remains the fallback */
293
+ }
294
+ }
295
+ },
296
+ stopWakes() {
297
+ for (const job of wakes) {
298
+ try {
299
+ job.stop();
300
+ } catch {
301
+ /* already stopped */
302
+ }
303
+ }
304
+ wakes.length = 0;
305
+ },
253
306
  };
254
307
 
255
308
  return runtime;
@@ -1,13 +1,58 @@
1
1
  /**
2
2
  * Next-occurrence helpers for cron / every schedules (console §9.6 timeline).
3
3
  *
4
- * Supports `every` intervals and simple daily cron (`M H * * *` / `M H * * DOW`).
5
- * Complex cron expressions fall back to a single `nextRunAt` when present.
4
+ * Cron math uses {@link Bun.cron.parse} (5-field + nicknames + `{ tz }`).
5
+ * Interval (`every`) stays on {@link parseDurationMs}.
6
6
  */
7
7
 
8
8
  import { parseDurationMs } from "./duration.ts";
9
9
  import { effectiveSchedule, type CronRow } from "./reconcile.ts";
10
10
 
11
+ /**
12
+ * Bun 1.4 `{ tz }` parse — `@types/bun` 1.3.14 only declares two arguments.
13
+ *
14
+ * @param expression - 5-field expression or nickname
15
+ * @param relativeMs - Exclusive start
16
+ * @param timezone - IANA zone
17
+ */
18
+ function parseCronNext(expression: string, relativeMs: number, timezone: string): Date | null {
19
+ return (
20
+ Bun.cron.parse as (
21
+ expression: string,
22
+ relativeDate?: Date | number,
23
+ options?: { readonly tz?: string },
24
+ ) => Date | null
25
+ )(expression, relativeMs, { tz: timezone });
26
+ }
27
+
28
+ /** Cap walk loops so a `* * * * *` range cannot hang the Console timeline. */
29
+ const MAX_CRON_FIRES = 10_000;
30
+ /** How far back {@link previousOccurrence} searches for a prior cron fire. */
31
+ const PREVIOUS_LOOKBACK_MS = 400 * 86_400_000;
32
+
33
+ /**
34
+ * Next matching UTC epoch-ms after `relativeMs`, or `undefined` when the
35
+ * expression is invalid or has no match within eight years.
36
+ *
37
+ * {@link Bun.cron.parse} is exclusive of `relativeMs`.
38
+ *
39
+ * @param cron - 5-field expression or nickname (`@hourly`)
40
+ * @param relativeMs - Search starts after this instant
41
+ * @param timezone - IANA zone passed as `{ tz }`
42
+ */
43
+ export function nextCronFireAt(
44
+ cron: string,
45
+ relativeMs: number,
46
+ timezone: string,
47
+ ): number | undefined {
48
+ try {
49
+ const next = parseCronNext(cron, relativeMs, timezone);
50
+ return next === null ? undefined : next.getTime();
51
+ } catch {
52
+ return undefined;
53
+ }
54
+ }
55
+
11
56
  /**
12
57
  * Compute fire times in `[from, until)` for a reconciled cron row.
13
58
  *
@@ -24,7 +69,7 @@ export function nextOccurrences(row: CronRow, from: number, until: number): read
24
69
  return nextEveryOccurrences(row, sched.every, from, until);
25
70
  }
26
71
  if (sched.cron) {
27
- return nextCronOccurrences(row, sched.cron, sched.timezone, from, until);
72
+ return nextCronOccurrences(sched.cron, row.timezone, from, until);
28
73
  }
29
74
  return [];
30
75
  }
@@ -41,25 +86,12 @@ export function previousOccurrence(row: CronRow, at: number): number | null {
41
86
  const interval = parseDurationMs(sched.every);
42
87
  if (interval <= 0) return null;
43
88
  if (row.lastRunAt === undefined) return null;
44
- // Floor to the slot boundary at or before `at`.
45
89
  const origin = row.lastRunAt % interval;
46
90
  const slot = at - ((at - origin) % interval);
47
91
  return slot > 0 ? slot : null;
48
92
  }
49
93
  if (sched.cron) {
50
- const parsed = parseSimpleDaily(sched.cron);
51
- if (!parsed) return null;
52
- // Walk back up to 400 days for a prior civil fire.
53
- for (let day = 1; day <= 400; day++) {
54
- const probe = at - day * 86_400_000;
55
- const local = zonedParts(probe, sched.timezone);
56
- if (local.hour === parsed.hour && local.minute === parsed.minute) {
57
- // Align to that local wall time's UTC instant near `probe`.
58
- const fires = civilFiresOnDay(local.date, parsed, sched.timezone);
59
- const prior = fires.filter((t) => t <= at).pop();
60
- if (prior !== undefined) return prior;
61
- }
62
- }
94
+ return previousCronFire(sched.cron, row.timezone, at);
63
95
  }
64
96
  return null;
65
97
  }
@@ -77,11 +109,10 @@ export function countMissedOccurrences(row: CronRow, from: number, until: number
77
109
  if (sched.every) {
78
110
  const interval = parseDurationMs(sched.every);
79
111
  if (interval <= 0) return 0;
80
- return Math.max(0, Math.floor((until - from) / interval) - 0);
112
+ return Math.max(0, Math.floor((until - from) / interval));
81
113
  }
82
114
  if (sched.cron) {
83
- // Count fires in (from, until].
84
- const fires = nextCronOccurrences(row, sched.cron, sched.timezone, from + 1, until + 1);
115
+ const fires = nextCronOccurrences(sched.cron, row.timezone, from + 1, until + 1);
85
116
  return fires.filter((t) => t > from && t <= until).length;
86
117
  }
87
118
  return 0;
@@ -102,145 +133,43 @@ function nextEveryOccurrences(
102
133
  : row.lastRunAt !== undefined
103
134
  ? row.lastRunAt + interval
104
135
  : from;
105
- // If still in the past relative to `from`, advance to first >= from.
106
136
  while (t < from) t += interval;
107
137
  while (t < until) {
108
138
  out.push(t);
109
139
  t += interval;
110
- if (out.length > 10_000) break;
140
+ if (out.length > MAX_CRON_FIRES) break;
111
141
  }
112
142
  return out;
113
143
  }
114
144
 
115
145
  function nextCronOccurrences(
116
- row: CronRow,
117
146
  cron: string,
118
147
  timezone: string,
119
148
  from: number,
120
149
  until: number,
121
150
  ): readonly number[] {
122
- const parsed = parseSimpleDaily(cron);
123
- if (!parsed) {
124
- if (row.nextRunAt !== undefined && row.nextRunAt >= from && row.nextRunAt < until) {
125
- return [row.nextRunAt];
126
- }
127
- return [];
128
- }
129
-
130
151
  const out: number[] = [];
131
- const start = new Date(from);
132
- for (let day = 0; day < 400; day++) {
133
- const probe = Date.UTC(
134
- start.getUTCFullYear(),
135
- start.getUTCMonth(),
136
- start.getUTCDate() + day,
137
- 12,
138
- 0,
139
- 0,
140
- );
141
- if (probe > until + 86_400_000) break;
142
- const local = zonedParts(probe, timezone);
143
- const fires = civilFiresOnDay(local.date, parsed, timezone);
144
- for (const t of fires) {
145
- if (t >= from && t < until) out.push(t);
146
- }
147
- if (out.length > 10_000) break;
152
+ // parse is exclusive of the cursor — step back 1ms so `from` itself can match.
153
+ let cursor = from - 1;
154
+ while (out.length < MAX_CRON_FIRES) {
155
+ const t = nextCronFireAt(cron, cursor, timezone);
156
+ if (t === undefined || t >= until) break;
157
+ if (t >= from) out.push(t);
158
+ cursor = t;
148
159
  }
149
- return out.sort((a, b) => a - b);
150
- }
151
-
152
- /** Parsed simple daily cron. */
153
- interface SimpleDaily {
154
- readonly minute: number;
155
- readonly hour: number;
156
- readonly dow?: number;
160
+ return out;
157
161
  }
158
162
 
159
- function parseSimpleDaily(cron: string): SimpleDaily | null {
160
- const parts = cron.trim().split(/\s+/);
161
- if (parts.length < 5) return null;
162
- const minute = parts[0]!;
163
- const hour = parts[1]!;
164
- const dom = parts[2]!;
165
- const mon = parts[3]!;
166
- const dow = parts[4]!;
167
- if (!/^\d+$/.test(minute) || !/^\d+$/.test(hour)) return null;
168
- if (dom !== "*" || mon !== "*") return null;
169
- const m = Number(minute);
170
- const h = Number(hour);
171
- if (m < 0 || m > 59 || h < 0 || h > 23) return null;
172
- if (dow === "*") return { minute: m, hour: h };
173
- if (/^\d+$/.test(dow)) {
174
- const d = Number(dow);
175
- if (d < 0 || d > 6) return null;
176
- return { minute: m, hour: h, dow: d };
163
+ function previousCronFire(cron: string, timezone: string, at: number): number | null {
164
+ let cursor = at - PREVIOUS_LOOKBACK_MS;
165
+ let prev: number | null = null;
166
+ let steps = 0;
167
+ while (steps < MAX_CRON_FIRES) {
168
+ const t = nextCronFireAt(cron, cursor, timezone);
169
+ if (t === undefined || t >= at) break;
170
+ prev = t;
171
+ cursor = t;
172
+ steps++;
177
173
  }
178
- return null;
179
- }
180
-
181
- function civilFiresOnDay(
182
- dateStr: string,
183
- parsed: SimpleDaily,
184
- timezone: string,
185
- ): readonly number[] {
186
- const matches: number[] = [];
187
- const base = Date.parse(`${dateStr}T00:00:00.000Z`);
188
- if (Number.isNaN(base)) return [];
189
-
190
- for (let offsetMin = -14 * 60; offsetMin <= 14 * 60; offsetMin += 15) {
191
- const utc = base + parsed.hour * 3_600_000 + parsed.minute * 60_000 - offsetMin * 60_000;
192
- const local = zonedParts(utc, timezone);
193
- if (local.date === dateStr && local.hour === parsed.hour && local.minute === parsed.minute) {
194
- if (parsed.dow !== undefined) {
195
- const localDow = zonedDow(utc, timezone);
196
- if (localDow !== parsed.dow) continue;
197
- }
198
- if (!matches.includes(utc)) matches.push(utc);
199
- }
200
- }
201
- return matches;
202
- }
203
-
204
- interface ZonedParts {
205
- readonly date: string;
206
- readonly hour: number;
207
- readonly minute: number;
208
- }
209
-
210
- function zonedParts(utcMs: number, timezone: string): ZonedParts {
211
- const fmt = new Intl.DateTimeFormat("en-US", {
212
- timeZone: timezone,
213
- year: "numeric",
214
- month: "2-digit",
215
- day: "2-digit",
216
- hour: "2-digit",
217
- minute: "2-digit",
218
- hourCycle: "h23",
219
- });
220
- const parts = fmt.formatToParts(new Date(utcMs));
221
- const get = (type: Intl.DateTimeFormatPartTypes): string =>
222
- parts.find((p) => p.type === type)?.value ?? "0";
223
- return {
224
- date: `${get("year")}-${get("month")}-${get("day")}`,
225
- hour: Number(get("hour")),
226
- minute: Number(get("minute")),
227
- };
228
- }
229
-
230
- function zonedDow(utcMs: number, timezone: string): number {
231
- const fmt = new Intl.DateTimeFormat("en-US", {
232
- timeZone: timezone,
233
- weekday: "short",
234
- });
235
- const w = fmt.format(new Date(utcMs));
236
- const map: Record<string, number> = {
237
- Sun: 0,
238
- Mon: 1,
239
- Tue: 2,
240
- Wed: 3,
241
- Thu: 4,
242
- Fri: 5,
243
- Sat: 6,
244
- };
245
- return map[w] ?? 0;
174
+ return prev;
246
175
  }
@@ -466,8 +466,9 @@ describe("DST ambiguity", () => {
466
466
  expect(doctorSrc).not.toMatch(/detectDstAmbiguity/);
467
467
  });
468
468
 
469
- test("overlap day: two civil instants; lease does not span the gap; stub isDue avoids walk", async () => {
470
- // US fall-back 2026-11-01: 01:30 local occurs twice (~1h apart in UTC).
469
+ test("overlap day: crontab fires once; next slot is the following day", async () => {
470
+ // US fall-back 2026-11-01: 01:30 local occurs twice. Bun.cron.parse
471
+ // matches crontab — a fixed wall time fires at the first occurrence only.
471
472
  const from = Date.UTC(2026, 10, 1, 0, 0, 0);
472
473
  const until = Date.UTC(2026, 10, 2, 0, 0, 0);
473
474
  const fires = nextOccurrences(
@@ -481,13 +482,11 @@ describe("DST ambiguity", () => {
481
482
  from,
482
483
  until,
483
484
  );
484
- expect(fires.length).toBe(2);
485
- expect(fires[1]! - fires[0]!).toBe(3_600_000);
485
+ expect(fires).toHaveLength(1);
486
+ expect(fires[0]).toBe(Date.UTC(2026, 10, 1, 5, 30, 0));
486
487
 
487
- // Stub isDue (nextRunAt unset after first fire): does not walk civil fires →
488
- // advancing across the overlap gap does not double-fire.
489
488
  const store = createMemoryCronStore();
490
- const rt = createTestClockRuntime(0, {
489
+ const rt = createTestClockRuntime(fires[0]! - 1, {
491
490
  store,
492
491
  instanceId: "dst-fire",
493
492
  leaseMs: 1_000,
@@ -503,43 +502,13 @@ describe("DST ambiguity", () => {
503
502
  rt.onCron("ambig", () => {
504
503
  ran.push(rt.now());
505
504
  });
505
+ rt.advance(2);
506
506
  expect((await rt.tick()).ran).toEqual(["ambig"]);
507
507
  rt.advance(3_600_000);
508
508
  expect((await rt.tick()).ran).toEqual([]);
509
509
  expect(ran).toHaveLength(1);
510
-
511
- // Lease TTL << 1h overlap gap: if nextRunAt is driven to the second civil
512
- // instant, a second fire CAN occur. Leader lease is not a DST policy.
513
- const store2 = createMemoryCronStore();
514
- const rt2 = createTestClockRuntime(fires[0]! - 1, {
515
- store: store2,
516
- instanceId: "dst-risk",
517
- leaseMs: 1_000,
518
- });
519
- rt2.register(
520
- clock("ambig2", {
521
- cron: "30 1 * * *",
522
- timezone: "America/New_York",
523
- }),
524
- );
525
- await rt2.reconcile();
526
- await store2.put({
527
- ...(await store2.get("ambig2"))!,
528
- nextRunAt: fires[0],
529
- });
530
- const ran2: number[] = [];
531
- rt2.onCron("ambig2", () => {
532
- ran2.push(rt2.now());
533
- });
534
- rt2.advance(2);
535
- expect((await rt2.tick()).ran).toEqual(["ambig2"]);
536
- rt2.advance(fires[1]! - rt2.now());
537
- await store2.put({
538
- ...(await store2.get("ambig2"))!,
539
- nextRunAt: fires[1],
540
- });
541
- expect((await rt2.tick()).ran).toEqual(["ambig2"]);
542
- expect(ran2).toHaveLength(2);
510
+ const after = await store.get("ambig");
511
+ expect(after?.nextRunAt).toBeGreaterThan(fires[0]! + 3_600_000);
543
512
  });
544
513
  });
545
514
 
@@ -50,7 +50,12 @@ export { runDurable, type DurableResult, type RunDurableOptions } from "./clock/
50
50
 
51
51
  export { cronHealth, type CatchUpPolicy, type CronHealth } from "./clock/health.ts";
52
52
 
53
- export { nextOccurrences, previousOccurrence, countMissedOccurrences } from "./clock/schedule.ts";
53
+ export {
54
+ nextOccurrences,
55
+ previousOccurrence,
56
+ countMissedOccurrences,
57
+ nextCronFireAt,
58
+ } from "./clock/schedule.ts";
54
59
 
55
60
  export {
56
61
  pauseCron,
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Gate boot audit — every HTTP trigger must declare auth posture.
3
3
  *
4
- * Fail loud: missing gate and missing {@link gate.public} → {@link GateBootError}.
4
+ * Fail loud: missing gate and missing `.public()` → {@link GateBootError}.
5
5
  * `unguardedHttp: "allow"` is honoured **only** when `env === "test"` — never a
6
- * production-wide bypass. Migrate real apps with per-trigger `gate.public`.
6
+ * production-wide bypass. Migrate real apps with per-trigger `.public()`.
7
7
  */
8
8
 
9
9
  import { GATE_PUBLIC_NAME } from "./declare.ts";
@@ -19,7 +19,7 @@ export interface GatePostureGap {
19
19
  }
20
20
 
21
21
  /**
22
- * Thrown when HTTP triggers omit both a gate chain and {@link gate.public}.
22
+ * Thrown when HTTP triggers omit both a gate chain and `.public()`.
23
23
  * Lists every gap (Vault-style — fail once with the full set).
24
24
  */
25
25
  export class GateBootError extends Error {
@@ -28,7 +28,7 @@ export class GateBootError extends Error {
28
28
  constructor(gaps: readonly GatePostureGap[]) {
29
29
  const lines = gaps.map((g) => ` - ${g.flowId} ${g.method} ${g.path}`);
30
30
  super(
31
- `gate boot failed — ${gaps.length} HTTP trigger(s) missing auth posture (attach a gate or gate.public):\n${lines.join("\n")}`,
31
+ `gate boot failed — ${gaps.length} HTTP trigger(s) missing auth posture (attach a gate or .public()):\n${lines.join("\n")}`,
32
32
  );
33
33
  this.name = "GateBootError";
34
34
  this.gaps = gaps;
@@ -84,7 +84,7 @@ export function hasHttpGatePosture(
84
84
  }
85
85
 
86
86
  /**
87
- * Collect HTTP triggers with neither a gate nor {@link gate.public}.
87
+ * Collect HTTP triggers with neither a gate nor `.public()`.
88
88
  *
89
89
  * @param bindings - Adopted / registered bindings
90
90
  */
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import type { RateStrategy } from "../../manifest/types.ts";
8
+ import { gateRegistry } from "../../kernel/element-registries.ts";
8
9
  import { DEFAULT_RATE_STRATEGY } from "./constants.ts";
9
10
  import { GATE_PUBLIC_NAME, type GateAllDecl, type GateMember } from "./flatten.ts";
10
11
 
@@ -89,6 +90,29 @@ export interface RateGateDecl {
89
90
  /** Declared gate handle. */
90
91
  export type GateDecl = PolicyGateDecl | RateGateDecl;
91
92
 
93
+ /**
94
+ * `gate.policy` / `gate.scope` / `gate.rate` push into the shared
95
+ * {@link gateRegistry} (`src/kernel/element-registries.ts`) so {@link oke}
96
+ * can auto-populate `gate.policies` with zero explicit array — mirrors the
97
+ * {@link on} trigger-drain registry (`src/kernel/on.ts`). `gate.public` is
98
+ * built into the runtime; `gate.all` is a composition of already-registered
99
+ * members.
100
+ *
101
+ * Snapshot of every policy / rate declared since the last reset.
102
+ */
103
+ export function listGates(): readonly GateDecl[] {
104
+ return gateRegistry.slice();
105
+ }
106
+
107
+ /**
108
+ * Clear the gate registry (tests / fresh app adopt).
109
+ *
110
+ * @internal
111
+ */
112
+ export function resetGates(): void {
113
+ gateRegistry.length = 0;
114
+ }
115
+
92
116
  /**
93
117
  * Shape of the {@link gate} element namespace.
94
118
  */
@@ -151,9 +175,11 @@ export const gate: GateNamespace = {
151
175
  );
152
176
  }
153
177
  if (typeof checkOrOptions === "function") {
154
- return { kind: "policy", name, check: checkOrOptions };
178
+ const decl: PolicyGateDecl = { kind: "policy", name, check: checkOrOptions };
179
+ gateRegistry.push(decl);
180
+ return decl;
155
181
  }
156
- return {
182
+ const decl: PolicyGateDecl = {
157
183
  kind: "policy",
158
184
  name,
159
185
  check: checkOrOptions.check,
@@ -161,6 +187,8 @@ export const gate: GateNamespace = {
161
187
  ? { description: checkOrOptions.description }
162
188
  : {}),
163
189
  };
190
+ gateRegistry.push(decl);
191
+ return decl;
164
192
  },
165
193
 
166
194
  /**
@@ -172,12 +200,14 @@ export const gate: GateNamespace = {
172
200
  if (name === GATE_PUBLIC_NAME) {
173
201
  throw new TypeError('gate.scope: name "public" is reserved — use gate.public');
174
202
  }
175
- return {
203
+ const decl: PolicyGateDecl = {
176
204
  kind: "policy",
177
205
  name,
178
206
  check: ({ auth }) => auth.scopes.has(name),
179
207
  scopes: [name],
180
208
  };
209
+ gateRegistry.push(decl);
210
+ return decl;
181
211
  },
182
212
 
183
213
  public: {
@@ -201,7 +231,7 @@ export const gate: GateNamespace = {
201
231
  }
202
232
  const strategy = options.strategy ?? DEFAULT_RATE_STRATEGY;
203
233
  const name = `rate:${strategy}:${options.max}/${options.per}`;
204
- return {
234
+ const decl: RateGateDecl = {
205
235
  kind: "rate",
206
236
  name,
207
237
  strategy,
@@ -211,6 +241,8 @@ export const gate: GateNamespace = {
211
241
  overridable: options.overridable ?? false,
212
242
  ...(options.description !== undefined ? { description: options.description } : {}),
213
243
  };
244
+ gateRegistry.push(decl);
245
+ return decl;
214
246
  },
215
247
 
216
248
  /**
@@ -7,7 +7,7 @@
7
7
 
8
8
  import type { GateDecl } from "./declare.ts";
9
9
 
10
- /** Reserved policy name for `gate.public` / `.gate.public`. */
10
+ /** Reserved policy name for `gate.public` / `.public()`. */
11
11
  export const GATE_PUBLIC_NAME = "public";
12
12
 
13
13
  /** Member of `gate.all` — a policy/rate or a nested `all` handle. */
@@ -70,7 +70,7 @@ export interface GateRuntime {
70
70
  */
71
71
  export function createGateRuntime(options: CreateGateRuntimeOptions = {}): GateRuntime {
72
72
  const map = new Map<string, GateDecl>();
73
- // Built-in sentinel — `.gate.public` works without listing it in `oke({ gates })`.
73
+ // Built-in sentinel — `.public()` works without listing it in `oke({ gates })`.
74
74
  map.set(gate.public.name, gate.public);
75
75
  for (const g of options.gates ?? []) {
76
76
  if (isGateAllDecl(g)) {
@@ -176,6 +176,8 @@ export type {
176
176
  AiModelDecl,
177
177
  AiPromptDecl,
178
178
  AiAgentDecl,
179
+ AiMcpServerDecl,
180
+ AiMcpServerOptions,
179
181
  AiEmbedDecl,
180
182
  AiRuntime,
181
183
  AgentDenial,
@@ -34,7 +34,8 @@ export interface SignalOptions {
34
34
  }
35
35
 
36
36
  /**
37
- * Declared signal handle — usable as `on(signal, flow)` and `fx.emit(signal, …)`.
37
+ * Declared signal handle — usable as `on(signal, flow)`, `fx.emit(signal, …)`,
38
+ * and `fx.deadLetters(signal)`.
38
39
  */
39
40
  export interface SignalDecl<T = unknown> {
40
41
  /** Signal name (manifest key). */
@@ -3,7 +3,12 @@
3
3
  * emit / subscribe / live / drain for `fx` and tests.
4
4
  */
5
5
 
6
- import type { SignalBus, SignalDriver, SignalEmitOptions } from "../../drivers/signal-types.ts";
6
+ import type {
7
+ DeadLetter,
8
+ SignalBus,
9
+ SignalDriver,
10
+ SignalEmitOptions,
11
+ } from "../../drivers/signal-types.ts";
7
12
  import type { SignalDecl } from "./declare.ts";
8
13
 
9
14
  /** Options for {@link createSignalRuntime}. */
@@ -48,6 +53,12 @@ export interface SignalRuntime {
48
53
  * @param options - Optional emit options (`key` for per-key once ordering)
49
54
  */
50
55
  emit(name: string, payload?: unknown, options?: SignalEmitOptions): Promise<void>;
56
+ /**
57
+ * Dead-lettered messages for one signal (auto-starts).
58
+ *
59
+ * @param name - Signal name
60
+ */
61
+ deadLetters(name: string): Promise<readonly DeadLetter[]>;
51
62
  /** Close the bus. */
52
63
  close(): Promise<void>;
53
64
  }
@@ -87,6 +98,10 @@ export function createSignalRuntime(options: CreateSignalRuntimeOptions): Signal
87
98
  const b = await this.start();
88
99
  await b.emit(name, payload, options);
89
100
  },
101
+ async deadLetters(name) {
102
+ const b = await this.start();
103
+ return b.deadLetters(name);
104
+ },
90
105
  async close() {
91
106
  if (bus) {
92
107
  await bus.close();
@@ -8,6 +8,7 @@
8
8
 
9
9
  export { signal } from "./signal/declare.ts";
10
10
  export type { SignalDecl, SignalOptions } from "./signal/declare.ts";
11
+ export type { DeadLetter, SignalFailureReason } from "../drivers/signal-types.ts";
11
12
 
12
13
  export { createSignalRuntime } from "./signal/runtime.ts";
13
14
  export type { CreateSignalRuntimeOptions, SignalRuntime } from "./signal/runtime.ts";