@owlmeans/router 0.1.18-rc.33 → 0.1.18-rc.35

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.
package/README.md CHANGED
@@ -1,24 +1,196 @@
1
1
  # @owlmeans/router
2
2
 
3
- Internal router service for the OwlMeans entrypoint system.
4
-
5
- ## Overview
6
-
7
- - Provides the `RouterService` that attaches entrypoints to a runtime router (e.g. Express, Bun's HTTP server)
8
- - Used internally by `@owlmeans/server-route` and `@owlmeans/client-route`
9
- - Not typically used directly in application code
3
+ The plugin host for OwlMeans UI routing. It defines the `RouterService` facade (`outlet`,
4
+ `provider`, `useParams`, `useLocation`, `useNavigate`, `useSearchParams`, `compile`), keeps a
5
+ registry of `RouterPlugin`s and selects the active one by cascade, and ships the neutral route IR
6
+ (`RouteObject`) plus a pure, DOM-free route matcher. An app imports it for the router types and the
7
+ `ROUTER_SERVICE` alias, and a plugin author builds on it. It does not route anything by itself — a
8
+ browser app gets the default OwlMeans router from `@owlmeans/web-router` (registered by
9
+ `@owlmeans/web-client` / `@owlmeans/web-panel`) or opts into react-router v7 with
10
+ `@owlmeans/web-router-react-router`. It has nothing to do with server HTTP routing; that is
11
+ `@owlmeans/server-route`. The package's skills are `router` and `router-plugins`.
10
12
 
11
13
  ## Installation
12
14
 
13
15
  ```bash
14
- bun add @owlmeans/router@^0.1.18-rc.33
16
+ bun add @owlmeans/router@^0.1.18-rc.35
15
17
  ```
16
18
 
17
- ## Related Packages
19
+ `react` is a peer dependency — the package uses its component types only.
20
+
21
+ ## Concepts
22
+
23
+ - **Host** — `RouterService`, a lazy service registered under `ROUTER_SERVICE` (`'router-service'`).
24
+ Every facade call re-selects the active plugin and delegates to it.
25
+ - **Plugin** — a `RouterPlugin`: one routing mechanic (browser History, react-router, SSR, native)
26
+ with its own `compile`, provider, outlet and hooks. Plugins are keyed by `alias`; registering the
27
+ same alias again replaces the earlier one.
28
+ - **Cascade** — plugins are kept sorted by `priority` (descending, stable). `plugin(env?)` returns
29
+ the first whose `match(env, ctx)` is truthy (`match` omitted means "always applies") and throws
30
+ when none matches.
31
+ - **Environment** — `RouterEnv` (`{ hasWindow, ssr, request? }`) is what `match` sees.
32
+ `defaultRouterEnv()` derives it from `window`; an SSR host passes a per-request env.
33
+ - **Route IR** — `RouteObject` (`{ index?, path?, children?, Component? }`), shape-compatible with
34
+ react-router's route objects, produced by `@owlmeans/client` from the entrypoint tree and consumed
35
+ by every plugin.
36
+ - **Matcher** — `routeMatcherHelper`: flattens a route tree into branches, ranks them (static beats
37
+ `:param`, deeper beats shallower) and matches a pathname to a root→leaf chain with merged params.
38
+
39
+ ## Usage
40
+
41
+ ### Read routing state through the facade
42
+
43
+ In a client app the facade is also reachable as `context.router()`; the alias works anywhere a
44
+ context is at hand.
45
+
46
+ ```typescript
47
+ import { ROUTER_SERVICE } from '@owlmeans/router'
48
+ import type { RouterService } from '@owlmeans/router'
49
+
50
+ export const useProjectId = (ctx: AppContext): string | undefined => {
51
+ const router = ctx.service<RouterService>(ROUTER_SERVICE)
52
+ const { projectId } = router.useParams<{ projectId?: string }>()
53
+
54
+ return projectId
55
+ }
56
+ ```
57
+
58
+ ### Switch an app to another plugin
59
+
60
+ The default browser plugin registers at priority 0. A plugin with a higher priority wins the
61
+ cascade without removing it:
62
+
63
+ ```typescript
64
+ import { makeContext as makeBase } from '@owlmeans/web-panel'
65
+ import { appendReactRouter } from '@owlmeans/web-router-react-router'
66
+
67
+ export const makeContext = (cfg: AppConfig) => {
68
+ const context = makeBase(cfg)
69
+ appendReactRouter(context) // priority 100 — outranks the default OwlMeans router
70
+
71
+ return context
72
+ }
73
+ ```
74
+
75
+ ### Author a plugin
76
+
77
+ Reach the host with `ensureRouterService` (it registers an empty host when none exists yet), set a
78
+ priority above `DEFAULT_ROUTER_PRIORITY` and a `match` that fires only in your environment. The
79
+ matcher can be reused as is — it is pure and SSR-safe.
80
+
81
+ ```typescript
82
+ import { DEFAULT_ROUTER_PRIORITY, ensureRouterService, routeMatcherHelper } from '@owlmeans/router'
83
+ import type { LibraryRouter, RouterPlugin } from '@owlmeans/router'
84
+ import type { BasicConfig, BasicContext } from '@owlmeans/context'
85
+
86
+ export const makeSsrRouterPlugin = (): RouterPlugin => ({
87
+ alias: 'ssr-router',
88
+ priority: DEFAULT_ROUTER_PRIORITY + 50,
89
+ mode: 'ssr',
90
+ match: env => env.ssr,
91
+ compile: routes => ({
92
+ routes,
93
+ branches: routeMatcherHelper.rankRouteBranches(routeMatcherHelper.flattenRoutes(routes)),
94
+ }) as LibraryRouter,
95
+ provider: () => SsrRouterProvider,
96
+ outlet: () => SsrOutlet,
97
+ useParams: useSsrParams,
98
+ useLocation: useSsrLocation,
99
+ useNavigate: useSsrNavigate,
100
+ useSearchParams: useSsrSearchParams,
101
+ })
102
+
103
+ export const appendSsrRouter = <C extends BasicConfig, T extends BasicContext<C>>(ctx: T): T => {
104
+ ensureRouterService(ctx).registerPlugin(makeSsrRouterPlugin())
105
+
106
+ return ctx
107
+ }
108
+ ```
109
+
110
+ ### Match a pathname without a DOM
111
+
112
+ ```typescript
113
+ import { routeMatcherHelper } from '@owlmeans/router'
114
+ import type { RouteObject } from '@owlmeans/router'
115
+
116
+ const routes: RouteObject[] = [
117
+ { path: '/', children: [
118
+ { index: true },
119
+ { path: 'projects/:projectId', children: [{ index: true }, { path: 'stories/:storyId' }] },
120
+ ] },
121
+ ]
122
+
123
+ const branches = routeMatcherHelper.rankRouteBranches(routeMatcherHelper.flattenRoutes(routes))
124
+ const chain = routeMatcherHelper.matchRoutes(branches, '/projects/p1/stories/s7')
125
+ // chain: root → leaf RouteMatch[]; every level carries { projectId: 'p1', storyId: 's7' }
126
+ ```
127
+
128
+ ## API
129
+
130
+ ### Functions and helpers
131
+
132
+ | Symbol | Purpose |
133
+ |---|---|
134
+ | `makeRouterService(alias?)` | Build an empty host (lazy service, default alias `ROUTER_SERVICE`) |
135
+ | `ensureRouterService(ctx)` | Return the host on a context, registering an empty one if missing |
136
+ | `defaultRouterEnv()` | `{ hasWindow, ssr: !hasWindow }` from the global `window` |
137
+ | `createRouteMatcherHelper()` / `routeMatcherHelper` | The matcher factory and its shared instance |
138
+ | `routeMatcherHelper.splitPath(path?)` | Non-empty `/`-separated parts |
139
+ | `routeMatcherHelper.segmentsOf(path?)` | Static / `:param` / splat pattern segments |
140
+ | `routeMatcherHelper.flattenRoutes(routes)` | Route tree → `RouteBranch[]` (each with its root→node chain) |
141
+ | `routeMatcherHelper.rankRouteBranches(branches)` | Most specific first, stable within equal score |
142
+ | `routeMatcherHelper.matchRoutes(branches, pathname)` | `RouteMatch[]` chain or `null`; static segments match case-insensitively, params are URI-decoded |
143
+
144
+ ### `RouterService` members
145
+
146
+ | Member | Purpose |
147
+ |---|---|
148
+ | `registerPlugin(plugin)` | Add or replace (by alias) a plugin and re-sort by priority |
149
+ | `plugin(env?)` | The active plugin for `env` (default `defaultRouterEnv()`) |
150
+ | `outlet()`, `provider()` | Components of the active plugin |
151
+ | `useParams`, `useLocation`, `useNavigate`, `useSearchParams` | Hooks of the active plugin |
152
+ | `compile(routes)` | Compile a `RouteObject[]` into the active plugin's `LibraryRouter` |
153
+
154
+ ### Constants
155
+
156
+ | Symbol | Value | Purpose |
157
+ |---|---|---|
158
+ | `ROUTER_SERVICE`, `DEFAULT_ALIAS` | `'router-service'` | Host alias |
159
+ | `DEFAULT_ROUTER_PRIORITY` | `0` | Priority of the default plugin |
160
+ | `ROUTER_PLUGIN` | `'router-plugin'` | Config-record id for declaring a preferred router plugin |
161
+
162
+ ### Types
163
+
164
+ | Symbol | Purpose |
165
+ |---|---|
166
+ | `RouterService`, `RouterPlugin`, `RouterEnv` | Host, plugin contract, selection environment |
167
+ | `RouteObject`, `LibraryRouter`, `RouterProvider` | Route IR, opaque compiled router, the component rendering it |
168
+ | `RouteBranch`, `RouteMatch`, `PatternSegment`, `RouteParams` | Matcher output |
169
+ | `RouteMatcherHelper` | The matcher's interface |
170
+ | `UseParamsHook`, `UseLocationHook`, `UseNavigateHook`, `UseSearchParamsHook` | Hook signatures a plugin implements |
171
+ | `Location`, `Path`, `NavigateFunction`, `NavigateOptions`, `SetSearchParams` | Navigation value types |
172
+ | `ComponentType` | Re-exported from `react` |
173
+
174
+ ## Common pitfalls
175
+
176
+ - Splat (`*`) and optional (`:x?`) segments are not implemented — `matchRoutes` throws on a splat
177
+ branch. Keep route trees to static, `:param`, nested and index routes.
178
+ - Do not check `typeof window` inside a plugin's `match`; selection reads `RouterEnv`, which an SSR
179
+ host overrides per request.
180
+ - Keep the facade methods assignable plain properties when extending the host — a native host
181
+ replaces them by assignment.
182
+ - Keep the host lazy: plugin packages call `ensureRouterService` from `makeContext`, before the
183
+ context is initialized, and an eager service would throw there.
184
+ - Registering a plugin under an existing alias replaces it; use a distinct alias to add a second
185
+ mechanic.
186
+
187
+ ## Related packages
18
188
 
19
- - [`@owlmeans/server-route`](../server-route) — server-side router implementation
20
- - [`@owlmeans/client-route`](../client-route) — client-side router implementation
21
- - [`@owlmeans/entrypoint`](../entrypoint) — entrypoints registered with the router
189
+ - [`@owlmeans/web-router`](../web-router) — the default OwlMeans in-browser plugin (`appendWebRouter`)
190
+ - [`@owlmeans/web-router-react-router`](../web-router-react-router) — opt-in react-router v7 plugin (`appendReactRouter`)
191
+ - [`@owlmeans/client`](../client) — builds the `RouteObject` tree from entrypoints and exposes `context.router()`
192
+ - [`@owlmeans/client-route`](../client-route) — client-side route declarations
193
+ - [`@owlmeans/context`](../context) — the lazy service the host is built on
22
194
 
23
195
  <!-- owlmeans:agent-guidance:start -->
24
196
  ## Agent guidance
@@ -28,7 +200,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
28
200
  your project's skill store (`.agents/skills/`):
29
201
 
30
202
  ```sh
31
- npx @owlmeans/agent-skills@^0.1.18-rc.49
203
+ npx @owlmeans/agent-skills@^0.1.18-rc.57
32
204
  ```
33
205
 
34
206
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
- "schemaVersion": 2,
2
+ "schemaVersion": 3,
3
3
  "package": "@owlmeans/router",
4
- "version": "0.1.18-rc.33",
5
- "generatedAt": "2026-10-05T21:03:06.399Z",
4
+ "version": "0.1.18-rc.35",
5
+ "generatedAt": "2026-10-20T23:19:58.630Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -8,7 +8,7 @@ user-invocable: false
8
8
  # @owlmeans/router
9
9
 
10
10
  **Layer:** Core (L1)
11
- **Install:** `"@owlmeans/router": "^0.1.18-rc.33"` in `dependencies`
11
+ **Install:** `"@owlmeans/router": "^0.1.18-rc.35"` in `dependencies`
12
12
 
13
13
  `@owlmeans/router` is the **plugin host** for OwlMeans UI routing. It does not talk to any
14
14
  concrete router; it defines the contract, holds a registry of routing plugins, and selects the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/router",
3
- "version": "0.1.18-rc.33",
3
+ "version": "0.1.18-rc.35",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -21,7 +21,7 @@
21
21
  }
22
22
  },
23
23
  "dependencies": {
24
- "@owlmeans/context": "^0.1.18-rc.34"
24
+ "@owlmeans/context": "^0.1.18-rc.35"
25
25
  },
26
26
  "peerDependencies": {
27
27
  "react": "*"