@real-router/sources 0.2.3 → 0.2.4
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 +62 -147
- package/dist/cjs/index.js +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
# @real-router/sources
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@real-router/sources)
|
|
4
|
+
[](https://www.npmjs.com/package/@real-router/sources)
|
|
5
|
+
[](https://bundlejs.com/?q=@real-router/sources&treeshake=[*])
|
|
6
|
+
[](../../LICENSE)
|
|
5
7
|
|
|
6
|
-
Framework-agnostic subscription layer for Real-Router.
|
|
8
|
+
> Framework-agnostic subscription layer for [Real-Router](https://github.com/greydragon888/real-router). Reactive primitives compatible with `useSyncExternalStore` and vanilla JS.
|
|
9
|
+
|
|
10
|
+
Used internally by [`@real-router/react`](https://www.npmjs.com/package/@real-router/react). Use this package directly when building integrations for other frameworks or vanilla JS applications.
|
|
7
11
|
|
|
8
12
|
## Installation
|
|
9
13
|
|
|
10
14
|
```bash
|
|
11
15
|
npm install @real-router/sources
|
|
12
|
-
# or
|
|
13
|
-
pnpm add @real-router/sources
|
|
14
|
-
# or
|
|
15
|
-
yarn add @real-router/sources
|
|
16
|
-
# or
|
|
17
|
-
bun add @real-router/sources
|
|
18
16
|
```
|
|
19
17
|
|
|
18
|
+
**Peer dependency:** `@real-router/core`
|
|
19
|
+
|
|
20
20
|
## Quick Start
|
|
21
21
|
|
|
22
22
|
```typescript
|
|
@@ -25,200 +25,115 @@ import { createRouteSource } from "@real-router/sources";
|
|
|
25
25
|
|
|
26
26
|
const router = createRouter([
|
|
27
27
|
{ name: "home", path: "/" },
|
|
28
|
-
{ name: "users", path: "/users" },
|
|
29
|
-
{ name: "users.profile", path: "/:id" },
|
|
28
|
+
{ name: "users", path: "/users/:id" },
|
|
30
29
|
]);
|
|
31
30
|
|
|
32
|
-
router.start();
|
|
31
|
+
await router.start("/");
|
|
33
32
|
|
|
34
33
|
const source = createRouteSource(router);
|
|
35
|
-
|
|
36
|
-
// Subscribe to route changes
|
|
37
34
|
const unsubscribe = source.subscribe(() => {
|
|
38
35
|
console.log("Route:", source.getSnapshot().route?.name);
|
|
39
36
|
});
|
|
40
|
-
|
|
41
|
-
// Clean up when done
|
|
42
|
-
unsubscribe();
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## API
|
|
48
|
-
|
|
49
|
-
### `createRouteSource(router)`
|
|
50
|
-
|
|
51
|
-
Creates a source for the full router state. Subscribes to the router on the first listener and unsubscribes when all listeners are removed (lazy-connection pattern).\
|
|
52
|
-
`router: Router` — router instance\
|
|
53
|
-
Returns: `RouterSource<RouteSnapshot>`
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
const source = createRouteSource(router);
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
---
|
|
60
|
-
|
|
61
|
-
### `createRouteNodeSource(router, nodeName)`
|
|
62
|
-
|
|
63
|
-
Creates a source scoped to a specific route node. Only updates when the node is in the transition path, avoiding unnecessary re-renders for unrelated navigations. Uses a lazy-connection pattern: subscribes to the router on the first listener and unsubscribes when all listeners are removed.\
|
|
64
|
-
`router: Router` — router instance\
|
|
65
|
-
`nodeName: string` — route node name to scope updates to\
|
|
66
|
-
Returns: `RouterSource<RouteNodeSnapshot>`
|
|
67
|
-
|
|
68
|
-
```typescript
|
|
69
|
-
const source = createRouteNodeSource(router, "users");
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
---
|
|
73
|
-
|
|
74
|
-
### `createActiveRouteSource(router, routeName, params?, options?)`
|
|
75
|
-
|
|
76
|
-
Creates a source that tracks whether a specific route is active. Returns a boolean snapshot.\
|
|
77
|
-
`router: Router` — router instance\
|
|
78
|
-
`routeName: string` — route name to check\
|
|
79
|
-
`params?: Record<string, unknown>` — optional params to match\
|
|
80
|
-
`options?: ActiveRouteSourceOptions` — matching options\
|
|
81
|
-
Returns: `RouterSource<boolean>`
|
|
82
|
-
|
|
83
|
-
```typescript
|
|
84
|
-
const source = createActiveRouteSource(router, "users.profile", { id: "123" });
|
|
85
|
-
const source = createActiveRouteSource(router, "users", undefined, {
|
|
86
|
-
strict: false,
|
|
87
|
-
ignoreQueryParams: true,
|
|
88
|
-
});
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
Call `source.destroy()` when the source is no longer needed.
|
|
92
|
-
|
|
93
|
-
**Options:**
|
|
94
|
-
|
|
95
|
-
| Option | Type | Default | Description |
|
|
96
|
-
| ------------------- | --------- | ------- | ---------------------------------------------------------- |
|
|
97
|
-
| `strict` | `boolean` | `false` | When `true`, only matches the exact route, not descendants |
|
|
98
|
-
| `ignoreQueryParams` | `boolean` | `true` | When `true`, ignores query parameters when matching |
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
### `createTransitionSource(router)`
|
|
103
|
-
|
|
104
|
-
Creates a source that tracks the router's transition lifecycle. Updates on `TRANSITION_START`, `TRANSITION_SUCCESS`, `TRANSITION_ERROR`, and `TRANSITION_CANCEL` events. Unlike other sources, this uses eager subscription (subscribes to events immediately, not lazily on first listener).\
|
|
105
|
-
`router: Router` — router instance\
|
|
106
|
-
Returns: `RouterSource<RouterTransitionSnapshot>`
|
|
107
|
-
|
|
108
|
-
```typescript
|
|
109
|
-
const source = createTransitionSource(router);
|
|
110
|
-
|
|
111
|
-
source.subscribe(() => {
|
|
112
|
-
const { isTransitioning, toRoute, fromRoute } = source.getSnapshot();
|
|
113
|
-
if (isTransitioning) {
|
|
114
|
-
console.log(`Navigating: ${fromRoute?.name} → ${toRoute?.name}`);
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
37
|
```
|
|
118
38
|
|
|
119
|
-
|
|
39
|
+
## Source Factories
|
|
120
40
|
|
|
121
|
-
|
|
41
|
+
| Factory | Snapshot | Updates when |
|
|
42
|
+
|---------|----------|--------------|
|
|
43
|
+
| `createRouteSource(router)` | `{ route, previousRoute }` | Every navigation |
|
|
44
|
+
| `createRouteNodeSource(router, node)` | `{ route, previousRoute }` | Only when node activates/deactivates |
|
|
45
|
+
| `createActiveRouteSource(router, name, params?, opts?)` | `boolean` | Route active status changes |
|
|
46
|
+
| `createTransitionSource(router)` | `{ isTransitioning, toRoute, fromRoute }` | Transition start/end/cancel/error |
|
|
122
47
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
All four factories return a `RouterSource<T>`:
|
|
48
|
+
All factories return a `RouterSource<T>`:
|
|
126
49
|
|
|
127
50
|
```typescript
|
|
128
51
|
interface RouterSource<T> {
|
|
129
|
-
subscribe(listener: () => void): () => void;
|
|
130
|
-
getSnapshot(): T;
|
|
131
|
-
destroy(): void;
|
|
52
|
+
subscribe(listener: () => void): () => void; // useSyncExternalStore-compatible
|
|
53
|
+
getSnapshot(): T; // current value, synchronous
|
|
54
|
+
destroy(): void; // teardown, remove router subscription
|
|
132
55
|
}
|
|
133
56
|
```
|
|
134
57
|
|
|
135
|
-
|
|
136
|
-
`getSnapshot` — returns the current snapshot synchronously.\
|
|
137
|
-
`destroy` — tears down the source and removes the router subscription.
|
|
58
|
+
### Lazy vs Eager Subscription
|
|
138
59
|
|
|
139
|
-
|
|
60
|
+
- `createRouteSource`, `createRouteNodeSource`, `createActiveRouteSource` — **lazy**: subscribe to the router on first listener, unsubscribe when all removed
|
|
61
|
+
- `createTransitionSource` — **eager**: subscribes immediately (needs to track `TRANSITION_START`)
|
|
140
62
|
|
|
141
|
-
|
|
63
|
+
### `createActiveRouteSource` Options
|
|
142
64
|
|
|
143
65
|
```typescript
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
RouterTransitionSnapshot,
|
|
149
|
-
ActiveRouteSourceOptions,
|
|
150
|
-
} from "@real-router/sources";
|
|
66
|
+
const source = createActiveRouteSource(router, "users", undefined, {
|
|
67
|
+
strict: false, // default: false — match descendants too
|
|
68
|
+
ignoreQueryParams: true, // default: true
|
|
69
|
+
});
|
|
151
70
|
```
|
|
152
71
|
|
|
153
|
-
`RouteSnapshot` — full router state: `{ route: State | undefined, previousRoute: State | undefined }`\
|
|
154
|
-
`RouteNodeSnapshot` — node-scoped state: `{ route: State | undefined, previousRoute: State | undefined }`\
|
|
155
|
-
`RouterTransitionSnapshot` — transition state: `{ isTransitioning: boolean, toRoute: State | null, fromRoute: State | null }`\
|
|
156
|
-
`ActiveRouteSourceOptions` — options for `createActiveRouteSource`: `{ strict?: boolean, ignoreQueryParams?: boolean }`\
|
|
157
|
-
`RouterSource<T>` — the source interface returned by all four factories
|
|
158
|
-
|
|
159
|
-
---
|
|
160
|
-
|
|
161
72
|
## Usage Examples
|
|
162
73
|
|
|
163
74
|
### With React (`useSyncExternalStore`)
|
|
164
75
|
|
|
165
|
-
```
|
|
76
|
+
```tsx
|
|
166
77
|
import { useSyncExternalStore } from "react";
|
|
167
78
|
import { createRouteSource } from "@real-router/sources";
|
|
168
79
|
|
|
169
80
|
const source = createRouteSource(router);
|
|
170
81
|
|
|
171
82
|
function CurrentRoute() {
|
|
172
|
-
const { route } = useSyncExternalStore(
|
|
173
|
-
|
|
174
|
-
source.getSnapshot,
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
return <p>Current route: {route.name}</p>;
|
|
83
|
+
const { route } = useSyncExternalStore(source.subscribe, source.getSnapshot);
|
|
84
|
+
return <p>Current route: {route?.name}</p>;
|
|
178
85
|
}
|
|
179
86
|
```
|
|
180
87
|
|
|
181
88
|
### With Vanilla JS
|
|
182
89
|
|
|
183
90
|
```typescript
|
|
184
|
-
import {
|
|
91
|
+
import { createRouteNodeSource } from "@real-router/sources";
|
|
185
92
|
|
|
186
|
-
|
|
93
|
+
// Only fires when navigating within the "users" subtree
|
|
94
|
+
const source = createRouteNodeSource(router, "users");
|
|
187
95
|
|
|
188
96
|
const unsubscribe = source.subscribe(() => {
|
|
189
|
-
const { route
|
|
190
|
-
console.log("
|
|
97
|
+
const { route } = source.getSnapshot();
|
|
98
|
+
console.log("Users section:", route?.name);
|
|
191
99
|
});
|
|
192
100
|
|
|
193
|
-
//
|
|
194
|
-
unsubscribe();
|
|
101
|
+
unsubscribe(); // automatically unsubscribes from router
|
|
195
102
|
```
|
|
196
103
|
|
|
197
|
-
###
|
|
104
|
+
### Transition Tracking
|
|
198
105
|
|
|
199
106
|
```typescript
|
|
200
|
-
import {
|
|
107
|
+
import { createTransitionSource } from "@real-router/sources";
|
|
201
108
|
|
|
202
|
-
|
|
203
|
-
const source = createRouteNodeSource(router, "users");
|
|
109
|
+
const source = createTransitionSource(router);
|
|
204
110
|
|
|
205
|
-
|
|
206
|
-
const {
|
|
207
|
-
|
|
111
|
+
source.subscribe(() => {
|
|
112
|
+
const { isTransitioning, toRoute, fromRoute } = source.getSnapshot();
|
|
113
|
+
if (isTransitioning) {
|
|
114
|
+
showSpinner();
|
|
115
|
+
} else {
|
|
116
|
+
hideSpinner();
|
|
117
|
+
}
|
|
208
118
|
});
|
|
209
|
-
|
|
210
|
-
// Later, clean up (automatically unsubscribes from router)
|
|
211
|
-
unsubscribe();
|
|
212
119
|
```
|
|
213
120
|
|
|
214
|
-
|
|
121
|
+
## Documentation
|
|
122
|
+
|
|
123
|
+
Full documentation: [Wiki — sources](https://github.com/greydragon888/real-router/wiki/sources-package)
|
|
215
124
|
|
|
216
125
|
## Related Packages
|
|
217
126
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
127
|
+
| Package | Description |
|
|
128
|
+
|---------|-------------|
|
|
129
|
+
| [@real-router/core](https://www.npmjs.com/package/@real-router/core) | Core router (required dependency) |
|
|
130
|
+
| [@real-router/react](https://www.npmjs.com/package/@real-router/react) | React integration (uses sources internally) |
|
|
131
|
+
| [@real-router/rx](https://www.npmjs.com/package/@real-router/rx) | Observable API (`state$`, `events$`) |
|
|
132
|
+
|
|
133
|
+
## Contributing
|
|
134
|
+
|
|
135
|
+
See [contributing guidelines](../../CONTRIBUTING.md) for development setup and PR process.
|
|
221
136
|
|
|
222
137
|
## License
|
|
223
138
|
|
|
224
|
-
MIT © [Oleg Ivanov](https://github.com/greydragon888)
|
|
139
|
+
[MIT](../../LICENSE) © [Oleg Ivanov](https://github.com/greydragon888)
|
package/dist/cjs/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var e=require("@real-router/route-utils"),t=require("@real-router/core"),s=require("@real-router/core/api"),r=class{#e;#t=!1;#s=new Set;#r;#o;#n;constructor(e,t){this.#e=e,this.#r=t?.onFirstSubscribe,this.#o=t?.onLastUnsubscribe,this.#n=t?.onDestroy,this.subscribe=this.subscribe.bind(this),this.getSnapshot=this.getSnapshot.bind(this),this.destroy=this.destroy.bind(this)}subscribe(e){return this.#t?()=>{}:(0===this.#s.size&&this.#r&&this.#r(),this.#s.add(e),()=>{this.#s.delete(e),!this.#t&&0===this.#s.size&&this.#o&&this.#o()})}getSnapshot(){return this.#e}updateSnapshot(e){this.#t||(this.#e=e,this.#s.forEach(e=>{e()}))}destroy(){this.#t||(this.#t=!0,this.#n?.(),this.#s.clear())}};function o(e,t,s,r){const o=r?.route??t.getState(),n=r?.previousRoute,i=""===s||void 0!==o&&(o.name===s||o.name.startsWith(`${s}.`))?o:void 0;return i===e.route&&n===e.previousRoute?e:{route:i,previousRoute:n}}var n=new WeakMap,i={isTransitioning:!1,toRoute:null,fromRoute:null};exports.createActiveRouteSource=function(t,s,o,n){const i=n?.strict??!1,u=n?.ignoreQueryParams??!0,a=t.isActiveRoute(s,o,i,u),c=new r(a,{onDestroy:()=>{h()}}),h=t.subscribe(r=>{const n=e.areRoutesRelated(s,r.route.name),a=r.previousRoute&&e.areRoutesRelated(s,r.previousRoute.name);if(!n&&!a)return;const h=!!n&&t.isActiveRoute(s,o,i,u);Object.is(c.getSnapshot(),h)||c.updateSnapshot(h)});return c},exports.createRouteNodeSource=function(e,t){let s=null;const i=function(e,t){let s=n.get(e);s||(s=new Map,n.set(e,s));let r=s.get(t);return r||(r=e.shouldUpdateNode(t),s.set(t,r)),r}(e,t),u=()=>{const e=s;s=null,e?.()},a=new r(o({route:void 0,previousRoute:void 0},e,t),{onFirstSubscribe:()=>{a.updateSnapshot(o(a.getSnapshot(),e,t)),s=e.subscribe(s=>{if(!i(s.route,s.previousRoute))return;const r=o(a.getSnapshot(),e,t,s);Object.is(a.getSnapshot(),r)||a.updateSnapshot(r)})},onLastUnsubscribe:u,onDestroy:u});return a},exports.createRouteSource=function(e){let t=null;const s=()=>{const e=t;t=null,e?.()},o=new r({route:e.getState(),previousRoute:void 0},{onFirstSubscribe:()=>{t=e.subscribe(e=>{o.updateSnapshot({route:e.route,previousRoute:e.previousRoute})})},onLastUnsubscribe:s,onDestroy:s});return o},exports.createTransitionSource=function(e){const o=new r(i,{onDestroy:()=>{a.forEach(e=>{e()})}}),n=s.getPluginApi(e),u=()=>{o.updateSnapshot(i)},a=[n.addEventListener(t.events.TRANSITION_START,(e,t)=>{o.updateSnapshot({isTransitioning:!0,toRoute:e,fromRoute:t??null})}),n.addEventListener(t.events.TRANSITION_SUCCESS,u),n.addEventListener(t.events.TRANSITION_ERROR,u),n.addEventListener(t.events.TRANSITION_CANCEL,u)];return o};//# sourceMappingURL=index.js.map
|
|
1
|
+
"use strict";var e=require("@real-router/route-utils"),t=require("@real-router/core"),s=require("@real-router/core/api"),r=class{#e;#t=!1;#s=new Set;#r;#o;#n;constructor(e,t){this.#e=e,this.#r=t?.onFirstSubscribe,this.#o=t?.onLastUnsubscribe,this.#n=t?.onDestroy,this.subscribe=this.subscribe.bind(this),this.getSnapshot=this.getSnapshot.bind(this),this.destroy=this.destroy.bind(this)}subscribe(e){return this.#t?()=>{}:(0===this.#s.size&&this.#r&&this.#r(),this.#s.add(e),()=>{this.#s.delete(e),!this.#t&&0===this.#s.size&&this.#o&&this.#o()})}getSnapshot(){return this.#e}updateSnapshot(e){this.#t||(this.#e=e,this.#s.forEach(e=>{e()}))}destroy(){this.#t||(this.#t=!0,this.#n?.(),this.#s.clear())}};function o(e,t,s,r){const o=r?.route??t.getState(),n=r?.previousRoute,i=""===s||void 0!==o&&(o.name===s||o.name.startsWith(`${s}.`))?o:void 0;return i===e.route&&n===e.previousRoute?e:{route:i,previousRoute:n}}var n=new WeakMap,i={isTransitioning:!1,toRoute:null,fromRoute:null};exports.createActiveRouteSource=function(t,s,o,n){const i=n?.strict??!1,u=n?.ignoreQueryParams??!0,a=t.isActiveRoute(s,o,i,u),c=new r(a,{onDestroy:()=>{h()}}),h=t.subscribe(r=>{const n=e.areRoutesRelated(s,r.route.name),a=r.previousRoute&&e.areRoutesRelated(s,r.previousRoute.name);if(!n&&!a)return;const h=!!n&&t.isActiveRoute(s,o,i,u);Object.is(c.getSnapshot(),h)||c.updateSnapshot(h)});return c},exports.createRouteNodeSource=function(e,t){let s=null;const i=function(e,t){let s=n.get(e);s||(s=new Map,n.set(e,s));let r=s.get(t);return r||(r=e.shouldUpdateNode(t),s.set(t,r)),r}(e,t),u=()=>{const e=s;s=null,e?.()},a=new r(o({route:void 0,previousRoute:void 0},e,t),{onFirstSubscribe:()=>{a.updateSnapshot(o(a.getSnapshot(),e,t)),s=e.subscribe(s=>{if(!i(s.route,s.previousRoute))return;const r=o(a.getSnapshot(),e,t,s);Object.is(a.getSnapshot(),r)||a.updateSnapshot(r)})},onLastUnsubscribe:u,onDestroy:u});return a},exports.createRouteSource=function(e){let t=null;const s=()=>{const e=t;t=null,e?.()},o=new r({route:e.getState(),previousRoute:void 0},{onFirstSubscribe:()=>{t=e.subscribe(e=>{o.updateSnapshot({route:e.route,previousRoute:e.previousRoute})})},onLastUnsubscribe:s,onDestroy:s});return o},exports.createTransitionSource=function(e){const o=new r(i,{onDestroy:()=>{a.forEach(e=>{e()})}}),n=s.getPluginApi(e),u=()=>{o.updateSnapshot(i)},a=[n.addEventListener(t.events.TRANSITION_START,(e,t)=>{o.updateSnapshot({isTransitioning:!0,toRoute:e,fromRoute:t??null})}),n.addEventListener(t.events.TRANSITION_SUCCESS,u),n.addEventListener(t.events.TRANSITION_ERROR,u),n.addEventListener(t.events.TRANSITION_CANCEL,u)];return o};//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@real-router/sources",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"description": "Framework-agnostic subscription layer for Real-Router state",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"homepage": "https://github.com/greydragon888/real-router",
|
|
44
44
|
"sideEffects": false,
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@real-router/core": "^0.
|
|
47
|
-
"@real-router/route-utils": "^0.1.
|
|
46
|
+
"@real-router/core": "^0.37.0",
|
|
47
|
+
"@real-router/route-utils": "^0.1.5"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"mitata": "1.0.34"
|