@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 +185 -13
- package/agent-meta/manifest.json +3 -3
- package/agent-meta/skills/router/SKILL.md +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,24 +1,196 @@
|
|
|
1
1
|
# @owlmeans/router
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
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.
|
|
16
|
+
bun add @owlmeans/router@^0.1.18-rc.35
|
|
15
17
|
```
|
|
16
18
|
|
|
17
|
-
|
|
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/
|
|
20
|
-
- [`@owlmeans/
|
|
21
|
-
- [`@owlmeans/
|
|
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.
|
|
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
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"schemaVersion":
|
|
2
|
+
"schemaVersion": 3,
|
|
3
3
|
"package": "@owlmeans/router",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-10-
|
|
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.
|
|
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.
|
|
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.
|
|
24
|
+
"@owlmeans/context": "^0.1.18-rc.35"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"react": "*"
|