intentx-react-router 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 delpikye-v
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,325 @@
1
+ # 🕹️ intentx-react-router
2
+
3
+ [![NPM](https://img.shields.io/npm/v/intentx-react-router.svg)](https://www.npmjs.com/package/intentx-react-router) ![Downloads](https://img.shields.io/npm/dt/intentx-react-router.svg)
4
+
5
+ <a href="https://codesandbox.io/p/sandbox/q8gg5s" target="_blank">LIVE EXAMPLE</a>
6
+
7
+ Intent‑based navigation layer for React applications.
8
+
9
+ Built on top of **React Router** and designed to work nicely with
10
+ event‑driven architectures like **eventbus-z**.
11
+
12
+ > Navigate by **intent**, not by URL.
13
+
14
+ ---
15
+
16
+ # Why intentx-react-router?
17
+
18
+ Traditional routing:
19
+ ```ts
20
+ navigate("/users/42/edit")
21
+ ```
22
+
23
+ Intent Router:
24
+ ```ts
25
+ navigateIntent("edit-user", 42)
26
+ ```
27
+
28
+ <b>Benefits:</b>
29
+
30
+ - 🧠 Intent‑driven navigation
31
+ - 🔗 URL structure fully decoupled from UI
32
+ - 🔧 Easy URL refactor
33
+ - 🧩 Micro‑frontend friendly
34
+ - 📡 Event‑driven navigation support
35
+ - 🪶 Lightweight core (\~200 lines)
36
+
37
+ ---
38
+
39
+ # Installation
40
+
41
+ ```bash
42
+ npm install intentx-react-router eventbus-z react-router
43
+ ```
44
+
45
+ ---
46
+
47
+ # Basic Setup
48
+
49
+ ## Define intents
50
+
51
+ ``` ts
52
+ import { createIntentRouter } from "intentx-react-router"
53
+
54
+ createIntentRouter({
55
+ "view-user": "/users/:userId/name/:page",
56
+ "edit-user": "/users/:userId/edit",
57
+ "open-cart": "/cart",
58
+ "checkout": "/checkout"
59
+ })
60
+ ```
61
+
62
+ ---
63
+
64
+ ## Bind React Router navigate
65
+
66
+ ``` ts
67
+ import { bindNavigate } from "intentx-react-router"
68
+
69
+ import { useNavigate } from "react-router"
70
+
71
+ function RouterBinder() {
72
+ const navigate = useNavigate()
73
+
74
+ bindNavigate(navigate)
75
+
76
+ return null
77
+ }
78
+ ```
79
+
80
+ ---
81
+
82
+ # Navigation
83
+
84
+ ## Object params
85
+
86
+ ``` ts
87
+ navigateIntent("view-user", {
88
+ userId: 1,
89
+ page: 2
90
+ })
91
+ ```
92
+
93
+ Result:
94
+
95
+ ```ts
96
+ /users/1/name/2
97
+ ```
98
+
99
+ ---
100
+
101
+ ## Array params
102
+
103
+ ``` ts
104
+ navigateIntent("view-user", [1,2])
105
+ ```
106
+
107
+ Params automatically map to:
108
+ ```ts
109
+ :userId
110
+ :page
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Single param shortcut
116
+
117
+ ``` ts
118
+ navigateIntent("edit-user", 1)
119
+ ```
120
+
121
+ Result:
122
+
123
+ ```text
124
+ /users/1/edit
125
+ ```
126
+
127
+ ---
128
+
129
+ # React Helpers
130
+
131
+ ## useNavigateIntent
132
+
133
+ ``` ts
134
+ import { useNavigateIntent } from "intentx-react-router"
135
+
136
+ function Button() {
137
+ const navigateIntent = useNavigateIntent()
138
+
139
+ return (
140
+ <button onClick={() => navigateIntent("checkout")}>
141
+ Checkout
142
+ </button>
143
+ )
144
+ }
145
+ ```
146
+
147
+ ---
148
+
149
+ ## IntentLink
150
+
151
+ ``` tsx
152
+ import { IntentLink } from "intentx-react-router"
153
+
154
+ <IntentLink intent="view-user" params={[1,2]}>
155
+ Open User
156
+ </IntentLink>
157
+ ```
158
+
159
+ Equivalent to:
160
+
161
+ ```ts
162
+ <Link to="/users/1/name/2" />
163
+ ```
164
+
165
+ ---
166
+
167
+ ## useIntent
168
+
169
+ Reads intent + params from URL.
170
+
171
+ ``` ts
172
+ import { useIntent } from "intentx-react-router"
173
+
174
+ function Page() {
175
+ const { intent, params } = useIntent()
176
+
177
+ console.log(intent)
178
+ console.log(params)
179
+
180
+ return null
181
+ }
182
+ ```
183
+
184
+ Example result:
185
+
186
+ ```ts
187
+ {
188
+ intent: "view-user",
189
+ params: {
190
+ userId: "1",
191
+ page: "2"
192
+ }
193
+ }
194
+ ```
195
+
196
+ ---
197
+
198
+ ## IntentRoute
199
+
200
+ Intent‑aware route wrapper.
201
+
202
+ ```tsx
203
+ import { IntentRoute } from "intentx-react-router"
204
+
205
+ <IntentRoute
206
+ intent="view-user"
207
+ component={UserPage}
208
+ />
209
+ ```
210
+
211
+ ---
212
+
213
+ # Guards
214
+
215
+ Global guard:
216
+
217
+ ```ts
218
+ addIntentGuard((intent, params) => {
219
+
220
+ if (!isLoggedIn && intent === "checkout") {
221
+ return "/login"
222
+ }
223
+
224
+ })
225
+ ```
226
+
227
+ Per‑intent guard:
228
+
229
+ ```ts
230
+ addIntentGuardFor("checkout", () => {
231
+
232
+ if (!cartReady) {
233
+ return "/cart"
234
+ }
235
+
236
+ })
237
+ ```
238
+
239
+ ---
240
+
241
+ # Preload
242
+
243
+ Preload logic before navigation.
244
+
245
+ ```ts
246
+ addIntentPreload("checkout", () => {
247
+ fetchCart()
248
+ })
249
+ ```
250
+
251
+ Manual preload:
252
+
253
+ ```ts
254
+ preloadIntent("checkout")
255
+ ```
256
+
257
+ ---
258
+
259
+ # Reverse Routing
260
+
261
+ Generate path manually.
262
+
263
+ ``` ts
264
+ generatePathFromIntent("view-user", [1,2])
265
+ ```
266
+
267
+ Result:
268
+ ```ts
269
+ /users/1/name/2
270
+ ```
271
+ ---
272
+
273
+ # Resolve Intent From URL
274
+
275
+ ``` ts
276
+ resolveIntentFromUrl("/users/1/name/2")
277
+ ```
278
+
279
+ Result:
280
+ ```ts
281
+ {
282
+ intent: "view-user",
283
+ params: {
284
+ userId: "1",
285
+ page: "2"
286
+ }
287
+ }
288
+ ```
289
+
290
+ ---
291
+
292
+ # Comparison
293
+
294
+ | Criteria | intent-router | React Router |
295
+ |-------------------------|---------------|--------------|
296
+ | Intent-based navigation | ✅ | ❌ |
297
+ | URL refactor safety | ✅ | ⚠️ |
298
+ | Event-driven navigation | ✅ | ❌ |
299
+ | Micro-frontend friendly | ✅ | ⚠️ |
300
+ | Reverse routing | ✅ | ❌ |
301
+ | Type-safe intent params | ✅ | ⚠️ |
302
+
303
+ ---
304
+
305
+ # Architecture
306
+
307
+ ```text
308
+ Component
309
+
310
+ navigateIntent()
311
+
312
+ eventbus-z
313
+
314
+ Intent Router
315
+
316
+ React Router navigate()
317
+
318
+ URL
319
+ ```
320
+
321
+ ---
322
+
323
+ # License
324
+
325
+ MIT
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+ type Props = {
3
+ intent: string;
4
+ component: React.ComponentType<any>;
5
+ };
6
+ export declare function IntentRoute({ intent, component: Component }: Props): JSX.Element | null;
7
+ export {};
@@ -0,0 +1,2 @@
1
+ export * from "./intentCore";
2
+ export * from "./IntentRoute";
@@ -0,0 +1,17 @@
1
+ type IntentMap = Record<string, string>;
2
+ type GuardFn = (intent: string, params: any) => boolean | string | void;
3
+ type NavigateFn = (path: string) => void;
4
+ type PreloadFn = () => Promise<any> | void;
5
+ export declare function createIntentRouter(map: IntentMap): void;
6
+ export declare function bindNavigate(fn: NavigateFn): void;
7
+ export declare function navigateIntent(intent: string, params?: Record<string, any> | any[] | string | number): void;
8
+ export declare function addIntentGuard(fn: GuardFn): void;
9
+ export declare function addIntentGuardFor(intent: string, fn: GuardFn): void;
10
+ export declare function addIntentPreload(intent: string, fn: PreloadFn): void;
11
+ export declare function preloadIntent(intent: string): void;
12
+ export declare function generatePathFromIntent(intent: string, params?: Record<string, any> | any[] | string | number): string;
13
+ export declare function resolveIntentFromUrl(url: string): {
14
+ intent: string;
15
+ params: Record<string, any>;
16
+ } | null;
17
+ export {};
@@ -0,0 +1,2 @@
1
+ export * from "./core";
2
+ export * from "./router";
@@ -0,0 +1,2 @@
1
+ import{createEventBus as n}from"eventbus-z";import{jsx as t}from"react/jsx-runtime";import{useLocation as r,Link as o,useNavigate as e}from"react-router";import{useEffect as i}from"react";const c=n(),u="INTENT_NAVIGATE";let s={},f=null,a=!1;const l=[],p={},m={};function h(n){s=n}function v(n){f=n,a||(c.$onMultiple(u,E),a=!0)}function d(n,t){c.$emit(u,{intent:n,params:t})}function g(n){l.push(n)}function y(n,t){p[n]||(p[n]=[]),p[n].push(t)}function A(n,t){m[n]||(m[n]=[]),m[n].push(t)}function w(n){z(n)}function E(n){const t=s[n.intent];if(!t)return void console.warn("Unknown intent:",n.intent);const r=$(t,n.params);for(const t of l){const o=t(n.intent,r);if(!1===o)return;if("string"==typeof o)return void(null==f||f(o))}const o=p[n.intent]||[];for(const t of o){const o=t(n.intent,r);if(!1===o)return;if("string"==typeof o)return void(null==f||f(o))}z(n.intent);const e=_(t,r);null==f||f(e)}function z(n){const t=m[n];if(t)for(const n of t)try{const t=n();t instanceof Promise&&t.catch(()=>{})}catch{}}function $(n,t){const r=function(n){const t=[];return n.replace(/:([A-Za-z0-9_]+)/g,(n,r)=>(t.push(r),"")),t}(n);if(!t)return{};if(Array.isArray(t)){const n={};return r.forEach((r,o)=>{void 0===t[o]&&console.warn(`Missing param "${r}"`),n[r]=t[o]}),n}return"object"==typeof t?t:1===r.length?{[r[0]]:t}:{}}function _(n,t){return n.replace(/:([A-Za-z0-9_]+)/g,(n,r)=>{var o;return null!==(o=t[r])&&void 0!==o?o:""})}function x(n,t){const r=s[n];if(!r)return"";return _(r,$(r,t))}function N(n){for(const t in s){const r=s[t],o=[],e=new RegExp("^"+r.replace(/:([A-Za-z0-9_]+)/g,(n,t)=>(o.push(t),"([^/]+)"))+"$"),i=n.match(e);if(!i)continue;const c={};return o.forEach((n,t)=>{c[n]=i[t+1]}),{intent:t,params:c}}return null}function P(){var n,t;const o=r(),e=N(o.pathname),i=new URLSearchParams(o.search),c={};return i.forEach((n,t)=>{c[t]=n}),{intent:null!==(n=null==e?void 0:e.intent)&&void 0!==n?n:null,params:null!==(t=null==e?void 0:e.params)&&void 0!==t?t:{},query:c}}function R({intent:n,component:r}){const o=P();return o.intent!==n?null:t(r,{...o})}function S({intent:n,params:r,query:e,children:i}){let c=x(n,r);if(e){const n=new URLSearchParams(e).toString();n&&(c+="?"+n)}return t(o,{to:c,children:i})}function T(){const n=e();return i(()=>{v(n)},[n]),d}export{S as IntentLink,R as IntentRoute,g as addIntentGuard,y as addIntentGuardFor,A as addIntentPreload,v as bindNavigate,h as createIntentRouter,x as generatePathFromIntent,d as navigateIntent,w as preloadIntent,N as resolveIntentFromUrl,P as useIntent,T as useNavigateIntent};
2
+ //# sourceMappingURL=index.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
package/build/index.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";var n=require("eventbus-z"),t=require("react/jsx-runtime"),e=require("react-router"),r=require("react");const o=n.createEventBus(),i="INTENT_NAVIGATE";let u={},s=null,c=!1;const a=[],f={},l={};function p(n){s=n,c||(o.$onMultiple(i,h),c=!0)}function d(n,t){o.$emit(i,{intent:n,params:t})}function h(n){const t=u[n.intent];if(!t)return void console.warn("Unknown intent:",n.intent);const e=m(t,n.params);for(const t of a){const r=t(n.intent,e);if(!1===r)return;if("string"==typeof r)return void(null==s||s(r))}const r=f[n.intent]||[];for(const t of r){const r=t(n.intent,e);if(!1===r)return;if("string"==typeof r)return void(null==s||s(r))}v(n.intent);const o=x(t,e);null==s||s(o)}function v(n){const t=l[n];if(t)for(const n of t)try{const t=n();t instanceof Promise&&t.catch(()=>{})}catch{}}function m(n,t){const e=function(n){const t=[];return n.replace(/:([A-Za-z0-9_]+)/g,(n,e)=>(t.push(e),"")),t}(n);if(!t)return{};if(Array.isArray(t)){const n={};return e.forEach((e,r)=>{void 0===t[r]&&console.warn(`Missing param "${e}"`),n[e]=t[r]}),n}return"object"==typeof t?t:1===e.length?{[e[0]]:t}:{}}function x(n,t){return n.replace(/:([A-Za-z0-9_]+)/g,(n,e)=>{var r;return null!==(r=t[e])&&void 0!==r?r:""})}function g(n,t){const e=u[n];if(!e)return"";return x(e,m(e,t))}function I(n){for(const t in u){const e=u[t],r=[],o=new RegExp("^"+e.replace(/:([A-Za-z0-9_]+)/g,(n,t)=>(r.push(t),"([^/]+)"))+"$"),i=n.match(o);if(!i)continue;const s={};return r.forEach((n,t)=>{s[n]=i[t+1]}),{intent:t,params:s}}return null}function y(){var n,t;const r=e.useLocation(),o=I(r.pathname),i=new URLSearchParams(r.search),u={};return i.forEach((n,t)=>{u[t]=n}),{intent:null!==(n=null==o?void 0:o.intent)&&void 0!==n?n:null,params:null!==(t=null==o?void 0:o.params)&&void 0!==t?t:{},query:u}}exports.IntentLink=function({intent:n,params:r,query:o,children:i}){let u=g(n,r);if(o){const n=new URLSearchParams(o).toString();n&&(u+="?"+n)}return t.jsx(e.Link,{to:u,children:i})},exports.IntentRoute=function({intent:n,component:e}){const r=y();return r.intent!==n?null:t.jsx(e,{...r})},exports.addIntentGuard=function(n){a.push(n)},exports.addIntentGuardFor=function(n,t){f[n]||(f[n]=[]),f[n].push(t)},exports.addIntentPreload=function(n,t){l[n]||(l[n]=[]),l[n].push(t)},exports.bindNavigate=p,exports.createIntentRouter=function(n){u=n},exports.generatePathFromIntent=g,exports.navigateIntent=d,exports.preloadIntent=function(n){v(n)},exports.resolveIntentFromUrl=I,exports.useIntent=y,exports.useNavigateIntent=function(){const n=e.useNavigate();return r.useEffect(()=>{p(n)},[n]),d};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ type Props = {
3
+ intent: string;
4
+ params?: any;
5
+ query?: Record<string, any>;
6
+ children: React.ReactNode;
7
+ };
8
+ export declare function IntentLink({ intent, params, query, children }: Props): JSX.Element;
9
+ export {};
@@ -0,0 +1,3 @@
1
+ export * from "./IntentLink";
2
+ export * from "./useIntent";
3
+ export * from "./useNavigateIntent";
@@ -0,0 +1,5 @@
1
+ export declare function useIntent(): {
2
+ intent: string | null;
3
+ params: Record<string, any>;
4
+ query: Record<string, string>;
5
+ };
@@ -0,0 +1,2 @@
1
+ import { navigateIntent } from "../core/intentCore";
2
+ export declare function useNavigateIntent(): typeof navigateIntent;
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "intentx-react-router",
3
+ "version": "0.1.0",
4
+ "description": "Intent-based routing for React. Navigate by intent instead of URLs.",
5
+ "author": "Delpi.Kye",
6
+ "license": "MIT",
7
+
8
+ "type": "module",
9
+ "main": "./build/index.cjs.js",
10
+ "module": "./build/index.esm.js",
11
+ "types": "./build/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./build/index.d.ts",
15
+ "import": "./build/index.esm.js",
16
+ "require": "./build/index.cjs.js"
17
+ }
18
+ },
19
+
20
+ "files": [
21
+ "build"
22
+ ],
23
+ "sideEffects": false,
24
+ "scripts": {
25
+ "clean": "rimraf build",
26
+ "build": "rimraf build && rollup -c",
27
+ "watch": "rollup -c -w",
28
+ "typecheck": "tsc --noEmit"
29
+ },
30
+
31
+ "engines": {
32
+ "node": ">=16"
33
+ },
34
+
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/delpikye-v/intentx-route.git"
38
+ },
39
+ "homepage": "https://github.com/delpikye-v/intentx-react-router#readme",
40
+ "bugs": {
41
+ "url": "https://github.com/delpikye-v/intentx-react-router/issues"
42
+ },
43
+ "keywords": [
44
+ "react",
45
+ "react-router",
46
+ "router",
47
+ "intent-router",
48
+ "navigation",
49
+ "intent-navigation",
50
+ "react-navigation",
51
+ "frontend",
52
+ "spa",
53
+ "routing"
54
+ ],
55
+
56
+ "peerDependencies": {
57
+ "react": ">=18",
58
+ "react-router": ">=6"
59
+ },
60
+ "dependencies": {
61
+ "eventbus-z": "^2.4.0"
62
+ },
63
+ "devDependencies": {
64
+ "@rollup/plugin-commonjs": "^25.0.7",
65
+ "@rollup/plugin-node-resolve": "^15.2.3",
66
+ "@rollup/plugin-terser": "^0.4.4",
67
+ "@types/react": "^18.0.0",
68
+ "@types/react-dom": "^18.0.0",
69
+ "react": "^18.0.0",
70
+ "react-dom": "^18.0.0",
71
+ "rimraf": "^5.0.5",
72
+ "rollup": "^3.29.4",
73
+ "rollup-plugin-peer-deps-external": "^2.2.4",
74
+ "rollup-plugin-typescript2": "^0.36.0",
75
+ "tslib": "^2.6.2",
76
+ "typescript": "^5.0.0"
77
+ },
78
+
79
+ "publishConfig": {
80
+ "access": "public"
81
+ }
82
+ }