@tanstack/router-core 1.167.0 → 1.167.2

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 (166) hide show
  1. package/dist/cjs/Matches.cjs +15 -12
  2. package/dist/cjs/Matches.cjs.map +1 -1
  3. package/dist/cjs/_virtual/_rolldown/runtime.cjs +23 -0
  4. package/dist/cjs/config.cjs +9 -8
  5. package/dist/cjs/config.cjs.map +1 -1
  6. package/dist/cjs/defer.cjs +37 -21
  7. package/dist/cjs/defer.cjs.map +1 -1
  8. package/dist/cjs/index.cjs +87 -89
  9. package/dist/cjs/isServer/client.cjs +5 -3
  10. package/dist/cjs/isServer/client.cjs.map +1 -1
  11. package/dist/cjs/isServer/development.cjs +5 -3
  12. package/dist/cjs/isServer/development.cjs.map +1 -1
  13. package/dist/cjs/isServer/server.cjs +5 -3
  14. package/dist/cjs/isServer/server.cjs.map +1 -1
  15. package/dist/cjs/link.cjs +5 -4
  16. package/dist/cjs/link.cjs.map +1 -1
  17. package/dist/cjs/load-matches.cjs +619 -766
  18. package/dist/cjs/load-matches.cjs.map +1 -1
  19. package/dist/cjs/lru-cache.cjs +67 -64
  20. package/dist/cjs/lru-cache.cjs.map +1 -1
  21. package/dist/cjs/new-process-route-tree.cjs +707 -792
  22. package/dist/cjs/new-process-route-tree.cjs.map +1 -1
  23. package/dist/cjs/not-found.cjs +20 -7
  24. package/dist/cjs/not-found.cjs.map +1 -1
  25. package/dist/cjs/path.cjs +221 -232
  26. package/dist/cjs/path.cjs.map +1 -1
  27. package/dist/cjs/qss.cjs +62 -28
  28. package/dist/cjs/qss.cjs.map +1 -1
  29. package/dist/cjs/redirect.cjs +44 -30
  30. package/dist/cjs/redirect.cjs.map +1 -1
  31. package/dist/cjs/rewrite.cjs +56 -56
  32. package/dist/cjs/rewrite.cjs.map +1 -1
  33. package/dist/cjs/root.cjs +6 -4
  34. package/dist/cjs/root.cjs.map +1 -1
  35. package/dist/cjs/route.cjs +96 -105
  36. package/dist/cjs/route.cjs.map +1 -1
  37. package/dist/cjs/router.cjs +1153 -1524
  38. package/dist/cjs/router.cjs.map +1 -1
  39. package/dist/cjs/router.d.cts +2 -0
  40. package/dist/cjs/scroll-restoration.cjs +189 -207
  41. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  42. package/dist/cjs/searchMiddleware.cjs +48 -37
  43. package/dist/cjs/searchMiddleware.cjs.map +1 -1
  44. package/dist/cjs/searchParams.cjs +57 -45
  45. package/dist/cjs/searchParams.cjs.map +1 -1
  46. package/dist/cjs/ssr/client.cjs +6 -8
  47. package/dist/cjs/ssr/constants.cjs +6 -5
  48. package/dist/cjs/ssr/constants.cjs.map +1 -1
  49. package/dist/cjs/ssr/createRequestHandler.cjs +41 -59
  50. package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
  51. package/dist/cjs/ssr/handlerCallback.cjs +5 -4
  52. package/dist/cjs/ssr/handlerCallback.cjs.map +1 -1
  53. package/dist/cjs/ssr/headers.cjs +17 -26
  54. package/dist/cjs/ssr/headers.cjs.map +1 -1
  55. package/dist/cjs/ssr/json.cjs +8 -4
  56. package/dist/cjs/ssr/json.cjs.map +1 -1
  57. package/dist/cjs/ssr/serializer/RawStream.cjs +268 -268
  58. package/dist/cjs/ssr/serializer/RawStream.cjs.map +1 -1
  59. package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs +31 -32
  60. package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs.map +1 -1
  61. package/dist/cjs/ssr/serializer/seroval-plugins.cjs +12 -12
  62. package/dist/cjs/ssr/serializer/seroval-plugins.cjs.map +1 -1
  63. package/dist/cjs/ssr/serializer/transformer.cjs +45 -41
  64. package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -1
  65. package/dist/cjs/ssr/server.cjs +12 -14
  66. package/dist/cjs/ssr/ssr-client.cjs +173 -211
  67. package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
  68. package/dist/cjs/ssr/ssr-match-id.cjs +6 -5
  69. package/dist/cjs/ssr/ssr-match-id.cjs.map +1 -1
  70. package/dist/cjs/ssr/ssr-server.cjs +266 -300
  71. package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
  72. package/dist/cjs/ssr/transformStreamWithRouter.cjs +317 -337
  73. package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -1
  74. package/dist/cjs/ssr/tsrScript.cjs +6 -4
  75. package/dist/cjs/ssr/tsrScript.cjs.map +1 -1
  76. package/dist/cjs/ssr/tsrScript.d.cts +1 -0
  77. package/dist/cjs/utils/batch.cjs +13 -13
  78. package/dist/cjs/utils/batch.cjs.map +1 -1
  79. package/dist/cjs/utils.cjs +274 -208
  80. package/dist/cjs/utils.cjs.map +1 -1
  81. package/dist/esm/Matches.js +16 -13
  82. package/dist/esm/Matches.js.map +1 -1
  83. package/dist/esm/config.js +10 -9
  84. package/dist/esm/config.js.map +1 -1
  85. package/dist/esm/defer.js +37 -22
  86. package/dist/esm/defer.js.map +1 -1
  87. package/dist/esm/index.js +12 -82
  88. package/dist/esm/isServer/client.js +6 -5
  89. package/dist/esm/isServer/client.js.map +1 -1
  90. package/dist/esm/isServer/development.js +6 -5
  91. package/dist/esm/isServer/development.js.map +1 -1
  92. package/dist/esm/isServer/server.js +6 -5
  93. package/dist/esm/isServer/server.js.map +1 -1
  94. package/dist/esm/link.js +6 -5
  95. package/dist/esm/link.js.map +1 -1
  96. package/dist/esm/load-matches.js +614 -765
  97. package/dist/esm/load-matches.js.map +1 -1
  98. package/dist/esm/lru-cache.js +68 -65
  99. package/dist/esm/lru-cache.js.map +1 -1
  100. package/dist/esm/new-process-route-tree.js +705 -797
  101. package/dist/esm/new-process-route-tree.js.map +1 -1
  102. package/dist/esm/not-found.js +21 -9
  103. package/dist/esm/not-found.js.map +1 -1
  104. package/dist/esm/path.js +220 -241
  105. package/dist/esm/path.js.map +1 -1
  106. package/dist/esm/qss.js +63 -30
  107. package/dist/esm/qss.js.map +1 -1
  108. package/dist/esm/redirect.js +45 -34
  109. package/dist/esm/redirect.js.map +1 -1
  110. package/dist/esm/rewrite.js +57 -60
  111. package/dist/esm/rewrite.js.map +1 -1
  112. package/dist/esm/root.js +7 -5
  113. package/dist/esm/root.js.map +1 -1
  114. package/dist/esm/route.js +92 -105
  115. package/dist/esm/route.js.map +1 -1
  116. package/dist/esm/router.d.ts +2 -0
  117. package/dist/esm/router.js +1147 -1527
  118. package/dist/esm/router.js.map +1 -1
  119. package/dist/esm/scroll-restoration.js +188 -213
  120. package/dist/esm/scroll-restoration.js.map +1 -1
  121. package/dist/esm/searchMiddleware.js +48 -38
  122. package/dist/esm/searchMiddleware.js.map +1 -1
  123. package/dist/esm/searchParams.js +57 -48
  124. package/dist/esm/searchParams.js.map +1 -1
  125. package/dist/esm/ssr/client.js +1 -6
  126. package/dist/esm/ssr/constants.js +7 -7
  127. package/dist/esm/ssr/constants.js.map +1 -1
  128. package/dist/esm/ssr/createRequestHandler.js +39 -58
  129. package/dist/esm/ssr/createRequestHandler.js.map +1 -1
  130. package/dist/esm/ssr/handlerCallback.js +6 -5
  131. package/dist/esm/ssr/handlerCallback.js.map +1 -1
  132. package/dist/esm/ssr/headers.js +16 -26
  133. package/dist/esm/ssr/headers.js.map +1 -1
  134. package/dist/esm/ssr/json.js +9 -5
  135. package/dist/esm/ssr/json.js.map +1 -1
  136. package/dist/esm/ssr/serializer/RawStream.js +267 -273
  137. package/dist/esm/ssr/serializer/RawStream.js.map +1 -1
  138. package/dist/esm/ssr/serializer/ShallowErrorPlugin.js +31 -32
  139. package/dist/esm/ssr/serializer/ShallowErrorPlugin.js.map +1 -1
  140. package/dist/esm/ssr/serializer/seroval-plugins.js +10 -11
  141. package/dist/esm/ssr/serializer/seroval-plugins.js.map +1 -1
  142. package/dist/esm/ssr/serializer/transformer.js +44 -43
  143. package/dist/esm/ssr/serializer/transformer.js.map +1 -1
  144. package/dist/esm/ssr/server.js +2 -12
  145. package/dist/esm/ssr/ssr-client.js +169 -209
  146. package/dist/esm/ssr/ssr-client.js.map +1 -1
  147. package/dist/esm/ssr/ssr-match-id.js +7 -7
  148. package/dist/esm/ssr/ssr-match-id.js.map +1 -1
  149. package/dist/esm/ssr/ssr-server.js +262 -300
  150. package/dist/esm/ssr/ssr-server.js.map +1 -1
  151. package/dist/esm/ssr/transformStreamWithRouter.js +315 -338
  152. package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -1
  153. package/dist/esm/ssr/tsrScript.js +6 -5
  154. package/dist/esm/ssr/tsrScript.js.map +1 -1
  155. package/dist/esm/utils/batch.js +13 -14
  156. package/dist/esm/utils/batch.js.map +1 -1
  157. package/dist/esm/utils.js +273 -224
  158. package/dist/esm/utils.js.map +1 -1
  159. package/package.json +2 -2
  160. package/src/router.ts +2 -0
  161. package/dist/cjs/index.cjs.map +0 -1
  162. package/dist/cjs/ssr/client.cjs.map +0 -1
  163. package/dist/cjs/ssr/server.cjs.map +0 -1
  164. package/dist/esm/index.js.map +0 -1
  165. package/dist/esm/ssr/client.js.map +0 -1
  166. package/dist/esm/ssr/server.js.map +0 -1
package/dist/esm/utils.js CHANGED
@@ -1,267 +1,316 @@
1
1
  import { isServer } from "@tanstack/router-core/isServer";
2
+ //#region src/utils.ts
3
+ /**
4
+ * Return the last element of an array.
5
+ * Intended for non-empty arrays used within router internals.
6
+ */
2
7
  function last(arr) {
3
- return arr[arr.length - 1];
8
+ return arr[arr.length - 1];
4
9
  }
5
10
  function isFunction(d) {
6
- return typeof d === "function";
11
+ return typeof d === "function";
7
12
  }
13
+ /**
14
+ * Apply a value-or-updater to a previous value.
15
+ * Accepts either a literal value or a function of the previous value.
16
+ */
8
17
  function functionalUpdate(updater, previous) {
9
- if (isFunction(updater)) {
10
- return updater(previous);
11
- }
12
- return updater;
18
+ if (isFunction(updater)) return updater(previous);
19
+ return updater;
13
20
  }
14
- const hasOwn = Object.prototype.hasOwnProperty;
15
- const isEnumerable = Object.prototype.propertyIsEnumerable;
16
- const createNull = () => /* @__PURE__ */ Object.create(null);
17
- const nullReplaceEqualDeep = (prev, next) => replaceEqualDeep(prev, next, createNull);
21
+ var hasOwn = Object.prototype.hasOwnProperty;
22
+ var isEnumerable = Object.prototype.propertyIsEnumerable;
23
+ var createNull = () => Object.create(null);
24
+ var nullReplaceEqualDeep = (prev, next) => replaceEqualDeep(prev, next, createNull);
25
+ /**
26
+ * This function returns `prev` if `_next` is deeply equal.
27
+ * If not, it will replace any deeply equal children of `b` with those of `a`.
28
+ * This can be used for structural sharing between immutable JSON values for example.
29
+ * Do not use this with signals
30
+ */
18
31
  function replaceEqualDeep(prev, _next, _makeObj = () => ({}), _depth = 0) {
19
- if (isServer) {
20
- return _next;
21
- }
22
- if (prev === _next) {
23
- return prev;
24
- }
25
- if (_depth > 500) return _next;
26
- const next = _next;
27
- const array = isPlainArray(prev) && isPlainArray(next);
28
- if (!array && !(isPlainObject(prev) && isPlainObject(next))) return next;
29
- const prevItems = array ? prev : getEnumerableOwnKeys(prev);
30
- if (!prevItems) return next;
31
- const nextItems = array ? next : getEnumerableOwnKeys(next);
32
- if (!nextItems) return next;
33
- const prevSize = prevItems.length;
34
- const nextSize = nextItems.length;
35
- const copy = array ? new Array(nextSize) : _makeObj();
36
- let equalItems = 0;
37
- for (let i = 0; i < nextSize; i++) {
38
- const key = array ? i : nextItems[i];
39
- const p = prev[key];
40
- const n = next[key];
41
- if (p === n) {
42
- copy[key] = p;
43
- if (array ? i < prevSize : hasOwn.call(prev, key)) equalItems++;
44
- continue;
45
- }
46
- if (p === null || n === null || typeof p !== "object" || typeof n !== "object") {
47
- copy[key] = n;
48
- continue;
49
- }
50
- const v = replaceEqualDeep(p, n, _makeObj, _depth + 1);
51
- copy[key] = v;
52
- if (v === p) equalItems++;
53
- }
54
- return prevSize === nextSize && equalItems === prevSize ? prev : copy;
32
+ if (isServer) return _next;
33
+ if (prev === _next) return prev;
34
+ if (_depth > 500) return _next;
35
+ const next = _next;
36
+ const array = isPlainArray(prev) && isPlainArray(next);
37
+ if (!array && !(isPlainObject(prev) && isPlainObject(next))) return next;
38
+ const prevItems = array ? prev : getEnumerableOwnKeys(prev);
39
+ if (!prevItems) return next;
40
+ const nextItems = array ? next : getEnumerableOwnKeys(next);
41
+ if (!nextItems) return next;
42
+ const prevSize = prevItems.length;
43
+ const nextSize = nextItems.length;
44
+ const copy = array ? new Array(nextSize) : _makeObj();
45
+ let equalItems = 0;
46
+ for (let i = 0; i < nextSize; i++) {
47
+ const key = array ? i : nextItems[i];
48
+ const p = prev[key];
49
+ const n = next[key];
50
+ if (p === n) {
51
+ copy[key] = p;
52
+ if (array ? i < prevSize : hasOwn.call(prev, key)) equalItems++;
53
+ continue;
54
+ }
55
+ if (p === null || n === null || typeof p !== "object" || typeof n !== "object") {
56
+ copy[key] = n;
57
+ continue;
58
+ }
59
+ const v = replaceEqualDeep(p, n, _makeObj, _depth + 1);
60
+ copy[key] = v;
61
+ if (v === p) equalItems++;
62
+ }
63
+ return prevSize === nextSize && equalItems === prevSize ? prev : copy;
55
64
  }
65
+ /**
66
+ * Equivalent to `Reflect.ownKeys`, but ensures that objects are "clone-friendly":
67
+ * will return false if object has any non-enumerable properties.
68
+ *
69
+ * Optimized for the common case where objects have no symbol properties.
70
+ */
56
71
  function getEnumerableOwnKeys(o) {
57
- const names = Object.getOwnPropertyNames(o);
58
- for (const name of names) {
59
- if (!isEnumerable.call(o, name)) return false;
60
- }
61
- const symbols = Object.getOwnPropertySymbols(o);
62
- if (symbols.length === 0) return names;
63
- const keys = names;
64
- for (const symbol of symbols) {
65
- if (!isEnumerable.call(o, symbol)) return false;
66
- keys.push(symbol);
67
- }
68
- return keys;
72
+ const names = Object.getOwnPropertyNames(o);
73
+ for (const name of names) if (!isEnumerable.call(o, name)) return false;
74
+ const symbols = Object.getOwnPropertySymbols(o);
75
+ if (symbols.length === 0) return names;
76
+ const keys = names;
77
+ for (const symbol of symbols) {
78
+ if (!isEnumerable.call(o, symbol)) return false;
79
+ keys.push(symbol);
80
+ }
81
+ return keys;
69
82
  }
70
83
  function isPlainObject(o) {
71
- if (!hasObjectPrototype(o)) {
72
- return false;
73
- }
74
- const ctor = o.constructor;
75
- if (typeof ctor === "undefined") {
76
- return true;
77
- }
78
- const prot = ctor.prototype;
79
- if (!hasObjectPrototype(prot)) {
80
- return false;
81
- }
82
- if (!prot.hasOwnProperty("isPrototypeOf")) {
83
- return false;
84
- }
85
- return true;
84
+ if (!hasObjectPrototype(o)) return false;
85
+ const ctor = o.constructor;
86
+ if (typeof ctor === "undefined") return true;
87
+ const prot = ctor.prototype;
88
+ if (!hasObjectPrototype(prot)) return false;
89
+ if (!prot.hasOwnProperty("isPrototypeOf")) return false;
90
+ return true;
86
91
  }
87
92
  function hasObjectPrototype(o) {
88
- return Object.prototype.toString.call(o) === "[object Object]";
93
+ return Object.prototype.toString.call(o) === "[object Object]";
89
94
  }
95
+ /**
96
+ * Check if a value is a "plain" array (no extra enumerable keys).
97
+ */
90
98
  function isPlainArray(value) {
91
- return Array.isArray(value) && value.length === Object.keys(value).length;
99
+ return Array.isArray(value) && value.length === Object.keys(value).length;
92
100
  }
101
+ /**
102
+ * Perform a deep equality check with options for partial comparison and
103
+ * ignoring `undefined` values. Optimized for router state comparisons.
104
+ */
93
105
  function deepEqual(a, b, opts) {
94
- if (a === b) {
95
- return true;
96
- }
97
- if (typeof a !== typeof b) {
98
- return false;
99
- }
100
- if (Array.isArray(a) && Array.isArray(b)) {
101
- if (a.length !== b.length) return false;
102
- for (let i = 0, l = a.length; i < l; i++) {
103
- if (!deepEqual(a[i], b[i], opts)) return false;
104
- }
105
- return true;
106
- }
107
- if (isPlainObject(a) && isPlainObject(b)) {
108
- const ignoreUndefined = opts?.ignoreUndefined ?? true;
109
- if (opts?.partial) {
110
- for (const k in b) {
111
- if (!ignoreUndefined || b[k] !== void 0) {
112
- if (!deepEqual(a[k], b[k], opts)) return false;
113
- }
114
- }
115
- return true;
116
- }
117
- let aCount = 0;
118
- if (!ignoreUndefined) {
119
- aCount = Object.keys(a).length;
120
- } else {
121
- for (const k in a) {
122
- if (a[k] !== void 0) aCount++;
123
- }
124
- }
125
- let bCount = 0;
126
- for (const k in b) {
127
- if (!ignoreUndefined || b[k] !== void 0) {
128
- bCount++;
129
- if (bCount > aCount || !deepEqual(a[k], b[k], opts)) return false;
130
- }
131
- }
132
- return aCount === bCount;
133
- }
134
- return false;
106
+ if (a === b) return true;
107
+ if (typeof a !== typeof b) return false;
108
+ if (Array.isArray(a) && Array.isArray(b)) {
109
+ if (a.length !== b.length) return false;
110
+ for (let i = 0, l = a.length; i < l; i++) if (!deepEqual(a[i], b[i], opts)) return false;
111
+ return true;
112
+ }
113
+ if (isPlainObject(a) && isPlainObject(b)) {
114
+ const ignoreUndefined = opts?.ignoreUndefined ?? true;
115
+ if (opts?.partial) {
116
+ for (const k in b) if (!ignoreUndefined || b[k] !== void 0) {
117
+ if (!deepEqual(a[k], b[k], opts)) return false;
118
+ }
119
+ return true;
120
+ }
121
+ let aCount = 0;
122
+ if (!ignoreUndefined) aCount = Object.keys(a).length;
123
+ else for (const k in a) if (a[k] !== void 0) aCount++;
124
+ let bCount = 0;
125
+ for (const k in b) if (!ignoreUndefined || b[k] !== void 0) {
126
+ bCount++;
127
+ if (bCount > aCount || !deepEqual(a[k], b[k], opts)) return false;
128
+ }
129
+ return aCount === bCount;
130
+ }
131
+ return false;
135
132
  }
133
+ /**
134
+ * Create a promise with exposed resolve/reject and status fields.
135
+ * Useful for coordinating async router lifecycle operations.
136
+ */
136
137
  function createControlledPromise(onResolve) {
137
- let resolveLoadPromise;
138
- let rejectLoadPromise;
139
- const controlledPromise = new Promise((resolve, reject) => {
140
- resolveLoadPromise = resolve;
141
- rejectLoadPromise = reject;
142
- });
143
- controlledPromise.status = "pending";
144
- controlledPromise.resolve = (value) => {
145
- controlledPromise.status = "resolved";
146
- controlledPromise.value = value;
147
- resolveLoadPromise(value);
148
- onResolve?.(value);
149
- };
150
- controlledPromise.reject = (e) => {
151
- controlledPromise.status = "rejected";
152
- rejectLoadPromise(e);
153
- };
154
- return controlledPromise;
138
+ let resolveLoadPromise;
139
+ let rejectLoadPromise;
140
+ const controlledPromise = new Promise((resolve, reject) => {
141
+ resolveLoadPromise = resolve;
142
+ rejectLoadPromise = reject;
143
+ });
144
+ controlledPromise.status = "pending";
145
+ controlledPromise.resolve = (value) => {
146
+ controlledPromise.status = "resolved";
147
+ controlledPromise.value = value;
148
+ resolveLoadPromise(value);
149
+ onResolve?.(value);
150
+ };
151
+ controlledPromise.reject = (e) => {
152
+ controlledPromise.status = "rejected";
153
+ rejectLoadPromise(e);
154
+ };
155
+ return controlledPromise;
155
156
  }
157
+ /**
158
+ * Heuristically detect dynamic import "module not found" errors
159
+ * across major browsers for lazy route component handling.
160
+ */
156
161
  function isModuleNotFoundError(error) {
157
- if (typeof error?.message !== "string") return false;
158
- return error.message.startsWith("Failed to fetch dynamically imported module") || error.message.startsWith("error loading dynamically imported module") || error.message.startsWith("Importing a module script failed");
162
+ if (typeof error?.message !== "string") return false;
163
+ return error.message.startsWith("Failed to fetch dynamically imported module") || error.message.startsWith("error loading dynamically imported module") || error.message.startsWith("Importing a module script failed");
159
164
  }
160
165
  function isPromise(value) {
161
- return Boolean(
162
- value && typeof value === "object" && typeof value.then === "function"
163
- );
166
+ return Boolean(value && typeof value === "object" && typeof value.then === "function");
164
167
  }
165
168
  function findLast(array, predicate) {
166
- for (let i = array.length - 1; i >= 0; i--) {
167
- const item = array[i];
168
- if (predicate(item)) return item;
169
- }
170
- return void 0;
169
+ for (let i = array.length - 1; i >= 0; i--) {
170
+ const item = array[i];
171
+ if (predicate(item)) return item;
172
+ }
171
173
  }
174
+ /**
175
+ * Remove control characters that can cause open redirect vulnerabilities.
176
+ * Characters like \r (CR) and \n (LF) can trick URL parsers into interpreting
177
+ * paths like "/\r/evil.com" as "http://evil.com".
178
+ */
172
179
  function sanitizePathSegment(segment) {
173
- return segment.replace(/[\x00-\x1f\x7f]/g, "");
180
+ return segment.replace(/[\x00-\x1f\x7f]/g, "");
174
181
  }
175
182
  function decodeSegment(segment) {
176
- let decoded;
177
- try {
178
- decoded = decodeURI(segment);
179
- } catch {
180
- decoded = segment.replaceAll(/%[0-9A-F]{2}/gi, (match) => {
181
- try {
182
- return decodeURI(match);
183
- } catch {
184
- return match;
185
- }
186
- });
187
- }
188
- return sanitizePathSegment(decoded);
183
+ let decoded;
184
+ try {
185
+ decoded = decodeURI(segment);
186
+ } catch {
187
+ decoded = segment.replaceAll(/%[0-9A-F]{2}/gi, (match) => {
188
+ try {
189
+ return decodeURI(match);
190
+ } catch {
191
+ return match;
192
+ }
193
+ });
194
+ }
195
+ return sanitizePathSegment(decoded);
189
196
  }
190
- const DEFAULT_PROTOCOL_ALLOWLIST = [
191
- // Standard web navigation
192
- "http:",
193
- "https:",
194
- // Common browser-safe actions
195
- "mailto:",
196
- "tel:"
197
+ /**
198
+ * Default list of URL protocols to allow in links, redirects, and navigation.
199
+ * Any absolute URL protocol not in this list is treated as dangerous by default.
200
+ */
201
+ var DEFAULT_PROTOCOL_ALLOWLIST = [
202
+ "http:",
203
+ "https:",
204
+ "mailto:",
205
+ "tel:"
197
206
  ];
207
+ /**
208
+ * Check if a URL string uses a protocol that is not in the allowlist.
209
+ * Returns true for blocked protocols like javascript:, blob:, data:, etc.
210
+ *
211
+ * The URL constructor correctly normalizes:
212
+ * - Mixed case (JavaScript: → javascript:)
213
+ * - Whitespace/control characters (java\nscript: → javascript:)
214
+ * - Leading whitespace
215
+ *
216
+ * For relative URLs (no protocol), returns false (safe).
217
+ *
218
+ * @param url - The URL string to check
219
+ * @param allowlist - Set of protocols to allow
220
+ * @returns true if the URL uses a protocol that is not allowed
221
+ */
198
222
  function isDangerousProtocol(url, allowlist) {
199
- if (!url) return false;
200
- try {
201
- const parsed = new URL(url);
202
- return !allowlist.has(parsed.protocol);
203
- } catch {
204
- return false;
205
- }
223
+ if (!url) return false;
224
+ try {
225
+ const parsed = new URL(url);
226
+ return !allowlist.has(parsed.protocol);
227
+ } catch {
228
+ return false;
229
+ }
206
230
  }
207
- const HTML_ESCAPE_LOOKUP = {
208
- "&": "\\u0026",
209
- ">": "\\u003e",
210
- "<": "\\u003c",
211
- "\u2028": "\\u2028",
212
- "\u2029": "\\u2029"
231
+ var HTML_ESCAPE_LOOKUP = {
232
+ "&": "\\u0026",
233
+ ">": "\\u003e",
234
+ "<": "\\u003c",
235
+ "\u2028": "\\u2028",
236
+ "\u2029": "\\u2029"
213
237
  };
214
- const HTML_ESCAPE_REGEX = /[&><\u2028\u2029]/g;
238
+ var HTML_ESCAPE_REGEX = /[&><\u2028\u2029]/g;
239
+ /**
240
+ * Escape HTML special characters in a string to prevent XSS attacks
241
+ * when embedding strings in script tags during SSR.
242
+ *
243
+ * This is essential for preventing XSS vulnerabilities when user-controlled
244
+ * content is embedded in inline scripts.
245
+ */
215
246
  function escapeHtml(str) {
216
- return str.replace(HTML_ESCAPE_REGEX, (match) => HTML_ESCAPE_LOOKUP[match]);
247
+ return str.replace(HTML_ESCAPE_REGEX, (match) => HTML_ESCAPE_LOOKUP[match]);
217
248
  }
218
249
  function decodePath(path) {
219
- if (!path) return { path, handledProtocolRelativeURL: false };
220
- if (!/[%\\\x00-\x1f\x7f]/.test(path) && !path.startsWith("//")) {
221
- return { path, handledProtocolRelativeURL: false };
222
- }
223
- const re = /%25|%5C/gi;
224
- let cursor = 0;
225
- let result = "";
226
- let match;
227
- while (null !== (match = re.exec(path))) {
228
- result += decodeSegment(path.slice(cursor, match.index)) + match[0];
229
- cursor = re.lastIndex;
230
- }
231
- result = result + decodeSegment(cursor ? path.slice(cursor) : path);
232
- let handledProtocolRelativeURL = false;
233
- if (result.startsWith("//")) {
234
- handledProtocolRelativeURL = true;
235
- result = "/" + result.replace(/^\/+/, "");
236
- }
237
- return { path: result, handledProtocolRelativeURL };
250
+ if (!path) return {
251
+ path,
252
+ handledProtocolRelativeURL: false
253
+ };
254
+ if (!/[%\\\x00-\x1f\x7f]/.test(path) && !path.startsWith("//")) return {
255
+ path,
256
+ handledProtocolRelativeURL: false
257
+ };
258
+ const re = /%25|%5C/gi;
259
+ let cursor = 0;
260
+ let result = "";
261
+ let match;
262
+ while (null !== (match = re.exec(path))) {
263
+ result += decodeSegment(path.slice(cursor, match.index)) + match[0];
264
+ cursor = re.lastIndex;
265
+ }
266
+ result = result + decodeSegment(cursor ? path.slice(cursor) : path);
267
+ let handledProtocolRelativeURL = false;
268
+ if (result.startsWith("//")) {
269
+ handledProtocolRelativeURL = true;
270
+ result = "/" + result.replace(/^\/+/, "");
271
+ }
272
+ return {
273
+ path: result,
274
+ handledProtocolRelativeURL
275
+ };
238
276
  }
277
+ /**
278
+ * Encodes a path the same way `new URL()` would, but without the overhead of full URL parsing.
279
+ *
280
+ * This function encodes:
281
+ * - Whitespace characters (spaces → %20, tabs → %09, etc.)
282
+ * - Non-ASCII/Unicode characters (emojis, accented characters, etc.)
283
+ *
284
+ * It preserves:
285
+ * - Already percent-encoded sequences (won't double-encode %2F, %25, etc.)
286
+ * - ASCII special characters valid in URL paths (@, $, &, +, etc.)
287
+ * - Forward slashes as path separators
288
+ *
289
+ * Used to generate proper href values for SSR without constructing URL objects.
290
+ *
291
+ * @example
292
+ * encodePathLikeUrl('/path/file name.pdf') // '/path/file%20name.pdf'
293
+ * encodePathLikeUrl('/path/日本語') // '/path/%E6%97%A5%E6%9C%AC%E8%AA%9E'
294
+ * encodePathLikeUrl('/path/already%20encoded') // '/path/already%20encoded' (preserved)
295
+ */
239
296
  function encodePathLikeUrl(path) {
240
- if (!/\s|[^\u0000-\u007F]/.test(path)) return path;
241
- return path.replace(/\s|[^\u0000-\u007F]/gu, encodeURIComponent);
297
+ if (!/\s|[^\u0000-\u007F]/.test(path)) return path;
298
+ return path.replace(/\s|[^\u0000-\u007F]/gu, encodeURIComponent);
242
299
  }
300
+ /**
301
+ * Builds the dev-mode CSS styles URL for route-scoped CSS collection.
302
+ * Used by HeadContent components in all framework implementations to construct
303
+ * the URL for the `/@tanstack-start/styles.css` endpoint.
304
+ *
305
+ * @param basepath - The router's basepath (may or may not have leading slash)
306
+ * @param routeIds - Array of matched route IDs to include in the CSS collection
307
+ * @returns The full URL path for the dev styles CSS endpoint
308
+ */
243
309
  function buildDevStylesUrl(basepath, routeIds) {
244
- const trimmedBasepath = basepath.replace(/^\/+|\/+$/g, "");
245
- const normalizedBasepath = trimmedBasepath === "" ? "" : `/${trimmedBasepath}`;
246
- return `${normalizedBasepath}/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds.join(","))}`;
310
+ const trimmedBasepath = basepath.replace(/^\/+|\/+$/g, "");
311
+ return `${trimmedBasepath === "" ? "" : `/${trimmedBasepath}`}/@tanstack-start/styles.css?routes=${encodeURIComponent(routeIds.join(","))}`;
247
312
  }
248
- export {
249
- DEFAULT_PROTOCOL_ALLOWLIST,
250
- buildDevStylesUrl,
251
- createControlledPromise,
252
- decodePath,
253
- deepEqual,
254
- encodePathLikeUrl,
255
- escapeHtml,
256
- findLast,
257
- functionalUpdate,
258
- isDangerousProtocol,
259
- isModuleNotFoundError,
260
- isPlainArray,
261
- isPlainObject,
262
- isPromise,
263
- last,
264
- nullReplaceEqualDeep,
265
- replaceEqualDeep
266
- };
267
- //# sourceMappingURL=utils.js.map
313
+ //#endregion
314
+ export { DEFAULT_PROTOCOL_ALLOWLIST, buildDevStylesUrl, createControlledPromise, decodePath, deepEqual, encodePathLikeUrl, escapeHtml, findLast, functionalUpdate, isDangerousProtocol, isModuleNotFoundError, isPlainArray, isPlainObject, isPromise, last, nullReplaceEqualDeep, replaceEqualDeep };
315
+
316
+ //# sourceMappingURL=utils.js.map