@ktjs/router 0.7.3 → 0.14.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/README.md CHANGED
@@ -2,10 +2,14 @@
2
2
 
3
3
  <img src="https://raw.githubusercontent.com/baendlorel/kt.js/dev/.assets/ktjs-0.0.1.svg" alt="KT.js Logo" width="150"/>
4
4
 
5
- > 📦 Part of [KT.js](https://raw.githubusercontent.com/baendlorel/kt.js/dev/README.md) - A simple and easy-to-use web framework that never re-renders.
5
+ [![npm version](https://img.shields.io/npm/v/@ktjs/router.svg)](https://www.npmjs.com/package/@ktjs/router)
6
+
7
+ > 📦 Part of [KT.js](https://github.com/baendlorel/kt.js) - A simple and easy-to-use web framework that never re-renders.
6
8
 
7
9
  Client-side router with navigation guards for KT.js.
8
10
 
11
+ **Current Version:** 0.13.0
12
+
9
13
  ## Overview
10
14
 
11
15
  `@ktjs/router` is a lightweight, hash-based client-side router with powerful navigation guards and async/sync auto-adaptation. It provides all the essential routing features you need without the bloat.
package/dist/index.d.ts CHANGED
@@ -31,8 +31,10 @@ interface RawRouteConfig {
31
31
  name?: string;
32
32
  /** Optional metadata attached to the route */
33
33
  meta?: Record<string, any>;
34
- /** Route-level guard executed before entering this route */
35
- beforeEnter?: (context: RouteContext) => boolean | void | Promise<boolean | void>;
34
+ /** Route-level guard executed before entering this route. Return false to block, string/object to redirect */
35
+ beforeEnter?: (
36
+ context: RouteContext
37
+ ) => string | NavBaseOptions | boolean | void | Promise<string | NavBaseOptions | boolean | void>;
36
38
  /** Route-level hook executed after navigation */
37
39
  after?: (context: RouteContext) => void | Promise<void>;
38
40
  /** Nested child routes */
@@ -96,8 +98,11 @@ interface RouterConfig {
96
98
  /** Array of route definitions */
97
99
  routes: RawRouteConfig[];
98
100
 
99
- /** Global guard executed before each navigation (except silentPush) */
100
- beforeEach?: (to: RouteContext, from: RouteContext | null) => boolean | void | Promise<boolean | void>;
101
+ /** Global guard executed before each navigation (except silentPush). Return false to block, string/object to redirect */
102
+ beforeEach?: (
103
+ to: RouteContext,
104
+ from: RouteContext | null
105
+ ) => string | NavBaseOptions | boolean | void | Promise<string | NavBaseOptions | boolean | void>;
101
106
 
102
107
  /** Global hook executed after each navigation */
103
108
  afterEach?: (to: RouteContext, from: RouteContext | null) => void | Promise<void>;
@@ -189,49 +189,75 @@ var __ktjs_router__ = (function (exports) {
189
189
  const executeGuardsSync = (to, from, guardLevel) => {
190
190
  try {
191
191
  if (guardLevel === 0 /* GuardLevel.None */) {
192
- return true;
192
+ return { continue: true };
193
193
  }
194
194
  if (guardLevel & 1 /* GuardLevel.Global */) {
195
195
  const result = beforeEach(to, from);
196
196
  if (result === false) {
197
- return false;
197
+ return { continue: false };
198
+ }
199
+ if (typeof result === 'string') {
200
+ return { continue: false, redirectTo: { path: result } };
201
+ }
202
+ if (result && typeof result === 'object' && !('then' in result)) {
203
+ return { continue: false, redirectTo: result };
198
204
  }
199
205
  }
200
206
  if (guardLevel & 2 /* GuardLevel.Route */) {
201
207
  const targetRoute = to.matched[to.matched.length - 1];
202
208
  const result = targetRoute.beforeEnter(to);
203
209
  if (result === false) {
204
- return false;
210
+ return { continue: false };
211
+ }
212
+ if (typeof result === 'string') {
213
+ return { continue: false, redirectTo: { path: result } };
214
+ }
215
+ if (result && typeof result === 'object' && !('then' in result)) {
216
+ return { continue: false, redirectTo: result };
205
217
  }
206
218
  }
207
- return true;
219
+ return { continue: true };
208
220
  }
209
221
  catch (error) {
210
222
  onError(error);
211
- return false;
223
+ return { continue: false };
212
224
  }
213
225
  };
214
226
  const executeGuards = async (to, from, guardLevel) => {
215
227
  try {
216
228
  if (guardLevel === 0 /* GuardLevel.None */) {
217
- return true;
229
+ return { continue: true };
218
230
  }
219
231
  if (guardLevel & 1 /* GuardLevel.Global */) {
220
232
  const result = await beforeEach(to, from);
221
233
  if (result === false) {
222
- return false;
234
+ return { continue: false };
235
+ }
236
+ if (typeof result === 'string') {
237
+ return { continue: false, redirectTo: { path: result } };
238
+ }
239
+ if (result && typeof result === 'object') {
240
+ return { continue: false, redirectTo: result };
223
241
  }
224
242
  }
225
243
  if (guardLevel & 2 /* GuardLevel.Route */) {
226
244
  const targetRoute = to.matched[to.matched.length - 1];
227
- const result = targetRoute.beforeEnter(to);
228
- return result !== false;
245
+ const result = await targetRoute.beforeEnter(to);
246
+ if (result === false) {
247
+ return { continue: false };
248
+ }
249
+ if (typeof result === 'string') {
250
+ return { continue: false, redirectTo: { path: result } };
251
+ }
252
+ if (result && typeof result === 'object') {
253
+ return { continue: false, redirectTo: result };
254
+ }
229
255
  }
230
- return true;
256
+ return { continue: true };
231
257
  }
232
258
  catch (error) {
233
259
  onError(error);
234
- return false;
260
+ return { continue: false };
235
261
  }
236
262
  };
237
263
  const navigatePrepare = (options) => {
@@ -268,9 +294,9 @@ var __ktjs_router__ = (function (exports) {
268
294
  const to = {
269
295
  path: targetPath,
270
296
  name: matched.route.name,
271
- params: { ...matched.params, ...(options.params || {}) },
272
- query: options.query || {},
273
- meta: matched.route.meta || {},
297
+ params: { ...matched.params, ...(options.params ?? {}) },
298
+ query: options.query ?? {},
299
+ meta: matched.route.meta ?? {},
274
300
  matched: matched.result,
275
301
  };
276
302
  return {
@@ -280,15 +306,25 @@ var __ktjs_router__ = (function (exports) {
280
306
  fullPath,
281
307
  };
282
308
  };
283
- const navigateSync = (options) => {
309
+ const navigateSync = (options, redirectCount = 0) => {
284
310
  try {
311
+ // Prevent infinite redirect loop
312
+ if (redirectCount > 10) {
313
+ onError(new Error('Maximum redirect count exceeded'));
314
+ return false;
315
+ }
285
316
  const prep = navigatePrepare(options);
286
317
  if (!prep) {
287
318
  return false;
288
319
  }
289
320
  const { guardLevel, replace, to, fullPath } = prep;
290
321
  // Execute guards
291
- if (!executeGuardsSync(to, current, guardLevel)) {
322
+ const guardResult = executeGuardsSync(to, current, guardLevel);
323
+ if (!guardResult.continue) {
324
+ // Check if there's a redirect
325
+ if (guardResult.redirectTo) {
326
+ return navigateSync(guardResult.redirectTo, redirectCount + 1);
327
+ }
292
328
  return false;
293
329
  }
294
330
  // Update browser history
@@ -303,7 +339,7 @@ var __ktjs_router__ = (function (exports) {
303
339
  current = to;
304
340
  history.push(to);
305
341
  // Execute after hooks
306
- executeAfterHooksSync(to, history[history.length - 2] || null);
342
+ executeAfterHooksSync(to, history[history.length - 2] ?? null);
307
343
  return true;
308
344
  }
309
345
  catch (error) {
@@ -311,15 +347,24 @@ var __ktjs_router__ = (function (exports) {
311
347
  return false;
312
348
  }
313
349
  };
314
- const navigateAsync = async (options) => {
350
+ const navigateAsync = async (options, redirectCount = 0) => {
315
351
  try {
352
+ // Prevent infinite redirect loop
353
+ if (redirectCount > 10) {
354
+ onError(new Error('Maximum redirect count exceeded'));
355
+ return false;
356
+ }
316
357
  const prep = navigatePrepare(options);
317
358
  if (!prep) {
318
359
  return false;
319
360
  }
320
361
  const { guardLevel, replace, to, fullPath } = prep;
321
- const passed = await executeGuards(to, current, guardLevel);
322
- if (!passed) {
362
+ const guardResult = await executeGuards(to, current, guardLevel);
363
+ if (!guardResult.continue) {
364
+ // Check if there's a redirect
365
+ if (guardResult.redirectTo) {
366
+ return navigateAsync(guardResult.redirectTo, redirectCount + 1);
367
+ }
323
368
  return false;
324
369
  }
325
370
  // ---- Guards passed ----
@@ -330,7 +375,9 @@ var __ktjs_router__ = (function (exports) {
330
375
  else {
331
376
  window.history.pushState({ path: to.path }, '', url);
332
377
  }
333
- executeAfterHooks(to, history[history.length - 2] || null);
378
+ current = to;
379
+ history.push(to);
380
+ executeAfterHooks(to, history[history.length - 2] ?? null);
334
381
  return true;
335
382
  }
336
383
  catch (error) {
@@ -67,6 +67,16 @@ var __ktjs_router__ = (function (exports) {
67
67
  }
68
68
  }
69
69
 
70
+ function __spreadArray(to, from, pack) {
71
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
72
+ if (ar || !(i in from)) {
73
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
74
+ ar[i] = from[i];
75
+ }
76
+ }
77
+ return to.concat(ar || Array.prototype.slice.call(from));
78
+ }
79
+
70
80
  typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
71
81
  var e = new Error(message);
72
82
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
@@ -272,26 +282,38 @@ var __ktjs_router__ = (function (exports) {
272
282
  var executeGuardsSync = function (to, from, guardLevel) {
273
283
  try {
274
284
  if (guardLevel === 0 /* GuardLevel.None */) {
275
- return true;
285
+ return { continue: true };
276
286
  }
277
287
  if (guardLevel & 1 /* GuardLevel.Global */) {
278
288
  var result = beforeEach(to, from);
279
289
  if (result === false) {
280
- return false;
290
+ return { continue: false };
291
+ }
292
+ if (typeof result === 'string') {
293
+ return { continue: false, redirectTo: { path: result } };
294
+ }
295
+ if (result && typeof result === 'object' && !('then' in result)) {
296
+ return { continue: false, redirectTo: result };
281
297
  }
282
298
  }
283
299
  if (guardLevel & 2 /* GuardLevel.Route */) {
284
300
  var targetRoute = to.matched[to.matched.length - 1];
285
301
  var result = targetRoute.beforeEnter(to);
286
302
  if (result === false) {
287
- return false;
303
+ return { continue: false };
304
+ }
305
+ if (typeof result === 'string') {
306
+ return { continue: false, redirectTo: { path: result } };
307
+ }
308
+ if (result && typeof result === 'object' && !('then' in result)) {
309
+ return { continue: false, redirectTo: result };
288
310
  }
289
311
  }
290
- return true;
312
+ return { continue: true };
291
313
  }
292
314
  catch (error) {
293
315
  onError(error);
294
- return false;
316
+ return { continue: false };
295
317
  }
296
318
  };
297
319
  var executeGuards = function (to, from, guardLevel) { return __awaiter(void 0, void 0, void 0, function () {
@@ -299,35 +321,51 @@ var __ktjs_router__ = (function (exports) {
299
321
  return __generator(this, function (_a) {
300
322
  switch (_a.label) {
301
323
  case 0:
302
- _a.trys.push([0, 3, , 4]);
324
+ _a.trys.push([0, 5, , 6]);
303
325
  if (guardLevel === 0 /* GuardLevel.None */) {
304
- return [2 /*return*/, true];
326
+ return [2 /*return*/, { continue: true }];
305
327
  }
306
328
  if (!(guardLevel & 1 /* GuardLevel.Global */)) return [3 /*break*/, 2];
307
329
  return [4 /*yield*/, beforeEach(to, from)];
308
330
  case 1:
309
331
  result = _a.sent();
310
332
  if (result === false) {
311
- return [2 /*return*/, false];
333
+ return [2 /*return*/, { continue: false }];
334
+ }
335
+ if (typeof result === 'string') {
336
+ return [2 /*return*/, { continue: false, redirectTo: { path: result } }];
337
+ }
338
+ if (result && typeof result === 'object') {
339
+ return [2 /*return*/, { continue: false, redirectTo: result }];
312
340
  }
313
341
  _a.label = 2;
314
342
  case 2:
315
- if (guardLevel & 2 /* GuardLevel.Route */) {
316
- targetRoute = to.matched[to.matched.length - 1];
317
- result = targetRoute.beforeEnter(to);
318
- return [2 /*return*/, result !== false];
319
- }
320
- return [2 /*return*/, true];
343
+ if (!(guardLevel & 2 /* GuardLevel.Route */)) return [3 /*break*/, 4];
344
+ targetRoute = to.matched[to.matched.length - 1];
345
+ return [4 /*yield*/, targetRoute.beforeEnter(to)];
321
346
  case 3:
347
+ result = _a.sent();
348
+ if (result === false) {
349
+ return [2 /*return*/, { continue: false }];
350
+ }
351
+ if (typeof result === 'string') {
352
+ return [2 /*return*/, { continue: false, redirectTo: { path: result } }];
353
+ }
354
+ if (result && typeof result === 'object') {
355
+ return [2 /*return*/, { continue: false, redirectTo: result }];
356
+ }
357
+ _a.label = 4;
358
+ case 4: return [2 /*return*/, { continue: true }];
359
+ case 5:
322
360
  error_1 = _a.sent();
323
361
  onError(error_1);
324
- return [2 /*return*/, false];
325
- case 4: return [2 /*return*/];
362
+ return [2 /*return*/, { continue: false }];
363
+ case 6: return [2 /*return*/];
326
364
  }
327
365
  });
328
366
  }); };
329
367
  var navigatePrepare = function (options) {
330
- var _a, _b, _c;
368
+ var _a, _b, _c, _d, _e, _f;
331
369
  // Resolve target route
332
370
  var targetPath;
333
371
  var targetRoute;
@@ -361,27 +399,39 @@ var __ktjs_router__ = (function (exports) {
361
399
  var to = {
362
400
  path: targetPath,
363
401
  name: matched.route.name,
364
- params: __assign(__assign({}, matched.params), (options.params || {})),
365
- query: options.query || {},
366
- meta: matched.route.meta || {},
402
+ params: __assign(__assign({}, matched.params), ((_b = options.params) !== null && _b !== void 0 ? _b : {})),
403
+ query: (_c = options.query) !== null && _c !== void 0 ? _c : {},
404
+ meta: (_d = matched.route.meta) !== null && _d !== void 0 ? _d : {},
367
405
  matched: matched.result,
368
406
  };
369
407
  return {
370
- guardLevel: (_b = options.guardLevel) !== null && _b !== void 0 ? _b : 15 /* GuardLevel.Default */,
371
- replace: (_c = options.replace) !== null && _c !== void 0 ? _c : false,
408
+ guardLevel: (_e = options.guardLevel) !== null && _e !== void 0 ? _e : 15 /* GuardLevel.Default */,
409
+ replace: (_f = options.replace) !== null && _f !== void 0 ? _f : false,
372
410
  to: to,
373
411
  fullPath: fullPath,
374
412
  };
375
413
  };
376
- var navigateSync = function (options) {
414
+ var navigateSync = function (options, redirectCount) {
415
+ var _a;
416
+ if (redirectCount === void 0) { redirectCount = 0; }
377
417
  try {
418
+ // Prevent infinite redirect loop
419
+ if (redirectCount > 10) {
420
+ onError(new Error('Maximum redirect count exceeded'));
421
+ return false;
422
+ }
378
423
  var prep = navigatePrepare(options);
379
424
  if (!prep) {
380
425
  return false;
381
426
  }
382
427
  var guardLevel = prep.guardLevel, replace = prep.replace, to = prep.to, fullPath = prep.fullPath;
383
428
  // Execute guards
384
- if (!executeGuardsSync(to, current, guardLevel)) {
429
+ var guardResult = executeGuardsSync(to, current, guardLevel);
430
+ if (!guardResult.continue) {
431
+ // Check if there's a redirect
432
+ if (guardResult.redirectTo) {
433
+ return navigateSync(guardResult.redirectTo, redirectCount + 1);
434
+ }
385
435
  return false;
386
436
  }
387
437
  // Update browser history
@@ -396,7 +446,7 @@ var __ktjs_router__ = (function (exports) {
396
446
  current = to;
397
447
  history.push(to);
398
448
  // Execute after hooks
399
- executeAfterHooksSync(to, history[history.length - 2] || null);
449
+ executeAfterHooksSync(to, (_a = history[history.length - 2]) !== null && _a !== void 0 ? _a : null);
400
450
  return true;
401
451
  }
402
452
  catch (error) {
@@ -404,40 +454,59 @@ var __ktjs_router__ = (function (exports) {
404
454
  return false;
405
455
  }
406
456
  };
407
- var navigateAsync = function (options) { return __awaiter(void 0, void 0, void 0, function () {
408
- var prep, guardLevel, replace, to, fullPath, passed, url, error_2;
409
- return __generator(this, function (_a) {
410
- switch (_a.label) {
411
- case 0:
412
- _a.trys.push([0, 2, , 3]);
413
- prep = navigatePrepare(options);
414
- if (!prep) {
415
- return [2 /*return*/, false];
416
- }
417
- guardLevel = prep.guardLevel, replace = prep.replace, to = prep.to, fullPath = prep.fullPath;
418
- return [4 /*yield*/, executeGuards(to, current, guardLevel)];
419
- case 1:
420
- passed = _a.sent();
421
- if (!passed) {
457
+ var navigateAsync = function (options_1) {
458
+ var args_1 = [];
459
+ for (var _i = 1; _i < arguments.length; _i++) {
460
+ args_1[_i - 1] = arguments[_i];
461
+ }
462
+ return __awaiter(void 0, __spreadArray([options_1], args_1, true), void 0, function (options, redirectCount) {
463
+ var prep, guardLevel, replace, to, fullPath, guardResult, url, error_2;
464
+ var _a;
465
+ if (redirectCount === void 0) { redirectCount = 0; }
466
+ return __generator(this, function (_b) {
467
+ switch (_b.label) {
468
+ case 0:
469
+ _b.trys.push([0, 2, , 3]);
470
+ // Prevent infinite redirect loop
471
+ if (redirectCount > 10) {
472
+ onError(new Error('Maximum redirect count exceeded'));
473
+ return [2 /*return*/, false];
474
+ }
475
+ prep = navigatePrepare(options);
476
+ if (!prep) {
477
+ return [2 /*return*/, false];
478
+ }
479
+ guardLevel = prep.guardLevel, replace = prep.replace, to = prep.to, fullPath = prep.fullPath;
480
+ return [4 /*yield*/, executeGuards(to, current, guardLevel)];
481
+ case 1:
482
+ guardResult = _b.sent();
483
+ if (!guardResult.continue) {
484
+ // Check if there's a redirect
485
+ if (guardResult.redirectTo) {
486
+ return [2 /*return*/, navigateAsync(guardResult.redirectTo, redirectCount + 1)];
487
+ }
488
+ return [2 /*return*/, false];
489
+ }
490
+ url = fullPath;
491
+ if (replace) {
492
+ window.history.replaceState({ path: to.path }, '', url);
493
+ }
494
+ else {
495
+ window.history.pushState({ path: to.path }, '', url);
496
+ }
497
+ current = to;
498
+ history.push(to);
499
+ executeAfterHooks(to, (_a = history[history.length - 2]) !== null && _a !== void 0 ? _a : null);
500
+ return [2 /*return*/, true];
501
+ case 2:
502
+ error_2 = _b.sent();
503
+ onError(error_2);
422
504
  return [2 /*return*/, false];
423
- }
424
- url = fullPath;
425
- if (replace) {
426
- window.history.replaceState({ path: to.path }, '', url);
427
- }
428
- else {
429
- window.history.pushState({ path: to.path }, '', url);
430
- }
431
- executeAfterHooks(to, history[history.length - 2] || null);
432
- return [2 /*return*/, true];
433
- case 2:
434
- error_2 = _a.sent();
435
- onError(error_2);
436
- return [2 /*return*/, false];
437
- case 3: return [2 /*return*/];
438
- }
505
+ case 3: return [2 /*return*/];
506
+ }
507
+ });
439
508
  });
440
- }); };
509
+ };
441
510
  var navigate = asyncGuards ? navigateSync : navigateAsync;
442
511
  var executeAfterHooksSync = function (to, from) {
443
512
  var targetRoute = to.matched[to.matched.length - 1];
package/dist/index.mjs CHANGED
@@ -186,49 +186,75 @@ const createRouter = (config) => {
186
186
  const executeGuardsSync = (to, from, guardLevel) => {
187
187
  try {
188
188
  if (guardLevel === 0 /* GuardLevel.None */) {
189
- return true;
189
+ return { continue: true };
190
190
  }
191
191
  if (guardLevel & 1 /* GuardLevel.Global */) {
192
192
  const result = beforeEach(to, from);
193
193
  if (result === false) {
194
- return false;
194
+ return { continue: false };
195
+ }
196
+ if (typeof result === 'string') {
197
+ return { continue: false, redirectTo: { path: result } };
198
+ }
199
+ if (result && typeof result === 'object' && !('then' in result)) {
200
+ return { continue: false, redirectTo: result };
195
201
  }
196
202
  }
197
203
  if (guardLevel & 2 /* GuardLevel.Route */) {
198
204
  const targetRoute = to.matched[to.matched.length - 1];
199
205
  const result = targetRoute.beforeEnter(to);
200
206
  if (result === false) {
201
- return false;
207
+ return { continue: false };
208
+ }
209
+ if (typeof result === 'string') {
210
+ return { continue: false, redirectTo: { path: result } };
211
+ }
212
+ if (result && typeof result === 'object' && !('then' in result)) {
213
+ return { continue: false, redirectTo: result };
202
214
  }
203
215
  }
204
- return true;
216
+ return { continue: true };
205
217
  }
206
218
  catch (error) {
207
219
  onError(error);
208
- return false;
220
+ return { continue: false };
209
221
  }
210
222
  };
211
223
  const executeGuards = async (to, from, guardLevel) => {
212
224
  try {
213
225
  if (guardLevel === 0 /* GuardLevel.None */) {
214
- return true;
226
+ return { continue: true };
215
227
  }
216
228
  if (guardLevel & 1 /* GuardLevel.Global */) {
217
229
  const result = await beforeEach(to, from);
218
230
  if (result === false) {
219
- return false;
231
+ return { continue: false };
232
+ }
233
+ if (typeof result === 'string') {
234
+ return { continue: false, redirectTo: { path: result } };
235
+ }
236
+ if (result && typeof result === 'object') {
237
+ return { continue: false, redirectTo: result };
220
238
  }
221
239
  }
222
240
  if (guardLevel & 2 /* GuardLevel.Route */) {
223
241
  const targetRoute = to.matched[to.matched.length - 1];
224
- const result = targetRoute.beforeEnter(to);
225
- return result !== false;
242
+ const result = await targetRoute.beforeEnter(to);
243
+ if (result === false) {
244
+ return { continue: false };
245
+ }
246
+ if (typeof result === 'string') {
247
+ return { continue: false, redirectTo: { path: result } };
248
+ }
249
+ if (result && typeof result === 'object') {
250
+ return { continue: false, redirectTo: result };
251
+ }
226
252
  }
227
- return true;
253
+ return { continue: true };
228
254
  }
229
255
  catch (error) {
230
256
  onError(error);
231
- return false;
257
+ return { continue: false };
232
258
  }
233
259
  };
234
260
  const navigatePrepare = (options) => {
@@ -265,9 +291,9 @@ const createRouter = (config) => {
265
291
  const to = {
266
292
  path: targetPath,
267
293
  name: matched.route.name,
268
- params: { ...matched.params, ...(options.params || {}) },
269
- query: options.query || {},
270
- meta: matched.route.meta || {},
294
+ params: { ...matched.params, ...(options.params ?? {}) },
295
+ query: options.query ?? {},
296
+ meta: matched.route.meta ?? {},
271
297
  matched: matched.result,
272
298
  };
273
299
  return {
@@ -277,15 +303,25 @@ const createRouter = (config) => {
277
303
  fullPath,
278
304
  };
279
305
  };
280
- const navigateSync = (options) => {
306
+ const navigateSync = (options, redirectCount = 0) => {
281
307
  try {
308
+ // Prevent infinite redirect loop
309
+ if (redirectCount > 10) {
310
+ onError(new Error('Maximum redirect count exceeded'));
311
+ return false;
312
+ }
282
313
  const prep = navigatePrepare(options);
283
314
  if (!prep) {
284
315
  return false;
285
316
  }
286
317
  const { guardLevel, replace, to, fullPath } = prep;
287
318
  // Execute guards
288
- if (!executeGuardsSync(to, current, guardLevel)) {
319
+ const guardResult = executeGuardsSync(to, current, guardLevel);
320
+ if (!guardResult.continue) {
321
+ // Check if there's a redirect
322
+ if (guardResult.redirectTo) {
323
+ return navigateSync(guardResult.redirectTo, redirectCount + 1);
324
+ }
289
325
  return false;
290
326
  }
291
327
  // Update browser history
@@ -300,7 +336,7 @@ const createRouter = (config) => {
300
336
  current = to;
301
337
  history.push(to);
302
338
  // Execute after hooks
303
- executeAfterHooksSync(to, history[history.length - 2] || null);
339
+ executeAfterHooksSync(to, history[history.length - 2] ?? null);
304
340
  return true;
305
341
  }
306
342
  catch (error) {
@@ -308,15 +344,24 @@ const createRouter = (config) => {
308
344
  return false;
309
345
  }
310
346
  };
311
- const navigateAsync = async (options) => {
347
+ const navigateAsync = async (options, redirectCount = 0) => {
312
348
  try {
349
+ // Prevent infinite redirect loop
350
+ if (redirectCount > 10) {
351
+ onError(new Error('Maximum redirect count exceeded'));
352
+ return false;
353
+ }
313
354
  const prep = navigatePrepare(options);
314
355
  if (!prep) {
315
356
  return false;
316
357
  }
317
358
  const { guardLevel, replace, to, fullPath } = prep;
318
- const passed = await executeGuards(to, current, guardLevel);
319
- if (!passed) {
359
+ const guardResult = await executeGuards(to, current, guardLevel);
360
+ if (!guardResult.continue) {
361
+ // Check if there's a redirect
362
+ if (guardResult.redirectTo) {
363
+ return navigateAsync(guardResult.redirectTo, redirectCount + 1);
364
+ }
320
365
  return false;
321
366
  }
322
367
  // ---- Guards passed ----
@@ -327,7 +372,9 @@ const createRouter = (config) => {
327
372
  else {
328
373
  window.history.pushState({ path: to.path }, '', url);
329
374
  }
330
- executeAfterHooks(to, history[history.length - 2] || null);
375
+ current = to;
376
+ history.push(to);
377
+ executeAfterHooks(to, history[history.length - 2] ?? null);
331
378
  return true;
332
379
  }
333
380
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ktjs/router",
3
- "version": "0.7.3",
3
+ "version": "0.14.0",
4
4
  "description": "Router for kt.js - client-side routing with navigation guards",
5
5
  "type": "module",
6
6
  "module": "./dist/index.mjs",
@@ -31,7 +31,7 @@
31
31
  "directory": "packages/router"
32
32
  },
33
33
  "dependencies": {
34
- "@ktjs/core": "0.7.3"
34
+ "@ktjs/core": "0.13.0"
35
35
  },
36
36
  "scripts": {
37
37
  "build": "rollup -c rollup.config.mjs",