@real-router/core 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,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Oleg Ivanov
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.
22
+
package/README.md ADDED
@@ -0,0 +1,596 @@
1
+ # @real-router/core
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue.svg)](https://www.typescriptlang.org/)
5
+
6
+ Core router implementation for Real-Router.
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ npm install @real-router/core
12
+ # or
13
+ pnpm add @real-router/core
14
+ # or
15
+ yarn add @real-router/core
16
+ # or
17
+ bun add @real-router/core
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ```typescript
23
+ import { createRouter } from "@real-router/core";
24
+
25
+ const routes = [
26
+ { name: "home", path: "/" },
27
+ { name: "users", path: "/users" },
28
+ { name: "users.profile", path: "/:id" },
29
+ ];
30
+
31
+ const router = createRouter(routes);
32
+
33
+ router.start();
34
+ router.navigate("users.profile", { id: "123" });
35
+ ```
36
+
37
+ ## API Reference
38
+
39
+ ### `createRouter(routes?, options?, dependencies?)`
40
+
41
+ Creates a new router instance.
42
+
43
+ ```typescript
44
+ const router = createRouter(
45
+ routes, // Route[] - route definitions
46
+ options, // Partial<Options> - router options
47
+ dependencies // object - dependency injection
48
+ );
49
+ ```
50
+
51
+ ---
52
+
53
+ ## Router Methods
54
+
55
+ ### Lifecycle
56
+
57
+ #### `router.start(startPath?)`
58
+
59
+ Starts the router. Optionally accepts an initial path.
60
+
61
+ ```typescript
62
+ router.start();
63
+ router.start("/users/123");
64
+ router.start("/users/123", (err, state) => {
65
+ if (err) console.error(err);
66
+ });
67
+ ```
68
+
69
+ #### `router.stop()`
70
+
71
+ Stops the router.
72
+
73
+ ```typescript
74
+ router.stop();
75
+ ```
76
+
77
+ #### `router.isStarted()`
78
+
79
+ Returns whether the router is started.
80
+
81
+ ```typescript
82
+ if (router.isStarted()) {
83
+ router.navigate("home");
84
+ }
85
+ ```
86
+
87
+ ---
88
+
89
+ ### Navigation
90
+
91
+ #### `router.navigate(name, params?, options?, done?)`
92
+
93
+ Navigates to a route by name.
94
+
95
+ ```typescript
96
+ router.navigate("users");
97
+ router.navigate("users.profile", { id: "123" });
98
+ router.navigate("users.profile", { id: "123" }, { replace: true });
99
+ router.navigate("users.profile", { id: "123" }, { replace: true }, (err, state) => {
100
+ if (err) console.error(err);
101
+ });
102
+ ```
103
+
104
+ #### `router.navigateToDefault(options?, done?)`
105
+
106
+ Navigates to the default route.
107
+
108
+ ```typescript
109
+ router.navigateToDefault();
110
+ ```
111
+
112
+ #### `router.isNavigating()`
113
+
114
+ Returns whether a navigation is in progress.
115
+
116
+ ```typescript
117
+ if (!router.isNavigating()) {
118
+ router.navigate("home");
119
+ }
120
+ ```
121
+
122
+ #### Cancelling Navigation
123
+
124
+ The `navigate()` method returns a cancel function that can be used to abort the navigation.
125
+
126
+ ```typescript
127
+ const cancel = router.navigate("users.profile", { id: "123" });
128
+
129
+ // Later, if you need to cancel:
130
+ cancel();
131
+ ```
132
+
133
+ ---
134
+
135
+ ### State
136
+
137
+ #### `router.getState()`
138
+
139
+ Returns the current router state.
140
+
141
+ ```typescript
142
+ const state = router.getState();
143
+ // { name: "users.profile", params: { id: "123" }, path: "/users/123" }
144
+ ```
145
+
146
+ #### `router.getPreviousState()`
147
+
148
+ Returns the previous router state.
149
+
150
+ ```typescript
151
+ const prev = router.getPreviousState();
152
+ ```
153
+
154
+ #### `router.setState(state)`
155
+
156
+ Sets the router state directly (without navigation).
157
+
158
+ ```typescript
159
+ router.setState({ name: "home", params: {}, path: "/" });
160
+ ```
161
+
162
+ #### `router.areStatesEqual(state1, state2, ignoreQueryParams?)`
163
+
164
+ Compares two states for equality.
165
+
166
+ ```typescript
167
+ router.areStatesEqual(stateA, stateB);
168
+ router.areStatesEqual(stateA, stateB, true); // ignore query params
169
+ ```
170
+
171
+ ---
172
+
173
+ ### Routes
174
+
175
+ #### `router.addRoute(route)`
176
+
177
+ Adds a route definition.
178
+
179
+ ```typescript
180
+ router.addRoute({ name: "settings", path: "/settings" });
181
+ router.addRoute({ name: "settings.profile", path: "/profile" });
182
+ ```
183
+
184
+ #### `router.removeRoute(name)`
185
+
186
+ Removes a route by name.
187
+
188
+ ```typescript
189
+ router.removeRoute("settings");
190
+ ```
191
+
192
+ #### `router.getRoute(name)`
193
+
194
+ Gets a route definition by name.
195
+
196
+ ```typescript
197
+ const route = router.getRoute("users");
198
+ ```
199
+
200
+ #### `router.hasRoute(name)`
201
+
202
+ Checks if a route exists.
203
+
204
+ ```typescript
205
+ if (router.hasRoute("users")) {
206
+ router.navigate("users");
207
+ }
208
+ ```
209
+
210
+ #### `router.clearRoutes()`
211
+
212
+ Removes all routes from the router.
213
+
214
+ ```typescript
215
+ router.clearRoutes().addRoute([
216
+ { name: "home", path: "/" },
217
+ { name: "about", path: "/about" },
218
+ ]);
219
+ ```
220
+
221
+ #### `router.updateRoute(name, updates)`
222
+
223
+ Updates configuration of an existing route.
224
+
225
+ ```typescript
226
+ router.updateRoute("users", {
227
+ defaultParams: { page: 1 },
228
+ canActivate: authGuard,
229
+ });
230
+
231
+ // Remove configuration by setting to null
232
+ router.updateRoute("oldRoute", { forwardTo: null });
233
+ ```
234
+
235
+ ---
236
+
237
+ ### Path Building & Matching
238
+
239
+ #### `router.buildPath(name, params?)`
240
+
241
+ Builds a URL path from route name and params.
242
+
243
+ ```typescript
244
+ const path = router.buildPath("users.profile", { id: "123" });
245
+ // "/users/123"
246
+ ```
247
+
248
+ #### `router.buildState(name, params?)`
249
+
250
+ Builds a state object from route name and params.
251
+
252
+ ```typescript
253
+ const state = router.buildState("users.profile", { id: "123" });
254
+ // { name: "users.profile", params: { id: "123" }, path: "/users/123", meta: {...} }
255
+ ```
256
+
257
+ #### `router.matchPath(path)`
258
+
259
+ Matches a URL path to a state.
260
+
261
+ ```typescript
262
+ const state = router.matchPath("/users/123");
263
+ // { name: "users.profile", params: { id: "123" }, ... }
264
+ ```
265
+
266
+ #### `router.isActiveRoute(name, params?, strictEquality?, ignoreQueryParams?)`
267
+
268
+ Checks if a route is currently active.
269
+
270
+ ```typescript
271
+ router.isActiveRoute("users"); // true if current route starts with "users"
272
+ router.isActiveRoute("users", { id: "123" }); // true if params match
273
+ router.isActiveRoute("users", { id: "123" }, true); // strict equality
274
+ ```
275
+
276
+ #### `router.forward(fromRoute, toRoute)`
277
+
278
+ Sets up route forwarding (redirect).
279
+
280
+ ```typescript
281
+ router.forward("old-page", "new-page");
282
+ // Navigating to "old-page" will redirect to "new-page"
283
+ ```
284
+
285
+ ---
286
+
287
+ ### Guards
288
+
289
+ #### `router.canActivate(name, canActivateFn)`
290
+
291
+ Registers a guard for route activation.
292
+
293
+ ```typescript
294
+ router.canActivate("admin", (toState, fromState, done) => {
295
+ if (!isAuthenticated()) {
296
+ done({ redirect: { name: "login" } });
297
+ } else {
298
+ done();
299
+ }
300
+ });
301
+ ```
302
+
303
+ #### `router.canDeactivate(name, canDeactivateFn)`
304
+
305
+ Registers a guard for route deactivation.
306
+
307
+ ```typescript
308
+ router.canDeactivate("editor", (toState, fromState, done) => {
309
+ if (hasUnsavedChanges()) {
310
+ done({ error: new Error("Unsaved changes") });
311
+ } else {
312
+ done();
313
+ }
314
+ });
315
+ ```
316
+
317
+ #### `router.clearCanActivate(name)`
318
+
319
+ Clears activation guard for a route.
320
+
321
+ #### `router.clearCanDeactivate(name)`
322
+
323
+ Clears deactivation guard for a route.
324
+
325
+ ---
326
+
327
+ ### Events & Subscriptions
328
+
329
+ #### `router.subscribe(listener)`
330
+
331
+ Subscribes to state changes.
332
+
333
+ ```typescript
334
+ const unsubscribe = router.subscribe(({ route, previousRoute }) => {
335
+ console.log("Navigation:", previousRoute?.name, "→", route.name);
336
+ });
337
+
338
+ // Later: unsubscribe()
339
+ ```
340
+
341
+ #### `router.addEventListener(event, listener)`
342
+
343
+ Adds an event listener. Returns an unsubscribe function.
344
+
345
+ ```typescript
346
+ import { events } from "@real-router/core";
347
+
348
+ const unsubscribe = router.addEventListener(events.TRANSITION_START, (toState, fromState) => {
349
+ console.log("Starting:", toState.name);
350
+ });
351
+
352
+ router.addEventListener(events.TRANSITION_SUCCESS, (toState, fromState) => {
353
+ console.log("Success:", toState.name);
354
+ });
355
+
356
+ router.addEventListener(events.TRANSITION_ERROR, (toState, fromState, error) => {
357
+ console.error("Error:", error);
358
+ });
359
+
360
+ // Available events:
361
+ // events.ROUTER_START, events.ROUTER_STOP
362
+ // events.TRANSITION_START, events.TRANSITION_SUCCESS
363
+ // events.TRANSITION_ERROR, events.TRANSITION_CANCEL
364
+ ```
365
+
366
+ #### `router.removeEventListener(event, listener)`
367
+
368
+ Removes an event listener.
369
+
370
+ ---
371
+
372
+ ### Plugins
373
+
374
+ #### `router.usePlugin(plugin)`
375
+
376
+ Registers a plugin. Returns an unsubscribe function.
377
+
378
+ ```typescript
379
+ import { browserPluginFactory } from "@real-router/browser-plugin";
380
+
381
+ const unsubscribe = router.usePlugin(browserPluginFactory());
382
+
383
+ // Later, to remove the plugin:
384
+ unsubscribe();
385
+ ```
386
+
387
+ ---
388
+
389
+ ### Middleware
390
+
391
+ #### `router.useMiddleware(middleware)`
392
+
393
+ Registers middleware.
394
+
395
+ ```typescript
396
+ router.useMiddleware((router) => (toState, fromState, done) => {
397
+ console.log("Navigating:", toState.name);
398
+ done();
399
+ });
400
+ ```
401
+
402
+ #### `router.clearMiddleware()`
403
+
404
+ Clears all middleware.
405
+
406
+ ---
407
+
408
+ ### Options
409
+
410
+ #### `router.getOptions()`
411
+
412
+ Returns router options.
413
+
414
+ ```typescript
415
+ const options = router.getOptions();
416
+ ```
417
+
418
+ #### `router.setOption(name, value)`
419
+
420
+ Sets a router option. Can only be used before `router.start()`.
421
+
422
+ ```typescript
423
+ router.setOption("defaultRoute", "home");
424
+ router.setOption("trailingSlash", "never");
425
+ ```
426
+
427
+ ---
428
+
429
+ ### Dependencies
430
+
431
+ #### `router.getDependencies()`
432
+
433
+ Returns a shallow copy of all injected dependencies.
434
+
435
+ ```typescript
436
+ const deps = router.getDependencies();
437
+ ```
438
+
439
+ #### `router.getDependency(name)`
440
+
441
+ Returns a specific dependency.
442
+
443
+ ```typescript
444
+ const api = router.getDependency("api");
445
+ ```
446
+
447
+ #### `router.setDependency(name, value)`
448
+
449
+ Sets a single dependency.
450
+
451
+ ```typescript
452
+ router.setDependency("api", apiClient);
453
+ ```
454
+
455
+ #### `router.setDependencies(deps)`
456
+
457
+ Sets multiple dependencies at once.
458
+
459
+ ```typescript
460
+ router.setDependencies({
461
+ api: apiClient,
462
+ logger: console,
463
+ cache: cacheService,
464
+ });
465
+ ```
466
+
467
+ #### `router.hasDependency(name)`
468
+
469
+ Checks if a dependency exists.
470
+
471
+ ```typescript
472
+ if (router.hasDependency("api")) {
473
+ const api = router.getDependency("api");
474
+ }
475
+ ```
476
+
477
+ #### `router.removeDependency(name)`
478
+
479
+ Removes a dependency.
480
+
481
+ ```typescript
482
+ router.removeDependency("tempService");
483
+ ```
484
+
485
+ #### `router.resetDependencies()`
486
+
487
+ Removes all dependencies.
488
+
489
+ ```typescript
490
+ router.resetDependencies();
491
+ ```
492
+
493
+ ---
494
+
495
+ ### Cloning
496
+
497
+ #### `router.clone(dependencies?)`
498
+
499
+ Creates a clone of the router with the same configuration.
500
+
501
+ ```typescript
502
+ // Basic cloning
503
+ const clonedRouter = router.clone();
504
+
505
+ // SSR: Clone with request-specific dependencies
506
+ app.get("*", (req, res) => {
507
+ const ssrRouter = router.clone({ request: req });
508
+ ssrRouter.start(req.url, (err, state) => {
509
+ // Render with state...
510
+ });
511
+ });
512
+ ```
513
+
514
+ ---
515
+
516
+ ## Options
517
+
518
+ ```typescript
519
+ interface Options {
520
+ defaultRoute: string; // Default route name (default: "")
521
+ defaultParams: Params; // Default route params (default: {})
522
+ trailingSlash: "strict" | "never" | "always" | "preserve"; // (default: "preserve")
523
+ caseSensitive: boolean; // Case-sensitive matching (default: false)
524
+ urlParamsEncoding: "default" | "uri" | "uriComponent" | "none"; // (default: "default")
525
+ queryParamsMode: "default" | "strict" | "loose"; // (default: "loose")
526
+ queryParams?: QueryParamsOptions; // Query parameter parsing options
527
+ allowNotFound: boolean; // Allow navigation to unknown routes (default: true)
528
+ rewritePathOnMatch: boolean; // Rewrite path on successful match (default: false)
529
+ logger?: Partial<LoggerConfig>; // Logger configuration
530
+ }
531
+ ```
532
+
533
+ ## Observable Support
534
+
535
+ The router implements the Observable interface:
536
+
537
+ ```typescript
538
+ import { from } from "rxjs";
539
+
540
+ from(router).subscribe(({ route, previousRoute }) => {
541
+ console.log("Route changed:", route.name);
542
+ });
543
+ ```
544
+
545
+ ### RouterError
546
+
547
+ Navigation errors are instances of `RouterError`:
548
+
549
+ ```typescript
550
+ import { RouterError, errorCodes } from "@real-router/core";
551
+
552
+ router.navigate("users", {}, {}, (err, state) => {
553
+ if (err instanceof RouterError) {
554
+ switch (err.code) {
555
+ case errorCodes.ROUTE_NOT_FOUND:
556
+ console.log("Route not found");
557
+ break;
558
+ case errorCodes.CANNOT_ACTIVATE:
559
+ console.log("Activation blocked by guard");
560
+ break;
561
+ case errorCodes.CANNOT_DEACTIVATE:
562
+ console.log("Deactivation blocked by guard");
563
+ break;
564
+ case errorCodes.TRANSITION_CANCELLED:
565
+ console.log("Navigation was cancelled");
566
+ break;
567
+ }
568
+ }
569
+ });
570
+ ```
571
+
572
+ ### Error Codes
573
+
574
+ ```typescript
575
+ errorCodes.ROUTER_NOT_STARTED // "NOT_STARTED"
576
+ errorCodes.ROUTER_ALREADY_STARTED // "ALREADY_STARTED"
577
+ errorCodes.NO_START_PATH_OR_STATE // "NO_START_PATH_OR_STATE"
578
+ errorCodes.ROUTE_NOT_FOUND // "ROUTE_NOT_FOUND"
579
+ errorCodes.SAME_STATES // "SAME_STATES"
580
+ errorCodes.CANNOT_DEACTIVATE // "CANNOT_DEACTIVATE"
581
+ errorCodes.CANNOT_ACTIVATE // "CANNOT_ACTIVATE"
582
+ errorCodes.TRANSITION_ERR // "TRANSITION_ERR"
583
+ errorCodes.TRANSITION_CANCELLED // "CANCELLED"
584
+ ```
585
+
586
+ ## Related Packages
587
+
588
+ - [@real-router/react](https://www.npmjs.com/package/@real-router/react) — React integration
589
+ - [@real-router/browser-plugin](https://www.npmjs.com/package/@real-router/browser-plugin) — Browser history
590
+ - [@real-router/logger-plugin](https://www.npmjs.com/package/@real-router/logger-plugin) — Debug logging
591
+ - [@real-router/persistent-params-plugin](https://www.npmjs.com/package/@real-router/persistent-params-plugin) — Persistent params
592
+ - [@real-router/helpers](https://www.npmjs.com/package/@real-router/helpers) — Utilities
593
+
594
+ ## License
595
+
596
+ MIT © [Oleg Ivanov](https://github.com/greydragon888)