okengine 0.10.2 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (343) hide show
  1. package/package.json +76 -10
  2. package/site/content/docs/ai/mcp.mdx +0 -1
  3. package/site/content/docs/deployment/docker-swarm.mdx +48 -111
  4. package/site/content/docs/deployment/docker.mdx +43 -36
  5. package/site/content/docs/deployment/index.mdx +9 -9
  6. package/site/content/docs/deployment/kubernetes.mdx +4 -4
  7. package/site/content/docs/elements/ai.mdx +7 -13
  8. package/site/content/docs/elements/channel.mdx +9 -13
  9. package/site/content/docs/elements/clock.mdx +8 -14
  10. package/site/content/docs/elements/flow.mdx +46 -54
  11. package/site/content/docs/elements/gate.mdx +6 -11
  12. package/site/content/docs/elements/signal.mdx +2 -8
  13. package/site/content/docs/elements/store.mdx +79 -93
  14. package/site/content/docs/elements/vault.mdx +89 -76
  15. package/site/content/docs/get-started/basic-usage.mdx +8 -14
  16. package/site/content/docs/get-started/installation.mdx +36 -36
  17. package/site/content/docs/get-started/introduction.mdx +11 -0
  18. package/site/content/docs/get-started/why.mdx +3 -4
  19. package/site/content/docs/index.mdx +0 -1
  20. package/site/content/docs/meta.json +0 -1
  21. package/site/content/docs/plugins/anonymous.mdx +1 -1
  22. package/site/content/docs/plugins/magic-link.mdx +11 -11
  23. package/site/content/docs/plugins/otp.mdx +2 -2
  24. package/site/content/docs/plugins/passkey.mdx +1 -1
  25. package/site/content/docs/plugins/two-factor.mdx +1 -1
  26. package/site/content/docs/plugins/username.mdx +1 -1
  27. package/site/content/docs/recipes/caddy.mdx +1 -1
  28. package/site/content/docs/recipes/cockroachdb.mdx +2 -2
  29. package/site/content/docs/recipes/dragonfly.mdx +1 -1
  30. package/site/content/docs/recipes/index.mdx +1 -2
  31. package/site/content/docs/recipes/llama-cpp.mdx +21 -23
  32. package/site/content/docs/recipes/mailpit.mdx +11 -11
  33. package/site/content/docs/recipes/meilisearch.mdx +10 -10
  34. package/site/content/docs/recipes/meta.json +0 -1
  35. package/site/content/docs/recipes/ollama.mdx +1 -1
  36. package/site/content/docs/recipes/pgdog.mdx +12 -9
  37. package/site/content/docs/recipes/postgres.mdx +3 -3
  38. package/site/content/docs/recipes/redis.mdx +2 -2
  39. package/site/content/docs/recipes/rustfs.mdx +10 -7
  40. package/site/content/docs/recipes/sglang.mdx +1 -1
  41. package/site/content/docs/recipes/supabase-docker.mdx +1 -1
  42. package/site/content/docs/recipes/timescale.mdx +2 -2
  43. package/site/content/docs/recipes/valkey.mdx +1 -1
  44. package/site/content/docs/recipes/vllm.mdx +1 -1
  45. package/site/content/docs/recipes/yugabytedb.mdx +2 -2
  46. package/site/content/docs/reference/cli.md +61 -38
  47. package/site/content/docs/reference/configuration.mdx +145 -70
  48. package/site/content/docs/reference/environment-variables.mdx +36 -28
  49. package/site/content/docs/reference/errors.mdx +28 -29
  50. package/site/content/docs/reference/fx.mdx +14 -7
  51. package/site/content/docs/reference/i18n.mdx +17 -6
  52. package/site/content/docs/reference/index.mdx +5 -0
  53. package/site/content/docs/reference/meta.json +1 -0
  54. package/site/content/docs/reference/migrating-environments.mdx +158 -0
  55. package/site/content/docs/reference/plugins.mdx +2 -2
  56. package/src/auth/gate-auth.test.ts +12 -10
  57. package/src/cli/ai-setup/ai-setup.test.ts +30 -16
  58. package/src/cli/ai-setup/apply.ts +111 -15
  59. package/src/cli/ai-setup/catalog.ts +3 -3
  60. package/src/cli/ai-setup/index.ts +1 -1
  61. package/src/cli/ai-setup/prompts.ts +1 -1
  62. package/src/cli/ai-setup/recommend.test.ts +1 -1
  63. package/src/cli/ask-seed.test.ts +3 -3
  64. package/src/cli/build.ts +8 -9
  65. package/src/cli/db-auto-push.test.ts +6 -3
  66. package/src/cli/db-auto-push.ts +7 -5
  67. package/src/cli/db-seed.ts +7 -8
  68. package/src/cli/db.test.ts +70 -71
  69. package/src/cli/db.ts +86 -12
  70. package/src/cli/dev-controller.test.ts +35 -0
  71. package/src/cli/dev-controller.ts +298 -0
  72. package/src/cli/dev-controls.ts +11 -65
  73. package/src/cli/dev-db-push.test.ts +47 -15
  74. package/src/cli/dev-schema-sync.test.ts +11 -11
  75. package/src/cli/dev-schema-sync.ts +10 -10
  76. package/src/cli/dev-session-lock.test.ts +106 -0
  77. package/src/cli/dev-session-lock.ts +209 -0
  78. package/src/cli/dev.test.ts +246 -114
  79. package/src/cli/dev.ts +153 -205
  80. package/src/cli/doc-staleness.test.ts +3 -6
  81. package/src/cli/docker-clean.test.ts +33 -1
  82. package/src/cli/docker-clean.ts +28 -33
  83. package/src/cli/docker-cli.test.ts +12 -11
  84. package/src/cli/docker.ts +95 -20
  85. package/src/cli/doctor.test.ts +40 -0
  86. package/src/cli/doctor.ts +142 -9
  87. package/src/cli/drizzle-env.test.ts +11 -13
  88. package/src/cli/drizzle-env.ts +18 -11
  89. package/src/cli/ensure-drizzle-config.ts +11 -14
  90. package/src/cli/hero-meta.test.ts +15 -15
  91. package/src/cli/hero-meta.ts +10 -9
  92. package/src/cli/index.ts +36 -6
  93. package/src/cli/load-config.images.test.ts +44 -10
  94. package/src/cli/load-config.source.test.ts +50 -0
  95. package/src/cli/load-config.ts +35 -29
  96. package/src/cli/meilisearch-local.ts +1 -2
  97. package/src/cli/mode.ts +7 -58
  98. package/src/cli/registry.ts +175 -19
  99. package/src/cli/resolve-dev-sql-env.test.ts +8 -16
  100. package/src/cli/resolve-dev-sql-env.ts +13 -15
  101. package/src/cli/safe-defaults.test.ts +29 -11
  102. package/src/cli/schema.ts +65 -16
  103. package/src/cli/test.ts +58 -0
  104. package/src/cli/tui/App.tsx +252 -0
  105. package/src/cli/tui/DevLive.tsx +116 -0
  106. package/src/cli/tui/components/ConfirmDialog.tsx +48 -0
  107. package/src/cli/tui/components/HeaderBar.tsx +67 -0
  108. package/src/cli/tui/components/LogStream.tsx +47 -0
  109. package/src/cli/tui/components/PanelCard.tsx +39 -0
  110. package/src/cli/tui/components/SelectRow.tsx +37 -0
  111. package/src/cli/tui/components/SlashPalette.tsx +172 -0
  112. package/src/cli/tui/components/Spinner.tsx +32 -0
  113. package/src/cli/tui/components/StatusBar.tsx +51 -0
  114. package/src/cli/tui/components/StatusDot.tsx +37 -0
  115. package/src/cli/tui/components/TabBar.tsx +49 -0
  116. package/src/cli/tui/components/Wordmark.tsx +43 -0
  117. package/src/cli/tui/format.ts +38 -0
  118. package/src/cli/tui/keys.test.ts +18 -0
  119. package/src/cli/tui/keys.ts +26 -0
  120. package/src/cli/tui/launch.test.ts +18 -0
  121. package/src/cli/tui/launch.ts +57 -0
  122. package/src/cli/tui/panels/Dashboard.tsx +201 -0
  123. package/src/cli/tui/panels/Database.tsx +198 -0
  124. package/src/cli/tui/panels/Dev.tsx +257 -0
  125. package/src/cli/tui/panels/Docker.tsx +357 -0
  126. package/src/cli/tui/panels/Navigator.test.ts +22 -0
  127. package/src/cli/tui/panels/Navigator.tsx +213 -0
  128. package/src/cli/tui/slash-catalog.test.ts +31 -0
  129. package/src/cli/tui/slash-catalog.ts +184 -0
  130. package/src/cli/tui/slash-run.ts +54 -0
  131. package/src/cli/tui/state/ports.ts +95 -0
  132. package/src/cli/tui/theme.ts +24 -0
  133. package/src/cli/vault-cmd.test.ts +402 -0
  134. package/src/cli/vault-cmd.ts +727 -69
  135. package/src/cli/vault-secure-input.test.ts +56 -0
  136. package/src/cli/vault-secure-input.ts +124 -0
  137. package/src/compiler/effects-infer.ts +7 -0
  138. package/src/compiler/extract.ts +12 -0
  139. package/src/compiler/fixtures/skyport/oke.config.ts +7 -5
  140. package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +1 -1
  141. package/src/compiler/fixtures/skyport.expected.json +1 -1
  142. package/src/compiler/generate-adopt.ts +27 -1
  143. package/src/compiler-entry.ts +7 -0
  144. package/src/config/define-config.test.ts +111 -47
  145. package/src/config/driver-defaults.test.ts +115 -0
  146. package/src/config/driver-defaults.ts +156 -0
  147. package/src/config/index.ts +246 -131
  148. package/src/config/resolve-driver.test.ts +14 -19
  149. package/src/console/server/app.ts +4 -2
  150. package/src/console/server/console.test.ts +58 -1
  151. package/src/console/server/flows.ts +22 -2
  152. package/src/console/server/index.ts +3 -0
  153. package/src/console/server/operator-db.test.ts +55 -13
  154. package/src/console/server/operator-db.ts +305 -263
  155. package/src/console/server/serve.ts +16 -7
  156. package/src/console/server/state.ts +31 -9
  157. package/src/console/server/vault.test.ts +120 -5
  158. package/src/console/server/vault.ts +208 -12
  159. package/src/console/ui/dist/assets/{index-D-9KFce5.js → index--Y0Uaz-m.js} +2 -2
  160. package/src/console/ui/dist/assets/panel-vault-BkZdJPX8.js +1 -0
  161. package/src/console/ui/dist/assets/style-C3L349iZ.css +3 -0
  162. package/src/console/ui/dist/index.html +2 -2
  163. package/src/console/ui/shell/client.ts +16 -0
  164. package/src/console/ui/shell/panels/vault/VaultPanel.tsx +54 -1
  165. package/src/console/ui/vault/VaultA11yView.tsx +33 -1
  166. package/src/console/ui/vault/backend.test.ts +75 -0
  167. package/src/console/ui/vault/backend.ts +117 -0
  168. package/src/console/ui/vault/fixture.ts +21 -2
  169. package/src/console/ui/vault/index.ts +11 -1
  170. package/src/console/ui/vault/types.ts +26 -0
  171. package/src/docker/compose.ts +164 -61
  172. package/src/docker/derive.ts +106 -12
  173. package/src/docker/docker.test.ts +156 -110
  174. package/src/docker/helpers.ts +98 -0
  175. package/src/docker/images-config.test.ts +163 -0
  176. package/src/docker/index.ts +30 -5
  177. package/src/docker/recipes/index.ts +0 -3
  178. package/src/docker/recipes/llama-cpp.ts +4 -4
  179. package/src/docker/recipes/pgdog.ts +9 -2
  180. package/src/docker/resources.test.ts +71 -0
  181. package/src/docker/resources.ts +176 -0
  182. package/src/docker/stack-id.test.ts +11 -4
  183. package/src/docker/stack-id.ts +42 -32
  184. package/src/docker/stack.integration.test.ts +4 -15
  185. package/src/docker/types.ts +35 -3
  186. package/src/drivers/ai-ollama.integration.test.ts +2 -2
  187. package/src/drivers/conformance.test.ts +11 -12
  188. package/src/drivers/drizzle-dialect.test.ts +0 -4
  189. package/src/drivers/drizzle-dialect.ts +7 -13
  190. package/src/drivers/index.ts +24 -7
  191. package/src/drivers/journal-postgres.test.ts +3 -1
  192. package/src/drivers/journal-postgres.ts +4 -2
  193. package/src/drivers/meilisearch.ts +1 -1
  194. package/src/drivers/pglite.ts +28 -5
  195. package/src/drivers/types.ts +9 -11
  196. package/src/drivers/vault-aws-secrets-manager.test.ts +159 -0
  197. package/src/drivers/vault-aws-secrets-manager.ts +333 -0
  198. package/src/drivers/vault-builtin.test.ts +110 -0
  199. package/src/drivers/vault-builtin.ts +184 -0
  200. package/src/drivers/vault-driver-removal.test.ts +13 -0
  201. package/src/drivers/vault-managed.test.ts +95 -0
  202. package/src/drivers/vault-managed.ts +101 -25
  203. package/src/drivers/vault-types.ts +25 -5
  204. package/src/elements/ai/runtime.ts +1 -1
  205. package/src/elements/clock/chaos.test.ts +4 -1
  206. package/src/elements/clock/durable.ts +57 -4
  207. package/src/elements/clock.test.ts +13 -14
  208. package/src/elements/gate/constants.ts +18 -0
  209. package/src/elements/gate.test.ts +1 -1
  210. package/src/elements/gate.ts +2 -2
  211. package/src/elements/store/domain-ddl.test.ts +5 -12
  212. package/src/elements/store/emit-drizzle.ts +44 -19
  213. package/src/elements/store/index-boot.test.ts +15 -116
  214. package/src/elements/store/load-plugin-tables.ts +1 -1
  215. package/src/elements/store/resource-list-docs.fixture.ts +12 -6
  216. package/src/elements/store/resource-list-docs.test.ts +64 -44
  217. package/src/elements/store/resource.test.ts +17 -10
  218. package/src/elements/store/runtime.ts +9 -12
  219. package/src/elements/store/schema-decl.test.ts +16 -32
  220. package/src/elements/store/seed.test.ts +1 -2
  221. package/src/elements/store/seed.ts +7 -8
  222. package/src/elements/store/sql-condition.test.ts +2 -2
  223. package/src/elements/store/sql-session.test.ts +8 -10
  224. package/src/elements/store/sql-session.ts +10 -2
  225. package/src/elements/store.test.ts +13 -13
  226. package/src/elements/vault/audit.ts +233 -0
  227. package/src/elements/vault/boot-chain.test.ts +78 -0
  228. package/src/elements/vault/boot-chain.ts +43 -39
  229. package/src/elements/vault/builtin-adapter.test.ts +479 -0
  230. package/src/elements/vault/builtin-adapter.ts +1184 -0
  231. package/src/elements/vault/crypto.test.ts +237 -0
  232. package/src/elements/vault/crypto.ts +431 -0
  233. package/src/elements/vault/declare.ts +164 -3
  234. package/src/elements/vault/errors.ts +56 -0
  235. package/src/elements/vault/kms-unseal.test.ts +76 -0
  236. package/src/elements/vault/kms-unseal.ts +181 -0
  237. package/src/elements/vault/path.ts +90 -0
  238. package/src/elements/vault/resilience.test.ts +102 -0
  239. package/src/elements/vault/resilience.ts +107 -0
  240. package/src/elements/vault/runtime.ts +36 -4
  241. package/src/elements/vault/security-checklist.test.ts +130 -0
  242. package/src/elements/vault/storage.ts +448 -0
  243. package/src/elements/vault/types.ts +288 -0
  244. package/src/elements/vault/unseal.ts +150 -0
  245. package/src/elements/vault.test.ts +72 -4
  246. package/src/elements/vault.ts +91 -2
  247. package/src/full.ts +248 -0
  248. package/src/http.ts +41 -0
  249. package/src/i18n/interpolate-simple.ts +37 -0
  250. package/src/i18n/messages.ts +51 -4
  251. package/src/i18n-entry.ts +7 -0
  252. package/src/index.ts +35 -145
  253. package/src/journal-entry.ts +20 -0
  254. package/src/kernel/adopt-barrel-fresh.test.ts +25 -10
  255. package/src/kernel/app.ts +40 -42
  256. package/src/kernel/auto-registry.test.ts +2 -2
  257. package/src/kernel/boot-bind/ai.test.ts +5 -5
  258. package/src/kernel/boot-bind/ai.ts +2 -1
  259. package/src/kernel/boot-bind/channel.test.ts +6 -6
  260. package/src/kernel/boot-bind/channel.ts +5 -4
  261. package/src/kernel/boot-bind/clock.ts +5 -5
  262. package/src/kernel/boot-bind/gate.ts +6 -7
  263. package/src/kernel/boot-bind/honor-config.test.ts +42 -42
  264. package/src/kernel/boot-bind/journal.ts +5 -5
  265. package/src/kernel/boot-bind/signal.ts +3 -1
  266. package/src/kernel/boot-bind/store.test.ts +55 -37
  267. package/src/kernel/boot-bind/store.ts +24 -34
  268. package/src/kernel/boot-bind/vault.ts +19 -4
  269. package/src/kernel/boot.test.ts +12 -4
  270. package/src/kernel/boot.ts +31 -19
  271. package/src/kernel/budget-entry.ts +1 -1
  272. package/src/kernel/compensate.test.ts +288 -1
  273. package/src/kernel/compensate.ts +126 -0
  274. package/src/kernel/concurrency.test.ts +10 -9
  275. package/src/kernel/effects-stamping.test.ts +27 -2
  276. package/src/kernel/element-registries.ts +5 -3
  277. package/src/kernel/flow.ts +4 -2
  278. package/src/kernel/fx.test.ts +119 -3
  279. package/src/kernel/fx.ts +215 -40
  280. package/src/kernel/index.ts +14 -0
  281. package/src/kernel/journal.ts +130 -7
  282. package/src/kernel/pipeline.test.ts +20 -6
  283. package/src/kernel/redacted.test.ts +35 -0
  284. package/src/kernel/redacted.ts +24 -2
  285. package/src/kernel/router/create-default.ts +16 -0
  286. package/src/kernel/router/create-edge.ts +18 -0
  287. package/src/kernel/router/create.ts +23 -0
  288. package/src/kernel/router/index.ts +23 -0
  289. package/src/kernel/router/linear.ts +88 -0
  290. package/src/kernel/router/regexp.ts +188 -0
  291. package/src/kernel/router/smart.ts +120 -0
  292. package/src/kernel/router/trie.ts +108 -0
  293. package/src/kernel/router/types.ts +56 -0
  294. package/src/kernel/router.ts +18 -532
  295. package/src/kernel-entry.ts +43 -0
  296. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  297. package/src/manifest/fixtures/skyport.manifest.json +1 -1
  298. package/src/plugins/config-source.test.ts +7 -7
  299. package/src/release/build-lib.ts +58 -0
  300. package/src/release/exports.test.ts +4 -2
  301. package/src/release/exports.ts +30 -4
  302. package/src/release/measure.ts +101 -22
  303. package/src/runs/duckdb.ts +7 -2
  304. package/src/shared/optional-peer.ts +41 -0
  305. package/src/term.test.ts +3 -44
  306. package/src/test/create-test-app.ts +12 -0
  307. package/src/test/provisions.integration.test.ts +1 -1
  308. package/src/test/reset-element-registries.ts +2 -0
  309. package/src/upgrade/codemods.test.ts +7 -20
  310. package/src/upgrade/codemods.ts +93 -60
  311. package/site/content/docs/console/access.mdx +0 -29
  312. package/site/content/docs/console/ai.mdx +0 -35
  313. package/site/content/docs/console/architecture.mdx +0 -35
  314. package/site/content/docs/console/channels.mdx +0 -37
  315. package/site/content/docs/console/clock.mdx +0 -51
  316. package/site/content/docs/console/flows.mdx +0 -31
  317. package/site/content/docs/console/gates.mdx +0 -73
  318. package/site/content/docs/console/index.mdx +0 -54
  319. package/site/content/docs/console/manifest-diff.mdx +0 -34
  320. package/site/content/docs/console/meta.json +0 -24
  321. package/site/content/docs/console/overview.mdx +0 -40
  322. package/site/content/docs/console/plugins.mdx +0 -41
  323. package/site/content/docs/console/privacy.mdx +0 -32
  324. package/site/content/docs/console/runs.mdx +0 -50
  325. package/site/content/docs/console/signals.mdx +0 -40
  326. package/site/content/docs/console/store.mdx +0 -32
  327. package/site/content/docs/console/tenancy.mdx +0 -32
  328. package/site/content/docs/console/traces.mdx +0 -38
  329. package/site/content/docs/console/vault.mdx +0 -41
  330. package/site/content/docs/recipes/openbao.mdx +0 -141
  331. package/src/cli/ask-dev-mode.ts +0 -40
  332. package/src/cli/dev-mode.test.ts +0 -45
  333. package/src/cli/dev-mode.ts +0 -78
  334. package/src/cli/openbao-bootstrap.test.ts +0 -150
  335. package/src/cli/openbao-bootstrap.ts +0 -321
  336. package/src/cli/openbao-restart.integration.test.ts +0 -145
  337. package/src/console/ui/dist/assets/panel-vault-Za1GyJfM.js +0 -1
  338. package/src/console/ui/dist/assets/style-C8MxEWPd.css +0 -3
  339. package/src/docker/recipes/openbao.ts +0 -47
  340. package/src/drivers/libsql.ts +0 -179
  341. package/src/drivers/sqlite.ts +0 -44
  342. package/src/drivers/vault-openbao.test.ts +0 -97
  343. package/src/drivers/vault-openbao.ts +0 -143
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Compiled RegExp router — O(1) static map, plus per-bucket compiled
3
+ * regular expressions for parametric routes.
4
+ */
5
+
6
+ import {
7
+ isUnsupportedByRegExp,
8
+ UnsupportedPathError,
9
+ type RouteMatch,
10
+ type Router,
11
+ } from "./types.ts";
12
+
13
+ function escapeRegex(segment: string): string {
14
+ return segment.replace(/[.^$+{}()|[\]\\]/g, "\\$&");
15
+ }
16
+
17
+ interface CompiledDynamic<T> {
18
+ readonly regex: RegExp;
19
+ /** Per-alternative: which capture index holds the wrapper, param names. */
20
+ readonly alts: readonly {
21
+ readonly wrapGroup: number;
22
+ readonly keys: readonly string[];
23
+ readonly value: T;
24
+ }[];
25
+ }
26
+
27
+ interface MethodTable<T> {
28
+ readonly staticMap: Map<string, T>;
29
+ /**
30
+ * Dynamic routes bucketed by the first path segment (or `""` when the
31
+ * first segment is a param). Each bucket compiles to one RegExp.
32
+ */
33
+ buckets: Map<string, CompiledDynamic<T>> | undefined;
34
+ readonly dynamicRoutes: { path: string; value: T }[];
35
+ }
36
+
37
+ function emptyTable<T>(): MethodTable<T> {
38
+ return { staticMap: new Map(), buckets: undefined, dynamicRoutes: [] };
39
+ }
40
+
41
+ /** First segment key used for dynamic bucketing (`""` if param/empty). */
42
+ function bucketKey(path: string): string {
43
+ const parts = path.startsWith("/") ? path.split("/").slice(1) : path.split("/");
44
+ const first = parts[0] ?? "";
45
+ if (first.startsWith(":") || first === "*" || first === "") return "";
46
+ return first;
47
+ }
48
+
49
+ function compileDynamic<T>(routes: readonly { path: string; value: T }[]): CompiledDynamic<T> {
50
+ const alts: CompiledDynamic<T>["alts"][number][] = [];
51
+ const parts: string[] = [];
52
+ let group = 1;
53
+
54
+ for (const route of routes) {
55
+ if (isUnsupportedByRegExp(route.path)) {
56
+ throw new UnsupportedPathError(route.path);
57
+ }
58
+ const keys: string[] = [];
59
+ const segments = route.path.split("/");
60
+ const body = segments
61
+ .map((seg) => {
62
+ if (seg.startsWith(":") && seg.length > 1) {
63
+ keys.push(seg.slice(1));
64
+ return "([^/]+)";
65
+ }
66
+ return escapeRegex(seg);
67
+ })
68
+ .join("/");
69
+
70
+ parts.push(`(${body})`);
71
+ alts.push({ wrapGroup: group, keys, value: route.value });
72
+ group += 1 + keys.length;
73
+ }
74
+
75
+ const regex = parts.length === 0 ? /(?!)/ : new RegExp(`^(?:${parts.join("|")})$`);
76
+
77
+ return { regex, alts };
78
+ }
79
+
80
+ function matchCompiled<T>(dyn: CompiledDynamic<T>, path: string): RouteMatch<T> | undefined {
81
+ const m = dyn.regex.exec(path);
82
+ if (!m) return undefined;
83
+
84
+ for (const alt of dyn.alts) {
85
+ if (m[alt.wrapGroup] === undefined) continue;
86
+ const params: Record<string, string> = {};
87
+ for (let i = 0; i < alt.keys.length; i++) {
88
+ const key = alt.keys[i];
89
+ const captured = m[alt.wrapGroup + 1 + i];
90
+ if (key !== undefined && captured !== undefined) {
91
+ params[key] = captured;
92
+ }
93
+ }
94
+ return { value: alt.value, params };
95
+ }
96
+ return undefined;
97
+ }
98
+
99
+ /**
100
+ * Compiled RegExp router — O(1) static map, plus per-bucket compiled
101
+ * regular expressions for parametric routes.
102
+ */
103
+ export class RegExpRouter<T> implements Router<T> {
104
+ readonly name = "RegExpRouter";
105
+ readonly #tables = new Map<string, MethodTable<T>>();
106
+ #built = false;
107
+
108
+ /**
109
+ * @param method - HTTP method
110
+ * @param path - Path pattern
111
+ * @param value - Route value
112
+ */
113
+ add(method: string, path: string, value: T): void {
114
+ if (this.#built) {
115
+ throw new Error("RegExpRouter matcher is already built");
116
+ }
117
+ if (isUnsupportedByRegExp(path)) {
118
+ throw new UnsupportedPathError(path);
119
+ }
120
+ let table = this.#tables.get(method);
121
+ if (!table) {
122
+ table = emptyTable();
123
+ this.#tables.set(method, table);
124
+ }
125
+ if (!path.includes("/:")) {
126
+ table.staticMap.set(path, value);
127
+ return;
128
+ }
129
+ table.dynamicRoutes.push({ path, value });
130
+ }
131
+
132
+ /**
133
+ * Compile dynamic routes. Idempotent.
134
+ */
135
+ build(): void {
136
+ if (this.#built) return;
137
+ for (const table of this.#tables.values()) {
138
+ if (table.dynamicRoutes.length === 0) continue;
139
+ const grouped = new Map<string, { path: string; value: T }[]>();
140
+ for (const route of table.dynamicRoutes) {
141
+ const key = bucketKey(route.path);
142
+ let list = grouped.get(key);
143
+ if (!list) {
144
+ list = [];
145
+ grouped.set(key, list);
146
+ }
147
+ list.push(route);
148
+ }
149
+ const buckets = new Map<string, CompiledDynamic<T>>();
150
+ for (const [key, routes] of grouped) {
151
+ buckets.set(key, compileDynamic(routes));
152
+ }
153
+ table.buckets = buckets;
154
+ }
155
+ this.#built = true;
156
+ }
157
+
158
+ /**
159
+ * @param method - HTTP method
160
+ * @param path - Pathname
161
+ */
162
+ match(method: string, path: string): RouteMatch<T> | undefined {
163
+ this.build();
164
+ const table = this.#tables.get(method);
165
+ if (!table) return undefined;
166
+
167
+ const staticHit = table.staticMap.get(path);
168
+ if (staticHit !== undefined) {
169
+ return { value: staticHit, params: {} };
170
+ }
171
+
172
+ const buckets = table.buckets;
173
+ if (!buckets) return undefined;
174
+
175
+ const key = bucketKey(path);
176
+ const primary = buckets.get(key);
177
+ if (primary) {
178
+ const hit = matchCompiled(primary, path);
179
+ if (hit) return hit;
180
+ }
181
+ // Routes whose first segment is a param live in the `""` bucket.
182
+ if (key !== "") {
183
+ const catchAll = buckets.get("");
184
+ if (catchAll) return matchCompiled(catchAll, path);
185
+ }
186
+ return undefined;
187
+ }
188
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Smart router — buffers routes, then at first match (startup) selects
3
+ * the first candidate that accepts every pattern, falling back on
4
+ * {@link UnsupportedPathError}.
5
+ */
6
+
7
+ import { UnsupportedPathError, type RouteMatch, type Router, type RouterFactory } from "./types.ts";
8
+
9
+ /**
10
+ * Smart router — buffers routes, then at first match (startup) selects
11
+ * a candidate, falling back to the next on {@link UnsupportedPathError}.
12
+ *
13
+ * Duck-types an optional `build()` so RegExpRouter can compile without
14
+ * a hard `instanceof` import (keeps edge presets free of regexp.ts).
15
+ */
16
+ export class SmartRouter<T> implements Router<T> {
17
+ name = "SmartRouter";
18
+ readonly #factories: readonly RouterFactory<T>[];
19
+ /** Permanent route list (survives rebuilds for late auth bindings). */
20
+ readonly #allRoutes: [string, string, T][] = [];
21
+ /** Buffer until first {@link #select}; cleared while a delegate is active. */
22
+ #routes: [string, string, T][] | undefined = [];
23
+ #active: Router<T> | undefined;
24
+ /** Original {@link match} before a delegate is bound. */
25
+ readonly #matchSelf: (method: string, path: string) => RouteMatch<T> | undefined;
26
+
27
+ /**
28
+ * @param init - Candidate factories, fastest first (fresh instance per try)
29
+ */
30
+ constructor(init: {
31
+ readonly routers?: Router<T>[];
32
+ readonly factories?: readonly RouterFactory<T>[];
33
+ }) {
34
+ if (init.factories) {
35
+ this.#factories = init.factories;
36
+ } else if (init.routers) {
37
+ // Wrap pre-built instances once — prefer factories for isolation.
38
+ this.#factories = init.routers.map((r) => () => r);
39
+ } else {
40
+ this.#factories = [];
41
+ }
42
+ this.#matchSelf = (method, path) => {
43
+ const active = this.#active ?? this.#select();
44
+ return active.match(method, path);
45
+ };
46
+ this.match = this.#matchSelf;
47
+ }
48
+
49
+ /**
50
+ * @param method - HTTP method
51
+ * @param path - Path pattern
52
+ * @param value - Route value
53
+ */
54
+ add(method: string, path: string, value: T): void {
55
+ this.#allRoutes.push([method, path, value]);
56
+ if (this.#routes) {
57
+ this.#routes.push([method, path, value]);
58
+ return;
59
+ }
60
+ // Late registration after first match (e.g. auth HTTP at boot) — rebuild.
61
+ this.#active = undefined;
62
+ this.#routes = this.#allRoutes.slice();
63
+ this.match = this.#matchSelf;
64
+ }
65
+
66
+ /**
67
+ * Force selection now (startup), without waiting for the first match.
68
+ */
69
+ build(): Router<T> {
70
+ return this.#select();
71
+ }
72
+
73
+ /** Active delegate after {@link build} / first {@link match}. */
74
+ get activeRouter(): Router<T> {
75
+ if (!this.#active) {
76
+ throw new Error("No active router has been determined yet.");
77
+ }
78
+ return this.#active;
79
+ }
80
+
81
+ /**
82
+ * @param method - HTTP method
83
+ * @param path - Pathname
84
+ */
85
+ match(method: string, path: string): RouteMatch<T> | undefined {
86
+ return this.#matchSelf(method, path);
87
+ }
88
+
89
+ #select(): Router<T> {
90
+ if (this.#active) return this.#active;
91
+ const routes = this.#routes ?? this.#allRoutes;
92
+ if (routes.length === 0 && this.#allRoutes.length === 0) {
93
+ // Empty app — still pick the first factory so match() is defined.
94
+ }
95
+
96
+ for (const factory of this.#factories) {
97
+ const candidate = factory();
98
+ try {
99
+ for (const [method, path, value] of routes) {
100
+ candidate.add(method, path, value);
101
+ }
102
+ if ("build" in candidate && typeof candidate.build === "function") {
103
+ candidate.build();
104
+ }
105
+ this.#active = candidate;
106
+ this.name = `SmartRouter + ${candidate.name}`;
107
+ this.#routes = undefined;
108
+ this.match = candidate.match.bind(candidate);
109
+ return candidate;
110
+ } catch (e) {
111
+ if (e instanceof UnsupportedPathError) {
112
+ continue;
113
+ }
114
+ throw e;
115
+ }
116
+ }
117
+
118
+ throw new Error("No router candidate accepted the registered routes");
119
+ }
120
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Trie router — supports wildcards and optional-style rests the RegExp
3
+ * path cannot express. Used as SmartRouter fallback.
4
+ */
5
+
6
+ import type { RouteMatch, Router } from "./types.ts";
7
+
8
+ interface TrieNode<T> {
9
+ staticChildren: Map<string, TrieNode<T>>;
10
+ paramChild?: { name: string; node: TrieNode<T> };
11
+ wildcardChild?: TrieNode<T>;
12
+ value?: T;
13
+ }
14
+
15
+ function newTrieNode<T>(): TrieNode<T> {
16
+ return { staticChildren: new Map() };
17
+ }
18
+
19
+ /**
20
+ * Trie router — supports wildcards and optional-style rests the RegExp
21
+ * path cannot express. Used as SmartRouter fallback.
22
+ */
23
+ export class TrieRouter<T> implements Router<T> {
24
+ readonly name = "TrieRouter";
25
+ readonly #roots = new Map<string, TrieNode<T>>();
26
+
27
+ /**
28
+ * @param method - HTTP method
29
+ * @param path - Path pattern
30
+ * @param value - Route value
31
+ */
32
+ add(method: string, path: string, value: T): void {
33
+ let root = this.#roots.get(method);
34
+ if (!root) {
35
+ root = newTrieNode();
36
+ this.#roots.set(method, root);
37
+ }
38
+ const parts = path.startsWith("/") ? path.split("/").slice(1) : path.split("/");
39
+
40
+ let node = root;
41
+ for (const seg of parts) {
42
+ if (seg === "*") {
43
+ node.wildcardChild ??= newTrieNode();
44
+ node = node.wildcardChild;
45
+ continue;
46
+ }
47
+ if (seg.startsWith(":") && seg.length > 1) {
48
+ const name = seg.endsWith("?") ? seg.slice(1, -1) : seg.slice(1);
49
+ node.paramChild ??= { name, node: newTrieNode() };
50
+ node = node.paramChild.node;
51
+ continue;
52
+ }
53
+ let next = node.staticChildren.get(seg);
54
+ if (!next) {
55
+ next = newTrieNode();
56
+ node.staticChildren.set(seg, next);
57
+ }
58
+ node = next;
59
+ }
60
+ node.value = value;
61
+ }
62
+
63
+ /**
64
+ * @param method - HTTP method
65
+ * @param path - Pathname
66
+ */
67
+ match(method: string, path: string): RouteMatch<T> | undefined {
68
+ const root = this.#roots.get(method);
69
+ if (!root) return undefined;
70
+ const parts = path.startsWith("/") ? path.split("/").slice(1) : path.split("/");
71
+ // trailing slash: empty last segment
72
+ if (parts.length > 0 && parts[parts.length - 1] === "") {
73
+ parts.pop();
74
+ }
75
+ const params: Record<string, string> = {};
76
+ const found = this.#walk(root, parts, 0, params);
77
+ if (found === undefined) return undefined;
78
+ return { value: found, params };
79
+ }
80
+
81
+ #walk(
82
+ node: TrieNode<T>,
83
+ parts: readonly string[],
84
+ index: number,
85
+ params: Record<string, string>,
86
+ ): T | undefined {
87
+ if (index === parts.length) {
88
+ return node.value;
89
+ }
90
+ const seg = parts[index] ?? "";
91
+ const staticNext = node.staticChildren.get(seg);
92
+ if (staticNext) {
93
+ const hit = this.#walk(staticNext, parts, index + 1, params);
94
+ if (hit !== undefined) return hit;
95
+ }
96
+ if (node.paramChild) {
97
+ params[node.paramChild.name] = seg;
98
+ const hit = this.#walk(node.paramChild.node, parts, index + 1, params);
99
+ if (hit !== undefined) return hit;
100
+ delete params[node.paramChild.name];
101
+ }
102
+ if (node.wildcardChild) {
103
+ params["*"] = parts.slice(index).join("/");
104
+ return node.wildcardChild.value;
105
+ }
106
+ return undefined;
107
+ }
108
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Shared router types, errors, and RegExp-support probe.
3
+ */
4
+
5
+ /** Thrown when a router cannot express a path pattern. */
6
+ export class UnsupportedPathError extends Error {
7
+ /** Offending path pattern. */
8
+ readonly path: string;
9
+
10
+ /**
11
+ * @param path - Offending path pattern
12
+ */
13
+ constructor(path: string) {
14
+ super(`Unsupported path pattern: ${path}`);
15
+ this.name = "UnsupportedPathError";
16
+ this.path = path;
17
+ }
18
+ }
19
+
20
+ /** One match result. */
21
+ export interface RouteMatch<T> {
22
+ readonly value: T;
23
+ readonly params: Readonly<Record<string, string>>;
24
+ }
25
+
26
+ /** Router surface shared by all strategies. */
27
+ export interface Router<T> {
28
+ readonly name: string;
29
+ /**
30
+ * Register a route.
31
+ *
32
+ * @param method - HTTP method (uppercase)
33
+ * @param path - Path pattern
34
+ * @param value - Payload (usually a flow binding)
35
+ */
36
+ add(method: string, path: string, value: T): void;
37
+ /**
38
+ * Match a method + pathname.
39
+ *
40
+ * @param method - HTTP method
41
+ * @param path - Request pathname
42
+ */
43
+ match(method: string, path: string): RouteMatch<T> | undefined;
44
+ }
45
+
46
+ /** True when the pattern cannot be expressed by the RegExp strategy. */
47
+ export function isUnsupportedByRegExp(path: string): boolean {
48
+ // optional params, wildcards, custom regex segments, greedy rests
49
+ return /\/:/.test(path) === false ? /[*?{}()]/.test(path) : /[*?{}]|\/:[^/]+\?/.test(path);
50
+ }
51
+
52
+ /** Factory that produces a fresh router candidate. */
53
+ export type RouterFactory<T> = () => Router<T>;
54
+
55
+ /** Router preset names. */
56
+ export type RouterPreset = "default" | "edge";