@ktjs/router 0.13.0 → 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/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) => {
@@ -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
@@ -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,6 +375,8 @@ var __ktjs_router__ = (function (exports) {
330
375
  else {
331
376
  window.history.pushState({ path: to.path }, '', url);
332
377
  }
378
+ current = to;
379
+ history.push(to);
333
380
  executeAfterHooks(to, history[history.length - 2] ?? null);
334
381
  return true;
335
382
  }
@@ -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,30 +321,46 @@ 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
  }); };
@@ -373,16 +411,27 @@ var __ktjs_router__ = (function (exports) {
373
411
  fullPath: fullPath,
374
412
  };
375
413
  };
376
- var navigateSync = function (options) {
414
+ var navigateSync = function (options, redirectCount) {
377
415
  var _a;
416
+ if (redirectCount === void 0) { redirectCount = 0; }
378
417
  try {
418
+ // Prevent infinite redirect loop
419
+ if (redirectCount > 10) {
420
+ onError(new Error('Maximum redirect count exceeded'));
421
+ return false;
422
+ }
379
423
  var prep = navigatePrepare(options);
380
424
  if (!prep) {
381
425
  return false;
382
426
  }
383
427
  var guardLevel = prep.guardLevel, replace = prep.replace, to = prep.to, fullPath = prep.fullPath;
384
428
  // Execute guards
385
- 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
+ }
386
435
  return false;
387
436
  }
388
437
  // Update browser history
@@ -405,41 +454,59 @@ var __ktjs_router__ = (function (exports) {
405
454
  return false;
406
455
  }
407
456
  };
408
- var navigateAsync = function (options) { return __awaiter(void 0, void 0, void 0, function () {
409
- var prep, guardLevel, replace, to, fullPath, passed, url, error_2;
410
- var _a;
411
- return __generator(this, function (_b) {
412
- switch (_b.label) {
413
- case 0:
414
- _b.trys.push([0, 2, , 3]);
415
- prep = navigatePrepare(options);
416
- if (!prep) {
417
- return [2 /*return*/, false];
418
- }
419
- guardLevel = prep.guardLevel, replace = prep.replace, to = prep.to, fullPath = prep.fullPath;
420
- return [4 /*yield*/, executeGuards(to, current, guardLevel)];
421
- case 1:
422
- passed = _b.sent();
423
- 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);
424
504
  return [2 /*return*/, false];
425
- }
426
- url = fullPath;
427
- if (replace) {
428
- window.history.replaceState({ path: to.path }, '', url);
429
- }
430
- else {
431
- window.history.pushState({ path: to.path }, '', url);
432
- }
433
- executeAfterHooks(to, (_a = history[history.length - 2]) !== null && _a !== void 0 ? _a : null);
434
- return [2 /*return*/, true];
435
- case 2:
436
- error_2 = _b.sent();
437
- onError(error_2);
438
- return [2 /*return*/, false];
439
- case 3: return [2 /*return*/];
440
- }
505
+ case 3: return [2 /*return*/];
506
+ }
507
+ });
441
508
  });
442
- }); };
509
+ };
443
510
  var navigate = asyncGuards ? navigateSync : navigateAsync;
444
511
  var executeAfterHooksSync = function (to, from) {
445
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) => {
@@ -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
@@ -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,6 +372,8 @@ const createRouter = (config) => {
327
372
  else {
328
373
  window.history.pushState({ path: to.path }, '', url);
329
374
  }
375
+ current = to;
376
+ history.push(to);
330
377
  executeAfterHooks(to, history[history.length - 2] ?? null);
331
378
  return true;
332
379
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ktjs/router",
3
- "version": "0.13.0",
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",