@richie-router/core 0.0.1 → 0.1.1
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/cjs/index.cjs +463 -0
- package/dist/cjs/package.json +3 -0
- package/dist/esm/index.mjs +423 -0
- package/dist/esm/package.json +3 -0
- package/dist/types/index.d.ts +199 -0
- package/package.json +40 -7
- package/README.md +0 -45
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
function __accessProp(key) {
|
|
6
|
+
return this[key];
|
|
7
|
+
}
|
|
8
|
+
var __toCommonJS = (from) => {
|
|
9
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
10
|
+
if (entry)
|
|
11
|
+
return entry;
|
|
12
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (var key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(entry, key))
|
|
16
|
+
__defProp(entry, key, {
|
|
17
|
+
get: __accessProp.bind(from, key),
|
|
18
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
__moduleCache.set(from, entry);
|
|
22
|
+
return entry;
|
|
23
|
+
};
|
|
24
|
+
var __moduleCache;
|
|
25
|
+
var __returnValue = (v) => v;
|
|
26
|
+
function __exportSetter(name, newValue) {
|
|
27
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
28
|
+
}
|
|
29
|
+
var __export = (target, all) => {
|
|
30
|
+
for (var name in all)
|
|
31
|
+
__defProp(target, name, {
|
|
32
|
+
get: all[name],
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
set: __exportSetter.bind(all, name)
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// packages/core/src/index.ts
|
|
40
|
+
var exports_src = {};
|
|
41
|
+
__export(exports_src, {
|
|
42
|
+
serializeHeadConfig: () => serializeHeadConfig,
|
|
43
|
+
resolveHeadConfig: () => resolveHeadConfig,
|
|
44
|
+
redirect: () => redirect,
|
|
45
|
+
parseSearchValue: () => parseSearchValue,
|
|
46
|
+
notFound: () => notFound,
|
|
47
|
+
normalizeRouteIdRuntime: () => normalizeRouteIdRuntime,
|
|
48
|
+
mergeHeadConfigs: () => mergeHeadConfigs,
|
|
49
|
+
matchRouteTree: () => matchRouteTree,
|
|
50
|
+
matchPathname: () => matchPathname,
|
|
51
|
+
isRedirect: () => isRedirect,
|
|
52
|
+
isPathlessRoute: () => isPathlessRoute,
|
|
53
|
+
isNotFound: () => isNotFound,
|
|
54
|
+
isIndexRoute: () => isIndexRoute,
|
|
55
|
+
defineHeadTagSchema: () => defineHeadTagSchema,
|
|
56
|
+
defaultStringifySearch: () => defaultStringifySearch,
|
|
57
|
+
defaultParseSearch: () => defaultParseSearch,
|
|
58
|
+
createRouteNode: () => createRouteNode,
|
|
59
|
+
createParsedLocation: () => createParsedLocation,
|
|
60
|
+
collectRoutes: () => collectRoutes,
|
|
61
|
+
collectBranches: () => collectBranches,
|
|
62
|
+
buildPath: () => buildPath,
|
|
63
|
+
RouteNode: () => RouteNode,
|
|
64
|
+
RedirectError: () => RedirectError,
|
|
65
|
+
NotFoundError: () => NotFoundError
|
|
66
|
+
});
|
|
67
|
+
module.exports = __toCommonJS(exports_src);
|
|
68
|
+
function defineHeadTagSchema(schema) {
|
|
69
|
+
return schema;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
class RedirectError extends Error {
|
|
73
|
+
options;
|
|
74
|
+
constructor(options) {
|
|
75
|
+
super(`Redirect to ${options.to}`);
|
|
76
|
+
this.options = options;
|
|
77
|
+
this.name = "RedirectError";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
class NotFoundError extends Error {
|
|
82
|
+
constructor(message = "Not Found") {
|
|
83
|
+
super(message);
|
|
84
|
+
this.name = "NotFoundError";
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function redirect(options) {
|
|
88
|
+
throw new RedirectError(options);
|
|
89
|
+
}
|
|
90
|
+
function notFound(message) {
|
|
91
|
+
throw new NotFoundError(message);
|
|
92
|
+
}
|
|
93
|
+
function isRedirect(error) {
|
|
94
|
+
return error instanceof RedirectError;
|
|
95
|
+
}
|
|
96
|
+
function isNotFound(error) {
|
|
97
|
+
return error instanceof NotFoundError;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
class RouteNode {
|
|
101
|
+
id;
|
|
102
|
+
fullPath;
|
|
103
|
+
to;
|
|
104
|
+
parent;
|
|
105
|
+
children = [];
|
|
106
|
+
searchSchema;
|
|
107
|
+
routeTypes;
|
|
108
|
+
isRoot;
|
|
109
|
+
options;
|
|
110
|
+
constructor(path, options, init) {
|
|
111
|
+
this.id = path;
|
|
112
|
+
this.fullPath = path;
|
|
113
|
+
this.to = normalizeRouteIdRuntime(path);
|
|
114
|
+
this.options = options;
|
|
115
|
+
this.isRoot = init?.isRoot ?? false;
|
|
116
|
+
}
|
|
117
|
+
update(config) {
|
|
118
|
+
this.id = config.id;
|
|
119
|
+
this.fullPath = config.path;
|
|
120
|
+
this.to = normalizeRouteIdRuntime(config.path);
|
|
121
|
+
this.parent = config.getParentRoute?.();
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
_addFileChildren(children) {
|
|
125
|
+
this.children = Object.values(children);
|
|
126
|
+
for (const child of this.children) {
|
|
127
|
+
child.parent = this;
|
|
128
|
+
}
|
|
129
|
+
return this;
|
|
130
|
+
}
|
|
131
|
+
_addFileTypes() {
|
|
132
|
+
return this;
|
|
133
|
+
}
|
|
134
|
+
_setSearchSchema(schema) {
|
|
135
|
+
this.searchSchema = schema;
|
|
136
|
+
return this;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function createRouteNode(path, options, init) {
|
|
140
|
+
return new RouteNode(path, options, init);
|
|
141
|
+
}
|
|
142
|
+
function normalizeRouteIdRuntime(routeId) {
|
|
143
|
+
if (routeId === "__root__" || routeId === "/") {
|
|
144
|
+
return "/";
|
|
145
|
+
}
|
|
146
|
+
const withoutTrailingSlash = routeId !== "/" && routeId.endsWith("/") ? routeId.slice(0, -1) : routeId;
|
|
147
|
+
const segments = withoutTrailingSlash.split("/").filter(Boolean).map((segment) => {
|
|
148
|
+
if (segment.startsWith("_")) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
if (segment.endsWith("_")) {
|
|
152
|
+
return segment.slice(0, -1);
|
|
153
|
+
}
|
|
154
|
+
return segment;
|
|
155
|
+
}).filter((segment) => Boolean(segment));
|
|
156
|
+
return segments.length === 0 ? "/" : `/${segments.join("/")}`;
|
|
157
|
+
}
|
|
158
|
+
function isIndexRoute(route) {
|
|
159
|
+
return !route.isRoot && route.fullPath.endsWith("/");
|
|
160
|
+
}
|
|
161
|
+
function isPathlessRoute(route) {
|
|
162
|
+
return !route.isRoot && !isIndexRoute(route) && route.parent !== undefined && route.to === route.parent.to;
|
|
163
|
+
}
|
|
164
|
+
function parseSearchValue(value) {
|
|
165
|
+
if (value === "") {
|
|
166
|
+
return "";
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
return JSON.parse(value);
|
|
170
|
+
} catch {
|
|
171
|
+
return value;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function defaultParseSearch(searchStr) {
|
|
175
|
+
const result = {};
|
|
176
|
+
const params = new URLSearchParams(searchStr.startsWith("?") ? searchStr.slice(1) : searchStr);
|
|
177
|
+
for (const [key, value] of params.entries()) {
|
|
178
|
+
result[key] = parseSearchValue(value);
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
function defaultStringifySearch(search) {
|
|
183
|
+
const params = new URLSearchParams;
|
|
184
|
+
for (const [key, value] of Object.entries(search)) {
|
|
185
|
+
if (value === undefined) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
params.set(key, typeof value === "string" ? value : JSON.stringify(value));
|
|
189
|
+
}
|
|
190
|
+
const serialized = params.toString();
|
|
191
|
+
return serialized ? `?${serialized}` : "";
|
|
192
|
+
}
|
|
193
|
+
function normalizePattern(pattern) {
|
|
194
|
+
if (pattern === "/") {
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
return pattern.split("/").filter(Boolean);
|
|
198
|
+
}
|
|
199
|
+
function buildPath(to, params = {}) {
|
|
200
|
+
const normalized = normalizeRouteIdRuntime(to);
|
|
201
|
+
if (normalized === "/") {
|
|
202
|
+
return "/";
|
|
203
|
+
}
|
|
204
|
+
const segments = normalized.split("/").filter(Boolean).map((segment) => {
|
|
205
|
+
if (segment === "$") {
|
|
206
|
+
return encodeURIComponent(params._splat ?? "");
|
|
207
|
+
}
|
|
208
|
+
if (segment.startsWith("$")) {
|
|
209
|
+
const key = segment.slice(1);
|
|
210
|
+
const value = params[key];
|
|
211
|
+
if (value === undefined) {
|
|
212
|
+
throw new Error(`Missing route param "${key}" for path "${to}"`);
|
|
213
|
+
}
|
|
214
|
+
return encodeURIComponent(value);
|
|
215
|
+
}
|
|
216
|
+
return segment;
|
|
217
|
+
});
|
|
218
|
+
return `/${segments.join("/")}`;
|
|
219
|
+
}
|
|
220
|
+
function matchPathname(pattern, pathname, options) {
|
|
221
|
+
const patternSegments = normalizePattern(normalizeRouteIdRuntime(pattern));
|
|
222
|
+
const pathnameSegments = normalizePattern(pathname);
|
|
223
|
+
const params = {};
|
|
224
|
+
if (patternSegments.length === 0) {
|
|
225
|
+
if (!options?.partial && pathnameSegments.length > 0) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
return { params };
|
|
229
|
+
}
|
|
230
|
+
let pathnameIndex = 0;
|
|
231
|
+
for (let patternIndex = 0;patternIndex < patternSegments.length; patternIndex += 1) {
|
|
232
|
+
const segment = patternSegments[patternIndex];
|
|
233
|
+
if (segment === undefined) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
if (segment === "$") {
|
|
237
|
+
params._splat = decodeURIComponent(pathnameSegments.slice(pathnameIndex).join("/"));
|
|
238
|
+
pathnameIndex = pathnameSegments.length;
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
const current = pathnameSegments[pathnameIndex];
|
|
242
|
+
if (current === undefined) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
if (segment.startsWith("$")) {
|
|
246
|
+
params[segment.slice(1)] = decodeURIComponent(current);
|
|
247
|
+
pathnameIndex += 1;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (segment !== current) {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
pathnameIndex += 1;
|
|
254
|
+
}
|
|
255
|
+
if (!options?.partial && pathnameIndex < pathnameSegments.length) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
return { params };
|
|
259
|
+
}
|
|
260
|
+
function scorePattern(pattern) {
|
|
261
|
+
return normalizePattern(pattern).reduce((score, segment) => {
|
|
262
|
+
if (segment === "$") {
|
|
263
|
+
return score + 1;
|
|
264
|
+
}
|
|
265
|
+
if (segment.startsWith("$")) {
|
|
266
|
+
return score + 2;
|
|
267
|
+
}
|
|
268
|
+
return score + 3;
|
|
269
|
+
}, 0);
|
|
270
|
+
}
|
|
271
|
+
function canTerminate(route) {
|
|
272
|
+
if (route.isRoot || isPathlessRoute(route)) {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
if (route.children.length === 0) {
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
return !route.children.some((child) => isIndexRoute(child));
|
|
279
|
+
}
|
|
280
|
+
function collectBranches(routeTree) {
|
|
281
|
+
const branches = [];
|
|
282
|
+
function walk(route, ancestors) {
|
|
283
|
+
const nextAncestors = [...ancestors, route];
|
|
284
|
+
if (canTerminate(route)) {
|
|
285
|
+
branches.push({
|
|
286
|
+
leaf: route,
|
|
287
|
+
routes: nextAncestors,
|
|
288
|
+
score: scorePattern(route.to) + nextAncestors.length
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
for (const child of route.children) {
|
|
292
|
+
walk(child, nextAncestors);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
walk(routeTree, []);
|
|
296
|
+
return branches.sort((left, right) => right.score - left.score);
|
|
297
|
+
}
|
|
298
|
+
function collectRoutes(routeTree) {
|
|
299
|
+
const routes = [];
|
|
300
|
+
function walk(route) {
|
|
301
|
+
routes.push(route);
|
|
302
|
+
for (const child of route.children) {
|
|
303
|
+
walk(child);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
walk(routeTree);
|
|
307
|
+
return routes;
|
|
308
|
+
}
|
|
309
|
+
function matchRouteTree(routeTree, pathname) {
|
|
310
|
+
const branches = collectBranches(routeTree);
|
|
311
|
+
for (const branch of branches) {
|
|
312
|
+
const leafMatch = matchPathname(branch.leaf.to, pathname);
|
|
313
|
+
if (!leafMatch) {
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
const matched = branch.routes.map((route) => {
|
|
317
|
+
const partialMatch = matchPathname(route.to, pathname, {
|
|
318
|
+
partial: route !== branch.leaf
|
|
319
|
+
});
|
|
320
|
+
return {
|
|
321
|
+
route,
|
|
322
|
+
params: partialMatch?.params ?? {}
|
|
323
|
+
};
|
|
324
|
+
});
|
|
325
|
+
return matched;
|
|
326
|
+
}
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
function escapeHtml(value) {
|
|
330
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
331
|
+
}
|
|
332
|
+
function renderAttributes(attributes) {
|
|
333
|
+
return Object.entries(attributes).flatMap(([key, value]) => {
|
|
334
|
+
if (value === undefined || value === false) {
|
|
335
|
+
return [];
|
|
336
|
+
}
|
|
337
|
+
if (value === true) {
|
|
338
|
+
return [key];
|
|
339
|
+
}
|
|
340
|
+
return [`${key}="${escapeHtml(value)}"`];
|
|
341
|
+
}).join(" ");
|
|
342
|
+
}
|
|
343
|
+
function mergeHeadConfigs(heads) {
|
|
344
|
+
const titles = [];
|
|
345
|
+
const metaByKey = new Map;
|
|
346
|
+
const linksByKey = new Map;
|
|
347
|
+
const styles = [];
|
|
348
|
+
const scripts = [];
|
|
349
|
+
for (const head of heads) {
|
|
350
|
+
for (const meta of head.meta ?? []) {
|
|
351
|
+
if ("title" in meta) {
|
|
352
|
+
titles.push(meta);
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
if ("charset" in meta) {
|
|
356
|
+
metaByKey.set("charset", meta);
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
if ("name" in meta) {
|
|
360
|
+
metaByKey.set(`name:${meta.name}`, meta);
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
if ("property" in meta) {
|
|
364
|
+
metaByKey.set(`property:${meta.property}`, meta);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
metaByKey.set(`httpEquiv:${meta.httpEquiv}`, meta);
|
|
368
|
+
}
|
|
369
|
+
for (const link of head.links ?? []) {
|
|
370
|
+
linksByKey.set(`${link.rel}:${link.href}`, link);
|
|
371
|
+
}
|
|
372
|
+
styles.push(...head.styles ?? []);
|
|
373
|
+
scripts.push(...head.scripts ?? []);
|
|
374
|
+
}
|
|
375
|
+
const title = titles.at(-1);
|
|
376
|
+
return {
|
|
377
|
+
meta: [...title ? [title] : [], ...metaByKey.values()],
|
|
378
|
+
links: [...linksByKey.values()],
|
|
379
|
+
styles,
|
|
380
|
+
scripts
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
function resolveInlineHead(match, matches) {
|
|
384
|
+
const headOption = match.route.options.head;
|
|
385
|
+
if (!headOption || typeof headOption === "string") {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
return typeof headOption === "function" ? headOption({
|
|
389
|
+
params: match.params,
|
|
390
|
+
search: match.search,
|
|
391
|
+
matches
|
|
392
|
+
}) : headOption;
|
|
393
|
+
}
|
|
394
|
+
function resolveHeadConfig(matches, resolvedHeadByRoute) {
|
|
395
|
+
const heads = [];
|
|
396
|
+
for (const match of matches) {
|
|
397
|
+
const resolvedHead = resolvedHeadByRoute?.get(match.route.fullPath);
|
|
398
|
+
if (resolvedHead) {
|
|
399
|
+
heads.push(resolvedHead);
|
|
400
|
+
}
|
|
401
|
+
const inlineHead = resolveInlineHead(match, matches);
|
|
402
|
+
if (inlineHead) {
|
|
403
|
+
heads.push(inlineHead);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return mergeHeadConfigs(heads);
|
|
407
|
+
}
|
|
408
|
+
function serializeHeadConfig(head, options) {
|
|
409
|
+
const managedAttributes = options?.managedAttribute ? {
|
|
410
|
+
[options.managedAttribute]: "true"
|
|
411
|
+
} : {};
|
|
412
|
+
const meta = (head.meta ?? []).map((tag) => {
|
|
413
|
+
if ("title" in tag) {
|
|
414
|
+
return `<title ${renderAttributes(managedAttributes)}>${escapeHtml(tag.title)}</title>`;
|
|
415
|
+
}
|
|
416
|
+
if ("charset" in tag) {
|
|
417
|
+
return `<meta ${renderAttributes({ charset: tag.charset, ...managedAttributes })}>`;
|
|
418
|
+
}
|
|
419
|
+
if ("name" in tag) {
|
|
420
|
+
return `<meta ${renderAttributes({ name: tag.name, content: tag.content, ...managedAttributes })}>`;
|
|
421
|
+
}
|
|
422
|
+
if ("property" in tag) {
|
|
423
|
+
return `<meta ${renderAttributes({ property: tag.property, content: tag.content, ...managedAttributes })}>`;
|
|
424
|
+
}
|
|
425
|
+
return `<meta ${renderAttributes({ "http-equiv": tag.httpEquiv, content: tag.content, ...managedAttributes })}>`;
|
|
426
|
+
});
|
|
427
|
+
const links = (head.links ?? []).map((link) => `<link ${renderAttributes({
|
|
428
|
+
rel: link.rel,
|
|
429
|
+
href: link.href,
|
|
430
|
+
type: link.type,
|
|
431
|
+
media: link.media,
|
|
432
|
+
sizes: link.sizes,
|
|
433
|
+
crossorigin: link.crossorigin,
|
|
434
|
+
...managedAttributes
|
|
435
|
+
})}>`);
|
|
436
|
+
const styles = (head.styles ?? []).map((style) => {
|
|
437
|
+
const attributes = renderAttributes({ media: style.media, ...managedAttributes });
|
|
438
|
+
return `<style${attributes ? ` ${attributes}` : ""}>${style.children}</style>`;
|
|
439
|
+
});
|
|
440
|
+
const scripts = (head.scripts ?? []).map((script) => {
|
|
441
|
+
const attributes = renderAttributes({
|
|
442
|
+
src: script.src,
|
|
443
|
+
type: script.type,
|
|
444
|
+
async: script.async,
|
|
445
|
+
defer: script.defer,
|
|
446
|
+
...managedAttributes
|
|
447
|
+
});
|
|
448
|
+
return `<script${attributes ? ` ${attributes}` : ""}>${script.children ?? ""}</script>`;
|
|
449
|
+
});
|
|
450
|
+
return [...meta, ...links, ...styles, ...scripts].join("");
|
|
451
|
+
}
|
|
452
|
+
function createParsedLocation(href, state, parseSearch) {
|
|
453
|
+
const base = href.startsWith("http://") || href.startsWith("https://") ? href : `http://richie-router.local${href}`;
|
|
454
|
+
const url = new URL(base);
|
|
455
|
+
return {
|
|
456
|
+
pathname: url.pathname,
|
|
457
|
+
search: parseSearch(url.search),
|
|
458
|
+
searchStr: url.search,
|
|
459
|
+
hash: url.hash,
|
|
460
|
+
href: `${url.pathname}${url.search}${url.hash}`,
|
|
461
|
+
state
|
|
462
|
+
};
|
|
463
|
+
}
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
// packages/core/src/index.ts
|
|
2
|
+
function defineHeadTagSchema(schema) {
|
|
3
|
+
return schema;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
class RedirectError extends Error {
|
|
7
|
+
options;
|
|
8
|
+
constructor(options) {
|
|
9
|
+
super(`Redirect to ${options.to}`);
|
|
10
|
+
this.options = options;
|
|
11
|
+
this.name = "RedirectError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class NotFoundError extends Error {
|
|
16
|
+
constructor(message = "Not Found") {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "NotFoundError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function redirect(options) {
|
|
22
|
+
throw new RedirectError(options);
|
|
23
|
+
}
|
|
24
|
+
function notFound(message) {
|
|
25
|
+
throw new NotFoundError(message);
|
|
26
|
+
}
|
|
27
|
+
function isRedirect(error) {
|
|
28
|
+
return error instanceof RedirectError;
|
|
29
|
+
}
|
|
30
|
+
function isNotFound(error) {
|
|
31
|
+
return error instanceof NotFoundError;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class RouteNode {
|
|
35
|
+
id;
|
|
36
|
+
fullPath;
|
|
37
|
+
to;
|
|
38
|
+
parent;
|
|
39
|
+
children = [];
|
|
40
|
+
searchSchema;
|
|
41
|
+
routeTypes;
|
|
42
|
+
isRoot;
|
|
43
|
+
options;
|
|
44
|
+
constructor(path, options, init) {
|
|
45
|
+
this.id = path;
|
|
46
|
+
this.fullPath = path;
|
|
47
|
+
this.to = normalizeRouteIdRuntime(path);
|
|
48
|
+
this.options = options;
|
|
49
|
+
this.isRoot = init?.isRoot ?? false;
|
|
50
|
+
}
|
|
51
|
+
update(config) {
|
|
52
|
+
this.id = config.id;
|
|
53
|
+
this.fullPath = config.path;
|
|
54
|
+
this.to = normalizeRouteIdRuntime(config.path);
|
|
55
|
+
this.parent = config.getParentRoute?.();
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
_addFileChildren(children) {
|
|
59
|
+
this.children = Object.values(children);
|
|
60
|
+
for (const child of this.children) {
|
|
61
|
+
child.parent = this;
|
|
62
|
+
}
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
_addFileTypes() {
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
_setSearchSchema(schema) {
|
|
69
|
+
this.searchSchema = schema;
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function createRouteNode(path, options, init) {
|
|
74
|
+
return new RouteNode(path, options, init);
|
|
75
|
+
}
|
|
76
|
+
function normalizeRouteIdRuntime(routeId) {
|
|
77
|
+
if (routeId === "__root__" || routeId === "/") {
|
|
78
|
+
return "/";
|
|
79
|
+
}
|
|
80
|
+
const withoutTrailingSlash = routeId !== "/" && routeId.endsWith("/") ? routeId.slice(0, -1) : routeId;
|
|
81
|
+
const segments = withoutTrailingSlash.split("/").filter(Boolean).map((segment) => {
|
|
82
|
+
if (segment.startsWith("_")) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
if (segment.endsWith("_")) {
|
|
86
|
+
return segment.slice(0, -1);
|
|
87
|
+
}
|
|
88
|
+
return segment;
|
|
89
|
+
}).filter((segment) => Boolean(segment));
|
|
90
|
+
return segments.length === 0 ? "/" : `/${segments.join("/")}`;
|
|
91
|
+
}
|
|
92
|
+
function isIndexRoute(route) {
|
|
93
|
+
return !route.isRoot && route.fullPath.endsWith("/");
|
|
94
|
+
}
|
|
95
|
+
function isPathlessRoute(route) {
|
|
96
|
+
return !route.isRoot && !isIndexRoute(route) && route.parent !== undefined && route.to === route.parent.to;
|
|
97
|
+
}
|
|
98
|
+
function parseSearchValue(value) {
|
|
99
|
+
if (value === "") {
|
|
100
|
+
return "";
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
return JSON.parse(value);
|
|
104
|
+
} catch {
|
|
105
|
+
return value;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function defaultParseSearch(searchStr) {
|
|
109
|
+
const result = {};
|
|
110
|
+
const params = new URLSearchParams(searchStr.startsWith("?") ? searchStr.slice(1) : searchStr);
|
|
111
|
+
for (const [key, value] of params.entries()) {
|
|
112
|
+
result[key] = parseSearchValue(value);
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
function defaultStringifySearch(search) {
|
|
117
|
+
const params = new URLSearchParams;
|
|
118
|
+
for (const [key, value] of Object.entries(search)) {
|
|
119
|
+
if (value === undefined) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
params.set(key, typeof value === "string" ? value : JSON.stringify(value));
|
|
123
|
+
}
|
|
124
|
+
const serialized = params.toString();
|
|
125
|
+
return serialized ? `?${serialized}` : "";
|
|
126
|
+
}
|
|
127
|
+
function normalizePattern(pattern) {
|
|
128
|
+
if (pattern === "/") {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
return pattern.split("/").filter(Boolean);
|
|
132
|
+
}
|
|
133
|
+
function buildPath(to, params = {}) {
|
|
134
|
+
const normalized = normalizeRouteIdRuntime(to);
|
|
135
|
+
if (normalized === "/") {
|
|
136
|
+
return "/";
|
|
137
|
+
}
|
|
138
|
+
const segments = normalized.split("/").filter(Boolean).map((segment) => {
|
|
139
|
+
if (segment === "$") {
|
|
140
|
+
return encodeURIComponent(params._splat ?? "");
|
|
141
|
+
}
|
|
142
|
+
if (segment.startsWith("$")) {
|
|
143
|
+
const key = segment.slice(1);
|
|
144
|
+
const value = params[key];
|
|
145
|
+
if (value === undefined) {
|
|
146
|
+
throw new Error(`Missing route param "${key}" for path "${to}"`);
|
|
147
|
+
}
|
|
148
|
+
return encodeURIComponent(value);
|
|
149
|
+
}
|
|
150
|
+
return segment;
|
|
151
|
+
});
|
|
152
|
+
return `/${segments.join("/")}`;
|
|
153
|
+
}
|
|
154
|
+
function matchPathname(pattern, pathname, options) {
|
|
155
|
+
const patternSegments = normalizePattern(normalizeRouteIdRuntime(pattern));
|
|
156
|
+
const pathnameSegments = normalizePattern(pathname);
|
|
157
|
+
const params = {};
|
|
158
|
+
if (patternSegments.length === 0) {
|
|
159
|
+
if (!options?.partial && pathnameSegments.length > 0) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
return { params };
|
|
163
|
+
}
|
|
164
|
+
let pathnameIndex = 0;
|
|
165
|
+
for (let patternIndex = 0;patternIndex < patternSegments.length; patternIndex += 1) {
|
|
166
|
+
const segment = patternSegments[patternIndex];
|
|
167
|
+
if (segment === undefined) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
if (segment === "$") {
|
|
171
|
+
params._splat = decodeURIComponent(pathnameSegments.slice(pathnameIndex).join("/"));
|
|
172
|
+
pathnameIndex = pathnameSegments.length;
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
const current = pathnameSegments[pathnameIndex];
|
|
176
|
+
if (current === undefined) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
if (segment.startsWith("$")) {
|
|
180
|
+
params[segment.slice(1)] = decodeURIComponent(current);
|
|
181
|
+
pathnameIndex += 1;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (segment !== current) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
pathnameIndex += 1;
|
|
188
|
+
}
|
|
189
|
+
if (!options?.partial && pathnameIndex < pathnameSegments.length) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
return { params };
|
|
193
|
+
}
|
|
194
|
+
function scorePattern(pattern) {
|
|
195
|
+
return normalizePattern(pattern).reduce((score, segment) => {
|
|
196
|
+
if (segment === "$") {
|
|
197
|
+
return score + 1;
|
|
198
|
+
}
|
|
199
|
+
if (segment.startsWith("$")) {
|
|
200
|
+
return score + 2;
|
|
201
|
+
}
|
|
202
|
+
return score + 3;
|
|
203
|
+
}, 0);
|
|
204
|
+
}
|
|
205
|
+
function canTerminate(route) {
|
|
206
|
+
if (route.isRoot || isPathlessRoute(route)) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
if (route.children.length === 0) {
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
return !route.children.some((child) => isIndexRoute(child));
|
|
213
|
+
}
|
|
214
|
+
function collectBranches(routeTree) {
|
|
215
|
+
const branches = [];
|
|
216
|
+
function walk(route, ancestors) {
|
|
217
|
+
const nextAncestors = [...ancestors, route];
|
|
218
|
+
if (canTerminate(route)) {
|
|
219
|
+
branches.push({
|
|
220
|
+
leaf: route,
|
|
221
|
+
routes: nextAncestors,
|
|
222
|
+
score: scorePattern(route.to) + nextAncestors.length
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
for (const child of route.children) {
|
|
226
|
+
walk(child, nextAncestors);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
walk(routeTree, []);
|
|
230
|
+
return branches.sort((left, right) => right.score - left.score);
|
|
231
|
+
}
|
|
232
|
+
function collectRoutes(routeTree) {
|
|
233
|
+
const routes = [];
|
|
234
|
+
function walk(route) {
|
|
235
|
+
routes.push(route);
|
|
236
|
+
for (const child of route.children) {
|
|
237
|
+
walk(child);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
walk(routeTree);
|
|
241
|
+
return routes;
|
|
242
|
+
}
|
|
243
|
+
function matchRouteTree(routeTree, pathname) {
|
|
244
|
+
const branches = collectBranches(routeTree);
|
|
245
|
+
for (const branch of branches) {
|
|
246
|
+
const leafMatch = matchPathname(branch.leaf.to, pathname);
|
|
247
|
+
if (!leafMatch) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const matched = branch.routes.map((route) => {
|
|
251
|
+
const partialMatch = matchPathname(route.to, pathname, {
|
|
252
|
+
partial: route !== branch.leaf
|
|
253
|
+
});
|
|
254
|
+
return {
|
|
255
|
+
route,
|
|
256
|
+
params: partialMatch?.params ?? {}
|
|
257
|
+
};
|
|
258
|
+
});
|
|
259
|
+
return matched;
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
function escapeHtml(value) {
|
|
264
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
265
|
+
}
|
|
266
|
+
function renderAttributes(attributes) {
|
|
267
|
+
return Object.entries(attributes).flatMap(([key, value]) => {
|
|
268
|
+
if (value === undefined || value === false) {
|
|
269
|
+
return [];
|
|
270
|
+
}
|
|
271
|
+
if (value === true) {
|
|
272
|
+
return [key];
|
|
273
|
+
}
|
|
274
|
+
return [`${key}="${escapeHtml(value)}"`];
|
|
275
|
+
}).join(" ");
|
|
276
|
+
}
|
|
277
|
+
function mergeHeadConfigs(heads) {
|
|
278
|
+
const titles = [];
|
|
279
|
+
const metaByKey = new Map;
|
|
280
|
+
const linksByKey = new Map;
|
|
281
|
+
const styles = [];
|
|
282
|
+
const scripts = [];
|
|
283
|
+
for (const head of heads) {
|
|
284
|
+
for (const meta of head.meta ?? []) {
|
|
285
|
+
if ("title" in meta) {
|
|
286
|
+
titles.push(meta);
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if ("charset" in meta) {
|
|
290
|
+
metaByKey.set("charset", meta);
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if ("name" in meta) {
|
|
294
|
+
metaByKey.set(`name:${meta.name}`, meta);
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if ("property" in meta) {
|
|
298
|
+
metaByKey.set(`property:${meta.property}`, meta);
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
metaByKey.set(`httpEquiv:${meta.httpEquiv}`, meta);
|
|
302
|
+
}
|
|
303
|
+
for (const link of head.links ?? []) {
|
|
304
|
+
linksByKey.set(`${link.rel}:${link.href}`, link);
|
|
305
|
+
}
|
|
306
|
+
styles.push(...head.styles ?? []);
|
|
307
|
+
scripts.push(...head.scripts ?? []);
|
|
308
|
+
}
|
|
309
|
+
const title = titles.at(-1);
|
|
310
|
+
return {
|
|
311
|
+
meta: [...title ? [title] : [], ...metaByKey.values()],
|
|
312
|
+
links: [...linksByKey.values()],
|
|
313
|
+
styles,
|
|
314
|
+
scripts
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
function resolveInlineHead(match, matches) {
|
|
318
|
+
const headOption = match.route.options.head;
|
|
319
|
+
if (!headOption || typeof headOption === "string") {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
return typeof headOption === "function" ? headOption({
|
|
323
|
+
params: match.params,
|
|
324
|
+
search: match.search,
|
|
325
|
+
matches
|
|
326
|
+
}) : headOption;
|
|
327
|
+
}
|
|
328
|
+
function resolveHeadConfig(matches, resolvedHeadByRoute) {
|
|
329
|
+
const heads = [];
|
|
330
|
+
for (const match of matches) {
|
|
331
|
+
const resolvedHead = resolvedHeadByRoute?.get(match.route.fullPath);
|
|
332
|
+
if (resolvedHead) {
|
|
333
|
+
heads.push(resolvedHead);
|
|
334
|
+
}
|
|
335
|
+
const inlineHead = resolveInlineHead(match, matches);
|
|
336
|
+
if (inlineHead) {
|
|
337
|
+
heads.push(inlineHead);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return mergeHeadConfigs(heads);
|
|
341
|
+
}
|
|
342
|
+
function serializeHeadConfig(head, options) {
|
|
343
|
+
const managedAttributes = options?.managedAttribute ? {
|
|
344
|
+
[options.managedAttribute]: "true"
|
|
345
|
+
} : {};
|
|
346
|
+
const meta = (head.meta ?? []).map((tag) => {
|
|
347
|
+
if ("title" in tag) {
|
|
348
|
+
return `<title ${renderAttributes(managedAttributes)}>${escapeHtml(tag.title)}</title>`;
|
|
349
|
+
}
|
|
350
|
+
if ("charset" in tag) {
|
|
351
|
+
return `<meta ${renderAttributes({ charset: tag.charset, ...managedAttributes })}>`;
|
|
352
|
+
}
|
|
353
|
+
if ("name" in tag) {
|
|
354
|
+
return `<meta ${renderAttributes({ name: tag.name, content: tag.content, ...managedAttributes })}>`;
|
|
355
|
+
}
|
|
356
|
+
if ("property" in tag) {
|
|
357
|
+
return `<meta ${renderAttributes({ property: tag.property, content: tag.content, ...managedAttributes })}>`;
|
|
358
|
+
}
|
|
359
|
+
return `<meta ${renderAttributes({ "http-equiv": tag.httpEquiv, content: tag.content, ...managedAttributes })}>`;
|
|
360
|
+
});
|
|
361
|
+
const links = (head.links ?? []).map((link) => `<link ${renderAttributes({
|
|
362
|
+
rel: link.rel,
|
|
363
|
+
href: link.href,
|
|
364
|
+
type: link.type,
|
|
365
|
+
media: link.media,
|
|
366
|
+
sizes: link.sizes,
|
|
367
|
+
crossorigin: link.crossorigin,
|
|
368
|
+
...managedAttributes
|
|
369
|
+
})}>`);
|
|
370
|
+
const styles = (head.styles ?? []).map((style) => {
|
|
371
|
+
const attributes = renderAttributes({ media: style.media, ...managedAttributes });
|
|
372
|
+
return `<style${attributes ? ` ${attributes}` : ""}>${style.children}</style>`;
|
|
373
|
+
});
|
|
374
|
+
const scripts = (head.scripts ?? []).map((script) => {
|
|
375
|
+
const attributes = renderAttributes({
|
|
376
|
+
src: script.src,
|
|
377
|
+
type: script.type,
|
|
378
|
+
async: script.async,
|
|
379
|
+
defer: script.defer,
|
|
380
|
+
...managedAttributes
|
|
381
|
+
});
|
|
382
|
+
return `<script${attributes ? ` ${attributes}` : ""}>${script.children ?? ""}</script>`;
|
|
383
|
+
});
|
|
384
|
+
return [...meta, ...links, ...styles, ...scripts].join("");
|
|
385
|
+
}
|
|
386
|
+
function createParsedLocation(href, state, parseSearch) {
|
|
387
|
+
const base = href.startsWith("http://") || href.startsWith("https://") ? href : `http://richie-router.local${href}`;
|
|
388
|
+
const url = new URL(base);
|
|
389
|
+
return {
|
|
390
|
+
pathname: url.pathname,
|
|
391
|
+
search: parseSearch(url.search),
|
|
392
|
+
searchStr: url.search,
|
|
393
|
+
hash: url.hash,
|
|
394
|
+
href: `${url.pathname}${url.search}${url.hash}`,
|
|
395
|
+
state
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
export {
|
|
399
|
+
serializeHeadConfig,
|
|
400
|
+
resolveHeadConfig,
|
|
401
|
+
redirect,
|
|
402
|
+
parseSearchValue,
|
|
403
|
+
notFound,
|
|
404
|
+
normalizeRouteIdRuntime,
|
|
405
|
+
mergeHeadConfigs,
|
|
406
|
+
matchRouteTree,
|
|
407
|
+
matchPathname,
|
|
408
|
+
isRedirect,
|
|
409
|
+
isPathlessRoute,
|
|
410
|
+
isNotFound,
|
|
411
|
+
isIndexRoute,
|
|
412
|
+
defineHeadTagSchema,
|
|
413
|
+
defaultStringifySearch,
|
|
414
|
+
defaultParseSearch,
|
|
415
|
+
createRouteNode,
|
|
416
|
+
createParsedLocation,
|
|
417
|
+
collectRoutes,
|
|
418
|
+
collectBranches,
|
|
419
|
+
buildPath,
|
|
420
|
+
RouteNode,
|
|
421
|
+
RedirectError,
|
|
422
|
+
NotFoundError
|
|
423
|
+
};
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
export type AnyComponent = (props?: any) => unknown;
|
|
2
|
+
export interface SchemaLike<TOutput = unknown> {
|
|
3
|
+
parse(value: unknown): TOutput;
|
|
4
|
+
}
|
|
5
|
+
export interface HeadTagSchemaEntry<TSearchSchema extends SchemaLike<any> | undefined = SchemaLike<any> | undefined> {
|
|
6
|
+
searchSchema?: TSearchSchema;
|
|
7
|
+
}
|
|
8
|
+
export type HeadTagSchemaShape = Record<string, HeadTagSchemaEntry>;
|
|
9
|
+
export declare function defineHeadTagSchema<const TSchema extends HeadTagSchemaShape>(schema: TSchema): TSchema;
|
|
10
|
+
type SchemaOutput<TSchema> = TSchema extends {
|
|
11
|
+
_output: infer TOutput;
|
|
12
|
+
} ? TOutput : TSchema extends SchemaLike<infer TOutput> ? TOutput : never;
|
|
13
|
+
export type InferHeadTagSearchSchema<TSchema extends HeadTagSchemaShape, THeadTagName extends keyof TSchema> = TSchema[THeadTagName] extends {
|
|
14
|
+
searchSchema: infer TSearchSchema;
|
|
15
|
+
} ? SchemaOutput<TSearchSchema> : {};
|
|
16
|
+
export type Simplify<TValue> = {
|
|
17
|
+
[TKey in keyof TValue]: TValue[TKey];
|
|
18
|
+
} & {};
|
|
19
|
+
type NormalizePathInternal<TValue extends string> = TValue extends `/${infer TRest}` ? NormalizePathSegments<TRest> extends infer TSegments extends string ? TSegments extends '' ? '/' : `/${TSegments}` : never : TValue;
|
|
20
|
+
type NormalizePathSegments<TValue extends string> = TValue extends `${infer THead}/${infer TTail}` ? JoinNormalizedSegments<NormalizePathSegment<THead>, NormalizePathSegments<TTail>> : NormalizePathSegment<TValue>;
|
|
21
|
+
type JoinNormalizedSegments<TLeft extends string, TRight extends string> = TLeft extends '' ? TRight : TRight extends '' ? TLeft : `${TLeft}/${TRight}`;
|
|
22
|
+
type NormalizePathSegment<TSegment extends string> = TSegment extends '' ? '' : TSegment extends `_${string}` ? '' : TSegment extends `${infer TBase}_` ? TBase : TSegment;
|
|
23
|
+
export type NormalizeRouteId<TValue extends string> = TValue extends '__root__' ? '/' : TValue extends '/' ? '/' : TValue extends `${infer TPrefix}/` ? TPrefix extends '' ? '/' : NormalizePathInternal<TPrefix> : NormalizePathInternal<TValue>;
|
|
24
|
+
type ParsePathParamsInternal<TValue extends string> = TValue extends `${infer _Start}/$/${infer TRest}` ? '_splat' | ParsePathParamsInternal<`/${TRest}`> : TValue extends `${infer _Start}/$` ? '_splat' : TValue extends `${infer _Start}/$${infer TParam}/${infer TRest}` ? TParam | ParsePathParamsInternal<`/${TRest}`> : TValue extends `${infer _Start}/$${infer TParam}` ? TParam : never;
|
|
25
|
+
export type ParsePathParams<TPath extends string> = ParsePathParamsInternal<NormalizeRouteId<TPath>>;
|
|
26
|
+
export type ResolveAllParams<TPath extends string> = Simplify<{
|
|
27
|
+
[TKey in ParsePathParams<TPath>]: string;
|
|
28
|
+
}>;
|
|
29
|
+
export interface HeadTagTitle {
|
|
30
|
+
title: string;
|
|
31
|
+
}
|
|
32
|
+
export interface HeadTagName {
|
|
33
|
+
name: string;
|
|
34
|
+
content: string;
|
|
35
|
+
}
|
|
36
|
+
export interface HeadTagProperty {
|
|
37
|
+
property: string;
|
|
38
|
+
content: string;
|
|
39
|
+
}
|
|
40
|
+
export interface HeadTagHttpEquiv {
|
|
41
|
+
httpEquiv: string;
|
|
42
|
+
content: string;
|
|
43
|
+
}
|
|
44
|
+
export interface HeadTagCharset {
|
|
45
|
+
charset: string;
|
|
46
|
+
}
|
|
47
|
+
export type HeadTag = HeadTagTitle | HeadTagName | HeadTagProperty | HeadTagHttpEquiv | HeadTagCharset;
|
|
48
|
+
export interface HeadLinkTag {
|
|
49
|
+
rel: string;
|
|
50
|
+
href: string;
|
|
51
|
+
type?: string;
|
|
52
|
+
media?: string;
|
|
53
|
+
sizes?: string;
|
|
54
|
+
crossorigin?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface HeadStyleTag {
|
|
57
|
+
children: string;
|
|
58
|
+
media?: string;
|
|
59
|
+
}
|
|
60
|
+
export interface HeadScriptTag {
|
|
61
|
+
src?: string;
|
|
62
|
+
children?: string;
|
|
63
|
+
type?: string;
|
|
64
|
+
async?: boolean;
|
|
65
|
+
defer?: boolean;
|
|
66
|
+
}
|
|
67
|
+
export interface HeadConfig {
|
|
68
|
+
meta?: HeadTag[];
|
|
69
|
+
links?: HeadLinkTag[];
|
|
70
|
+
styles?: HeadStyleTag[];
|
|
71
|
+
scripts?: HeadScriptTag[];
|
|
72
|
+
}
|
|
73
|
+
export interface DehydratedHeadState {
|
|
74
|
+
href: string;
|
|
75
|
+
head: HeadConfig;
|
|
76
|
+
}
|
|
77
|
+
export interface ParsedLocation<TSearch = Record<string, unknown>> {
|
|
78
|
+
pathname: string;
|
|
79
|
+
search: TSearch;
|
|
80
|
+
searchStr: string;
|
|
81
|
+
hash: string;
|
|
82
|
+
href: string;
|
|
83
|
+
state: unknown;
|
|
84
|
+
}
|
|
85
|
+
export interface RedirectTarget {
|
|
86
|
+
to: string;
|
|
87
|
+
params?: Record<string, string>;
|
|
88
|
+
search?: Record<string, unknown> | true;
|
|
89
|
+
hash?: string;
|
|
90
|
+
replace?: boolean;
|
|
91
|
+
state?: Record<string, unknown>;
|
|
92
|
+
}
|
|
93
|
+
export declare class RedirectError extends Error {
|
|
94
|
+
readonly options: RedirectTarget;
|
|
95
|
+
constructor(options: RedirectTarget);
|
|
96
|
+
}
|
|
97
|
+
export declare class NotFoundError extends Error {
|
|
98
|
+
constructor(message?: string);
|
|
99
|
+
}
|
|
100
|
+
export declare function redirect(options: RedirectTarget): never;
|
|
101
|
+
export declare function notFound(message?: string): never;
|
|
102
|
+
export declare function isRedirect(error: unknown): error is RedirectError;
|
|
103
|
+
export declare function isNotFound(error: unknown): error is NotFoundError;
|
|
104
|
+
export interface RouteMatch<TRoute extends AnyRoute = AnyRoute> {
|
|
105
|
+
id: string;
|
|
106
|
+
pathname: string;
|
|
107
|
+
params: Record<string, string>;
|
|
108
|
+
route: TRoute;
|
|
109
|
+
search: unknown;
|
|
110
|
+
to: string;
|
|
111
|
+
}
|
|
112
|
+
export interface RouteOptions<TPath extends string = string, TSearch = unknown> {
|
|
113
|
+
component?: AnyComponent;
|
|
114
|
+
pendingComponent?: AnyComponent;
|
|
115
|
+
errorComponent?: AnyComponent;
|
|
116
|
+
notFoundComponent?: AnyComponent;
|
|
117
|
+
head?: string | HeadConfig | ((ctx: {
|
|
118
|
+
params: ResolveAllParams<TPath>;
|
|
119
|
+
search: TSearch;
|
|
120
|
+
matches: RouteMatch[];
|
|
121
|
+
}) => HeadConfig);
|
|
122
|
+
validateSearch?: (raw: Record<string, unknown>) => TSearch;
|
|
123
|
+
beforeLoad?: (ctx: {
|
|
124
|
+
location: ParsedLocation;
|
|
125
|
+
params: ResolveAllParams<TPath>;
|
|
126
|
+
search: TSearch;
|
|
127
|
+
navigate: (options: RedirectTarget) => Promise<void>;
|
|
128
|
+
cause: 'enter' | 'stay';
|
|
129
|
+
}) => void | Promise<void>;
|
|
130
|
+
pendingMs?: number;
|
|
131
|
+
pendingMinMs?: number;
|
|
132
|
+
staticData?: Record<string, unknown>;
|
|
133
|
+
}
|
|
134
|
+
export interface RouteTypeInfo<TId extends string = string, TTo extends string = string, TParams extends Record<string, string> = Record<string, string>, TSearch = unknown> {
|
|
135
|
+
id: TId;
|
|
136
|
+
fullPath: TId;
|
|
137
|
+
to: TTo;
|
|
138
|
+
params: TParams;
|
|
139
|
+
search: TSearch;
|
|
140
|
+
}
|
|
141
|
+
export declare class RouteNode<TId extends string = string, TTo extends string = string, TParams extends Record<string, string> = Record<string, string>, TSearch = unknown, TFileTypes = unknown> {
|
|
142
|
+
id: TId;
|
|
143
|
+
fullPath: TId;
|
|
144
|
+
to: TTo;
|
|
145
|
+
parent?: AnyRoute;
|
|
146
|
+
children: AnyRoute[];
|
|
147
|
+
searchSchema?: SchemaLike<TSearch>;
|
|
148
|
+
routeTypes?: TFileTypes;
|
|
149
|
+
readonly isRoot: boolean;
|
|
150
|
+
readonly options: RouteOptions<TId, TSearch>;
|
|
151
|
+
types: RouteTypeInfo<TId, TTo, TParams, TSearch>;
|
|
152
|
+
__fileTypes: TFileTypes;
|
|
153
|
+
constructor(path: TId, options: RouteOptions<TId, TSearch>, init?: {
|
|
154
|
+
isRoot?: boolean;
|
|
155
|
+
});
|
|
156
|
+
update(config: {
|
|
157
|
+
id: TId;
|
|
158
|
+
path: TId;
|
|
159
|
+
getParentRoute?: () => AnyRoute;
|
|
160
|
+
}): this;
|
|
161
|
+
_addFileChildren<TChildren extends Record<string, AnyRoute>>(children: TChildren): this;
|
|
162
|
+
_addFileTypes<TNextFileTypes>(): RouteNode<TId, TTo, TParams, TSearch, TNextFileTypes>;
|
|
163
|
+
_setSearchSchema<TSchema extends SchemaLike<TSearch>>(schema: TSchema): this;
|
|
164
|
+
}
|
|
165
|
+
export type AnyRoute = RouteNode<any, any, any, any, any>;
|
|
166
|
+
export declare function createRouteNode<TId extends string, TTo extends string, TParams extends Record<string, string>, TSearch>(path: TId, options: RouteOptions<TId, TSearch>, init?: {
|
|
167
|
+
isRoot?: boolean;
|
|
168
|
+
}): RouteNode<TId, TTo, TParams, TSearch>;
|
|
169
|
+
export declare function normalizeRouteIdRuntime(routeId: string): string;
|
|
170
|
+
export declare function isIndexRoute(route: AnyRoute): boolean;
|
|
171
|
+
export declare function isPathlessRoute(route: AnyRoute): boolean;
|
|
172
|
+
export declare function parseSearchValue(value: string): unknown;
|
|
173
|
+
export declare function defaultParseSearch(searchStr: string): Record<string, unknown>;
|
|
174
|
+
export declare function defaultStringifySearch(search: Record<string, unknown>): string;
|
|
175
|
+
export declare function buildPath(to: string, params?: Record<string, string>): string;
|
|
176
|
+
export declare function matchPathname(pattern: string, pathname: string, options?: {
|
|
177
|
+
partial?: boolean;
|
|
178
|
+
}): {
|
|
179
|
+
params: Record<string, string>;
|
|
180
|
+
} | null;
|
|
181
|
+
export interface RouteBranch {
|
|
182
|
+
leaf: AnyRoute;
|
|
183
|
+
routes: AnyRoute[];
|
|
184
|
+
score: number;
|
|
185
|
+
}
|
|
186
|
+
export declare function collectBranches(routeTree: AnyRoute): RouteBranch[];
|
|
187
|
+
export declare function collectRoutes(routeTree: AnyRoute): AnyRoute[];
|
|
188
|
+
export interface MatchedRoute {
|
|
189
|
+
route: AnyRoute;
|
|
190
|
+
params: Record<string, string>;
|
|
191
|
+
}
|
|
192
|
+
export declare function matchRouteTree(routeTree: AnyRoute, pathname: string): MatchedRoute[] | null;
|
|
193
|
+
export declare function mergeHeadConfigs(heads: HeadConfig[]): HeadConfig;
|
|
194
|
+
export declare function resolveHeadConfig(matches: RouteMatch[], resolvedHeadByRoute?: Map<string, HeadConfig>): HeadConfig;
|
|
195
|
+
export declare function serializeHeadConfig(head: HeadConfig, options?: {
|
|
196
|
+
managedAttribute?: string;
|
|
197
|
+
}): string;
|
|
198
|
+
export declare function createParsedLocation<TSearch>(href: string, state: unknown, parseSearch: (searchStr: string) => TSearch): ParsedLocation<TSearch>;
|
|
199
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,10 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@richie-router/core",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Shared route, search, and head utilities for Richie Router",
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"exports": {
|
|
7
|
+
"./package.json": "./package.json",
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/types/index.d.ts",
|
|
10
|
+
"import": "./dist/esm/index.mjs",
|
|
11
|
+
"require": "./dist/cjs/index.cjs",
|
|
12
|
+
"default": "./dist/esm/index.mjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"author": "Richie <oss@ricsam.dev>",
|
|
16
|
+
"homepage": "https://docs.ricsam.dev/richie-router",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/ricsam/richie-router/issues"
|
|
19
|
+
},
|
|
5
20
|
"keywords": [
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
21
|
+
"router",
|
|
22
|
+
"react",
|
|
23
|
+
"typescript",
|
|
24
|
+
"bun",
|
|
25
|
+
"file-based-routing",
|
|
26
|
+
"type-safe"
|
|
27
|
+
],
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/ricsam/richie-router.git",
|
|
31
|
+
"directory": "packages/core"
|
|
32
|
+
},
|
|
33
|
+
"main": "./dist/cjs/index.cjs",
|
|
34
|
+
"module": "./dist/esm/index.mjs",
|
|
35
|
+
"types": "./dist/types/index.d.ts",
|
|
36
|
+
"files": [
|
|
37
|
+
"dist"
|
|
38
|
+
],
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public",
|
|
41
|
+
"provenance": true
|
|
42
|
+
}
|
|
43
|
+
}
|
package/README.md
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# @richie-router/core
|
|
2
|
-
|
|
3
|
-
## ⚠️ IMPORTANT NOTICE ⚠️
|
|
4
|
-
|
|
5
|
-
**This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
|
|
6
|
-
|
|
7
|
-
This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
|
|
8
|
-
|
|
9
|
-
## Purpose
|
|
10
|
-
|
|
11
|
-
This package exists to:
|
|
12
|
-
1. Configure OIDC trusted publishing for the package name `@richie-router/core`
|
|
13
|
-
2. Enable secure, token-less publishing from CI/CD workflows
|
|
14
|
-
3. Establish provenance for packages published under this name
|
|
15
|
-
|
|
16
|
-
## What is OIDC Trusted Publishing?
|
|
17
|
-
|
|
18
|
-
OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
|
|
19
|
-
|
|
20
|
-
## Setup Instructions
|
|
21
|
-
|
|
22
|
-
To properly configure OIDC trusted publishing for this package:
|
|
23
|
-
|
|
24
|
-
1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
|
|
25
|
-
2. Configure the trusted publisher (e.g., GitHub Actions)
|
|
26
|
-
3. Specify the repository and workflow that should be allowed to publish
|
|
27
|
-
4. Use the configured workflow to publish your actual package
|
|
28
|
-
|
|
29
|
-
## DO NOT USE THIS PACKAGE
|
|
30
|
-
|
|
31
|
-
This package is a placeholder for OIDC configuration only. It:
|
|
32
|
-
- Contains no executable code
|
|
33
|
-
- Provides no functionality
|
|
34
|
-
- Should not be installed as a dependency
|
|
35
|
-
- Exists only for administrative purposes
|
|
36
|
-
|
|
37
|
-
## More Information
|
|
38
|
-
|
|
39
|
-
For more details about npm's trusted publishing feature, see:
|
|
40
|
-
- [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
|
|
41
|
-
- [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
**Maintained for OIDC setup purposes only**
|