@esmx/router 3.0.0-rc.18 → 3.0.0-rc.19

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.
Files changed (158) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +70 -0
  3. package/README.zh-CN.md +70 -0
  4. package/dist/error.d.ts +23 -0
  5. package/dist/error.mjs +61 -0
  6. package/dist/increment-id.d.ts +7 -0
  7. package/dist/increment-id.mjs +11 -0
  8. package/dist/index.d.ts +5 -3
  9. package/dist/index.mjs +14 -3
  10. package/dist/index.test.mjs +8 -0
  11. package/dist/location.d.ts +15 -0
  12. package/dist/location.mjs +53 -0
  13. package/dist/location.test.d.ts +8 -0
  14. package/dist/location.test.mjs +370 -0
  15. package/dist/matcher.d.ts +3 -0
  16. package/dist/matcher.mjs +44 -0
  17. package/dist/matcher.test.mjs +1492 -0
  18. package/dist/micro-app.d.ts +18 -0
  19. package/dist/micro-app.dom.test.d.ts +1 -0
  20. package/dist/micro-app.dom.test.mjs +532 -0
  21. package/dist/micro-app.mjs +80 -0
  22. package/dist/navigation.d.ts +43 -0
  23. package/dist/navigation.mjs +143 -0
  24. package/dist/navigation.test.d.ts +1 -0
  25. package/dist/navigation.test.mjs +681 -0
  26. package/dist/options.d.ts +4 -0
  27. package/dist/options.mjs +88 -0
  28. package/dist/route-task.d.ts +40 -0
  29. package/dist/route-task.mjs +75 -0
  30. package/dist/route-task.test.d.ts +1 -0
  31. package/dist/route-task.test.mjs +673 -0
  32. package/dist/route-transition.d.ts +53 -0
  33. package/dist/route-transition.mjs +307 -0
  34. package/dist/route-transition.test.d.ts +1 -0
  35. package/dist/route-transition.test.mjs +146 -0
  36. package/dist/route.d.ts +72 -0
  37. package/dist/route.mjs +194 -0
  38. package/dist/route.test.d.ts +1 -0
  39. package/dist/route.test.mjs +1664 -0
  40. package/dist/router-back.test.d.ts +1 -0
  41. package/dist/router-back.test.mjs +361 -0
  42. package/dist/router-forward.test.d.ts +1 -0
  43. package/dist/router-forward.test.mjs +376 -0
  44. package/dist/router-go.test.d.ts +1 -0
  45. package/dist/router-go.test.mjs +73 -0
  46. package/dist/router-guards-cleanup.test.d.ts +1 -0
  47. package/dist/router-guards-cleanup.test.mjs +437 -0
  48. package/dist/router-link.d.ts +10 -0
  49. package/dist/router-link.mjs +126 -0
  50. package/dist/router-push.test.d.ts +1 -0
  51. package/dist/router-push.test.mjs +115 -0
  52. package/dist/router-replace.test.d.ts +1 -0
  53. package/dist/router-replace.test.mjs +114 -0
  54. package/dist/router-resolve.test.d.ts +1 -0
  55. package/dist/router-resolve.test.mjs +393 -0
  56. package/dist/router-restart-app.dom.test.d.ts +1 -0
  57. package/dist/router-restart-app.dom.test.mjs +616 -0
  58. package/dist/router-window-navigation.test.d.ts +1 -0
  59. package/dist/router-window-navigation.test.mjs +359 -0
  60. package/dist/router.d.ts +109 -102
  61. package/dist/router.mjs +260 -361
  62. package/dist/types.d.ts +246 -0
  63. package/dist/types.mjs +18 -0
  64. package/dist/util.d.ts +26 -0
  65. package/dist/util.mjs +53 -0
  66. package/dist/util.test.d.ts +1 -0
  67. package/dist/util.test.mjs +1020 -0
  68. package/package.json +10 -13
  69. package/src/error.ts +84 -0
  70. package/src/increment-id.ts +12 -0
  71. package/src/index.test.ts +9 -0
  72. package/src/index.ts +54 -3
  73. package/src/location.test.ts +406 -0
  74. package/src/location.ts +96 -0
  75. package/src/matcher.test.ts +1685 -0
  76. package/src/matcher.ts +59 -0
  77. package/src/micro-app.dom.test.ts +708 -0
  78. package/src/micro-app.ts +101 -0
  79. package/src/navigation.test.ts +858 -0
  80. package/src/navigation.ts +195 -0
  81. package/src/options.ts +131 -0
  82. package/src/route-task.test.ts +901 -0
  83. package/src/route-task.ts +105 -0
  84. package/src/route-transition.test.ts +178 -0
  85. package/src/route-transition.ts +425 -0
  86. package/src/route.test.ts +2014 -0
  87. package/src/route.ts +308 -0
  88. package/src/router-back.test.ts +487 -0
  89. package/src/router-forward.test.ts +506 -0
  90. package/src/router-go.test.ts +91 -0
  91. package/src/router-guards-cleanup.test.ts +595 -0
  92. package/src/router-link.ts +235 -0
  93. package/src/router-push.test.ts +140 -0
  94. package/src/router-replace.test.ts +139 -0
  95. package/src/router-resolve.test.ts +475 -0
  96. package/src/router-restart-app.dom.test.ts +783 -0
  97. package/src/router-window-navigation.test.ts +457 -0
  98. package/src/router.ts +289 -470
  99. package/src/types.ts +341 -0
  100. package/src/util.test.ts +1262 -0
  101. package/src/util.ts +116 -0
  102. package/dist/history/abstract.d.ts +0 -29
  103. package/dist/history/abstract.mjs +0 -107
  104. package/dist/history/base.d.ts +0 -79
  105. package/dist/history/base.mjs +0 -275
  106. package/dist/history/html.d.ts +0 -30
  107. package/dist/history/html.mjs +0 -183
  108. package/dist/history/index.d.ts +0 -7
  109. package/dist/history/index.mjs +0 -16
  110. package/dist/matcher/create-matcher.d.ts +0 -5
  111. package/dist/matcher/create-matcher.mjs +0 -218
  112. package/dist/matcher/create-matcher.spec.mjs +0 -0
  113. package/dist/matcher/index.d.ts +0 -1
  114. package/dist/matcher/index.mjs +0 -1
  115. package/dist/task-pipe/index.d.ts +0 -1
  116. package/dist/task-pipe/index.mjs +0 -1
  117. package/dist/task-pipe/task.d.ts +0 -30
  118. package/dist/task-pipe/task.mjs +0 -66
  119. package/dist/types/index.d.ts +0 -694
  120. package/dist/types/index.mjs +0 -6
  121. package/dist/utils/bom.d.ts +0 -5
  122. package/dist/utils/bom.mjs +0 -10
  123. package/dist/utils/encoding.d.ts +0 -48
  124. package/dist/utils/encoding.mjs +0 -44
  125. package/dist/utils/guards.d.ts +0 -9
  126. package/dist/utils/guards.mjs +0 -12
  127. package/dist/utils/index.d.ts +0 -7
  128. package/dist/utils/index.mjs +0 -27
  129. package/dist/utils/path.d.ts +0 -60
  130. package/dist/utils/path.mjs +0 -282
  131. package/dist/utils/path.spec.mjs +0 -27
  132. package/dist/utils/scroll.d.ts +0 -25
  133. package/dist/utils/scroll.mjs +0 -59
  134. package/dist/utils/utils.d.ts +0 -16
  135. package/dist/utils/utils.mjs +0 -11
  136. package/dist/utils/warn.d.ts +0 -2
  137. package/dist/utils/warn.mjs +0 -12
  138. package/src/history/abstract.ts +0 -149
  139. package/src/history/base.ts +0 -408
  140. package/src/history/html.ts +0 -228
  141. package/src/history/index.ts +0 -20
  142. package/src/matcher/create-matcher.spec.ts +0 -3
  143. package/src/matcher/create-matcher.ts +0 -292
  144. package/src/matcher/index.ts +0 -1
  145. package/src/task-pipe/index.ts +0 -1
  146. package/src/task-pipe/task.ts +0 -97
  147. package/src/types/index.ts +0 -858
  148. package/src/utils/bom.ts +0 -14
  149. package/src/utils/encoding.ts +0 -153
  150. package/src/utils/guards.ts +0 -25
  151. package/src/utils/index.ts +0 -27
  152. package/src/utils/path.spec.ts +0 -32
  153. package/src/utils/path.ts +0 -418
  154. package/src/utils/scroll.ts +0 -120
  155. package/src/utils/utils.ts +0 -30
  156. package/src/utils/warn.ts +0 -13
  157. /package/dist/{matcher/create-matcher.spec.d.ts → index.test.d.ts} +0 -0
  158. /package/dist/{utils/path.spec.d.ts → matcher.test.d.ts} +0 -0
package/src/route.ts ADDED
@@ -0,0 +1,308 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { parseLocation } from './location';
3
+ import { parsedOptions } from './options';
4
+ import type { Router } from './router';
5
+
6
+ import {
7
+ type RouteConfirmHook,
8
+ type RouteHandleHook,
9
+ type RouteHandleResult,
10
+ type RouteLayerOptions,
11
+ type RouteLocationInput,
12
+ type RouteMatchResult,
13
+ type RouteMeta,
14
+ type RouteOptions,
15
+ type RouteParsedConfig,
16
+ type RouteState,
17
+ RouteType,
18
+ type RouterParsedOptions
19
+ } from './types';
20
+ import { isNonEmptyPlainObject, isPlainObject } from './util';
21
+
22
+ /**
23
+ * Configuration for non-enumerable properties in Route class
24
+ * These properties will be hidden during object traversal and serialization
25
+ */
26
+ export const NON_ENUMERABLE_PROPERTIES = [
27
+ // Private fields - internal implementation details
28
+ '_handled',
29
+ '_handle',
30
+ '_handleResult',
31
+ '_options',
32
+
33
+ // SSR-specific properties - meaningless in client environment
34
+ 'req',
35
+ 'res',
36
+
37
+ // Internal context - used by framework internally
38
+ 'context',
39
+
40
+ // Status code - internal status information
41
+ 'statusCode',
42
+
43
+ // Route behavior overrides - framework internal logic
44
+ 'confirm',
45
+
46
+ // Layer configuration - used for layer routes
47
+ 'layer'
48
+ ] satisfies string[];
49
+
50
+ /**
51
+ * Append user-provided parameters to URL path
52
+ * @param match Route matching result
53
+ * @param toInput User-provided route location object
54
+ * @param base Base URL
55
+ * @param to Current parsed URL object
56
+ */
57
+ export function applyRouteParams(
58
+ match: RouteMatchResult,
59
+ toInput: RouteLocationInput,
60
+ base: URL,
61
+ to: URL
62
+ ): void {
63
+ if (
64
+ !isPlainObject(toInput) ||
65
+ !isNonEmptyPlainObject(toInput.params) ||
66
+ !match.matches.length
67
+ ) {
68
+ return;
69
+ }
70
+
71
+ // Get the last matched route configuration
72
+ const lastMatch = match.matches[match.matches.length - 1];
73
+
74
+ // Split current path
75
+ const current = to.pathname.split('/');
76
+
77
+ // Compile new path with user parameters and split
78
+ const next = new URL(
79
+ lastMatch.compile(toInput.params).substring(1),
80
+ base
81
+ ).pathname.split('/');
82
+
83
+ // Replace current path segments with new path segments
84
+ next.forEach((item, index) => {
85
+ current[index] = item || current[index];
86
+ });
87
+
88
+ // Update URL path
89
+ to.pathname = current.join('/');
90
+
91
+ // Merge parameters to match result, user parameters take precedence
92
+ Object.assign(match.params, toInput.params);
93
+ }
94
+
95
+ /**
96
+ * Route class provides complete route object functionality
97
+ */
98
+ export class Route {
99
+ // Private fields for handle validation
100
+ private _handled = false;
101
+ private _handle: RouteHandleHook | null = null;
102
+ private _handleResult: RouteHandleResult | null = null;
103
+ private readonly _options: RouterParsedOptions;
104
+
105
+ // Public properties
106
+ public readonly statusCode: number | null = null;
107
+ public readonly state: RouteState;
108
+ public readonly keepScrollPosition: boolean;
109
+ /** Custom confirm handler that overrides default route-transition confirm logic */
110
+ public readonly confirm: RouteConfirmHook | null;
111
+ /** Layer configuration for layer routes */
112
+ public readonly layer: RouteLayerOptions | null;
113
+
114
+ // Read-only properties
115
+ public readonly type: RouteType;
116
+ public readonly req: IncomingMessage | null;
117
+ public readonly res: ServerResponse | null;
118
+ public readonly context: Record<string | symbol, any>;
119
+ public readonly url: URL;
120
+ public readonly path: string;
121
+ public readonly fullPath: string;
122
+ public readonly hash: string;
123
+ public readonly params: Record<string, string> = {};
124
+ public readonly query: Record<string, string | undefined> = {};
125
+ public readonly queryArray: Record<string, string[] | undefined> = {};
126
+ public readonly meta: RouteMeta;
127
+ public readonly matched: readonly RouteParsedConfig[];
128
+ public readonly config: RouteParsedConfig | null;
129
+
130
+ constructor(routeOptions: Partial<RouteOptions> = {}) {
131
+ const {
132
+ toType = RouteType.push,
133
+ toInput = '/',
134
+ from = null,
135
+ options = parsedOptions()
136
+ } = routeOptions;
137
+
138
+ this._options = options;
139
+ this.type = toType;
140
+ this.req = options.req;
141
+ this.res = options.res;
142
+ this.context = options.context;
143
+
144
+ const base = options.base;
145
+ const to = options.normalizeURL(parseLocation(toInput, base), from);
146
+ const isSameOrigin = to.origin === base.origin;
147
+ const isSameBase = to.pathname.startsWith(base.pathname);
148
+ const match =
149
+ isSameOrigin && isSameBase ? options.matcher(to, base) : null;
150
+
151
+ this.url = to;
152
+ this.path = match
153
+ ? to.pathname.substring(base.pathname.length - 1)
154
+ : to.pathname;
155
+ this.fullPath = (match ? this.path : to.pathname) + to.search + to.hash;
156
+ this.matched = match ? match.matches : Object.freeze([]);
157
+ this.keepScrollPosition = isPlainObject(toInput)
158
+ ? Boolean(toInput.keepScrollPosition)
159
+ : false;
160
+ this.confirm =
161
+ isPlainObject(toInput) && toInput.confirm ? toInput.confirm : null;
162
+ this.layer =
163
+ toType === RouteType.pushLayer &&
164
+ isPlainObject(toInput) &&
165
+ toInput.layer
166
+ ? toInput.layer
167
+ : null;
168
+ this.config =
169
+ this.matched.length > 0
170
+ ? this.matched[this.matched.length - 1]
171
+ : null;
172
+ this.meta = this.config?.meta || {};
173
+
174
+ // Initialize state object - create new local object, merge externally passed state
175
+ const state: RouteState = {};
176
+ if (isPlainObject(toInput) && toInput.state) {
177
+ Object.assign(state, toInput.state);
178
+ }
179
+ this.state = state;
180
+
181
+ // Process query parameters
182
+ for (const key of new Set(to.searchParams.keys())) {
183
+ this.query[key] = to.searchParams.get(key)!;
184
+ this.queryArray[key] = to.searchParams.getAll(key);
185
+ }
186
+ this.hash = to.hash;
187
+
188
+ // Apply user-provided route parameters (if match is successful)
189
+ if (match) {
190
+ applyRouteParams(match, toInput, base, to);
191
+ // Assign matched parameters to route object
192
+ Object.assign(this.params, match.params);
193
+ }
194
+
195
+ // Set status code
196
+ // Prioritize user-provided statusCode
197
+ if (isPlainObject(toInput) && typeof toInput.statusCode === 'number') {
198
+ this.statusCode = toInput.statusCode;
199
+ }
200
+ // If statusCode is not provided, keep default null value
201
+
202
+ // Configure property enumerability
203
+ // Set internal implementation details as non-enumerable, keep user-common properties enumerable
204
+ // Set specified properties as non-enumerable according to configuration
205
+ for (const property of NON_ENUMERABLE_PROPERTIES) {
206
+ Object.defineProperty(this, property, { enumerable: false });
207
+ }
208
+ }
209
+
210
+ get isPush(): boolean {
211
+ return this.type.startsWith('push');
212
+ }
213
+
214
+ // handle related getter/setter
215
+ get handle(): RouteHandleHook | null {
216
+ return this._handle;
217
+ }
218
+
219
+ set handle(val: RouteHandleHook | null) {
220
+ this.setHandle(val);
221
+ }
222
+
223
+ get handleResult(): RouteHandleResult | null {
224
+ return this._handleResult;
225
+ }
226
+
227
+ set handleResult(val: RouteHandleResult | null) {
228
+ this._handleResult = val;
229
+ }
230
+
231
+ /**
232
+ * Set handle function with validation logic wrapper
233
+ */
234
+ setHandle(val: RouteHandleHook | null): void {
235
+ if (typeof val !== 'function') {
236
+ this._handle = null;
237
+ return;
238
+ }
239
+ const self = this;
240
+ this._handle = function handle(
241
+ this: Route,
242
+ to: Route,
243
+ from: Route | null,
244
+ router: Router
245
+ ) {
246
+ if (self._handled) {
247
+ throw new Error(
248
+ 'Route handle hook can only be called once per navigation'
249
+ );
250
+ }
251
+ self._handled = true;
252
+ return val.call(this, to, from, router);
253
+ };
254
+ }
255
+
256
+ /**
257
+ * Apply navigation-generated state to current route
258
+ * Used by route handlers to add system state like pageId
259
+ * @param navigationState Navigation-generated state to apply
260
+ */
261
+ applyNavigationState(navigationState: Partial<RouteState>): void {
262
+ Object.assign(this.state, navigationState);
263
+ }
264
+
265
+ /**
266
+ * Sync all properties of current route to target route object
267
+ * Used for route object updates in reactive systems
268
+ * @param targetRoute Target route object
269
+ */
270
+ syncTo(targetRoute: Route): void {
271
+ // Copy enumerable properties
272
+ Object.assign(targetRoute, this);
273
+
274
+ // Copy non-enumerable properties - type-safe property copying
275
+ for (const property of NON_ENUMERABLE_PROPERTIES) {
276
+ if (!(property in this && property in targetRoute)) continue;
277
+ // Use Reflect.set for type-safe property setting
278
+ const value = Reflect.get(this, property);
279
+ Reflect.set(targetRoute, property, value);
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Clone current route instance
285
+ * Returns a new Route instance with same configuration and state
286
+ */
287
+ clone(): Route {
288
+ // Reconstruct route object, passing current state and confirm handler
289
+ const toInput: RouteLocationInput = {
290
+ path: this.fullPath,
291
+ state: { ...this.state },
292
+ ...(this.confirm && { confirm: this.confirm }),
293
+ ...(this.layer && { layer: this.layer }),
294
+ ...(this.statusCode !== null && { statusCode: this.statusCode })
295
+ };
296
+
297
+ // Get original options from constructor's finalOptions
298
+ const options = this._options;
299
+
300
+ const clonedRoute = new Route({
301
+ options,
302
+ toType: this.type,
303
+ toInput
304
+ });
305
+
306
+ return clonedRoute;
307
+ }
308
+ }