@vireocodedev/shell 0.2.0 → 0.2.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/config/shellConfig.d.ts +24 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +260 -0
- package/dist/index.js.map +1 -0
- package/dist/navigation/authRedirect.d.ts +11 -0
- package/dist/navigation/shellNavigation.d.ts +36 -0
- package/dist/overlay-history/overlayHistory.d.ts +37 -0
- package/dist/sitemap/routePath.d.ts +5 -0
- package/dist/sitemap/shellSitemap.d.ts +18 -0
- package/dist/sitemap/shellSitemap.types.d.ts +80 -0
- package/package.json +1 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ShellNavigationEntry } from '../navigation/shellNavigation';
|
|
3
|
+
import { ShellPage, ShellPermission, ShellSitemap } from '../sitemap/shellSitemap.types';
|
|
4
|
+
export declare const ShellModeSchema: z.ZodEnum<{
|
|
5
|
+
dashboard: "dashboard";
|
|
6
|
+
public: "public";
|
|
7
|
+
bare: "bare";
|
|
8
|
+
}>;
|
|
9
|
+
export type ShellMode = z.infer<typeof ShellModeSchema>;
|
|
10
|
+
export type ShellConfig<TPermission extends ShellPermission = ShellPermission, TContext = unknown> = {
|
|
11
|
+
mode: ShellMode;
|
|
12
|
+
sitemap: ShellSitemap;
|
|
13
|
+
entryPage: ShellPage<string, string, TPermission, TContext>;
|
|
14
|
+
loginPage?: ShellPage<string, string, TPermission, TContext>;
|
|
15
|
+
unauthorizedPage?: ShellPage<string, string, TPermission, TContext>;
|
|
16
|
+
navigation?: {
|
|
17
|
+
authenticated?: readonly ShellNavigationEntry<TPermission, TContext>[];
|
|
18
|
+
public?: readonly ShellNavigationEntry<TPermission, TContext>[];
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export type ShellConfigValidationOptions<TPermission extends ShellPermission = ShellPermission> = {
|
|
22
|
+
permissions?: ReadonlySet<TPermission> | readonly TPermission[] | Readonly<Record<TPermission, unknown>>;
|
|
23
|
+
};
|
|
24
|
+
export declare function defineShellConfig<TPermission extends ShellPermission = ShellPermission, TContext = unknown>(config: ShellConfig<TPermission, TContext>, options?: ShellConfigValidationOptions<TPermission>): ShellConfig<TPermission, TContext>;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { defineShellConfig, ShellModeSchema } from './config/shellConfig';
|
|
2
|
+
export type { ShellConfig, ShellConfigValidationOptions, ShellMode } from './config/shellConfig';
|
|
3
|
+
export { createAuthRedirectState, isSafeInternalPath, resolvePostLoginPath } from './navigation/authRedirect';
|
|
4
|
+
export type { ShellAuthRedirectState, ShellLocationSnapshot } from './navigation/authRedirect';
|
|
5
|
+
export { ShellNavigationIdSchema, shellNavigation } from './navigation/shellNavigation';
|
|
6
|
+
export type { ShellNavigationEntry } from './navigation/shellNavigation';
|
|
7
|
+
export { createOverlayHistoryRegistry, getCommonOverlayPrefixLength, OVERLAY_HISTORY_STATE_KEY, readOverlayStack, resolveOverlayHistoryAction, withOverlayStack, } from './overlay-history/overlayHistory';
|
|
8
|
+
export type { OverlayHistoryAction, OverlayHistoryEntry, OverlayHistoryEntryId, OverlayHistoryRegistry, OverlayHistoryResolveInput, } from './overlay-history/overlayHistory';
|
|
9
|
+
export { generateShellPath, getRequiredRouteParamNames, joinRoutePattern, normalizeRoutePath, } from './sitemap/routePath';
|
|
10
|
+
export { createShellSitemap, defineShellPages, defineShellSections, ShellRouteKeySchema, ShellRoutePathSchema, } from './sitemap/shellSitemap';
|
|
11
|
+
export type { ResolvedShellRoute, ShellLabel, ShellPage, ShellPageDefinition, ShellPathForPage, ShellPermission, ShellPermissionScope, ShellRouteMetadata, ShellRouteNode, ShellRouteParams, ShellRouteParamValue, ShellRouteTreeNode, ShellRouteTreePageEntries, ShellSection, ShellSectionDefinition, ShellSitemap, } from './sitemap/shellSitemap.types';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { z as e } from "zod";
|
|
2
|
+
//#region src/config/shellConfig.ts
|
|
3
|
+
var t = e.enum([
|
|
4
|
+
"dashboard",
|
|
5
|
+
"public",
|
|
6
|
+
"bare"
|
|
7
|
+
]);
|
|
8
|
+
function n(e, t) {
|
|
9
|
+
return t ? t instanceof Set ? t.has(e) : Array.isArray(t) ? t.includes(e) : Object.prototype.hasOwnProperty.call(t, e) : !0;
|
|
10
|
+
}
|
|
11
|
+
function r(r, i = {}) {
|
|
12
|
+
let a = [];
|
|
13
|
+
t.parse(r.mode);
|
|
14
|
+
let o = (t, n) => {
|
|
15
|
+
t && r.sitemap.getRoute(t.key)?.kind !== "page" && a.push({
|
|
16
|
+
code: e.ZodIssueCode.custom,
|
|
17
|
+
path: n,
|
|
18
|
+
message: `Page "${t.key}" is not mounted in the sitemap.`
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
if (o(r.entryPage, ["entryPage"]), o(r.loginPage, ["loginPage"]), o(r.unauthorizedPage, ["unauthorizedPage"]), Object.entries(r.navigation ?? {}).forEach(([t, r]) => {
|
|
22
|
+
let s = /* @__PURE__ */ new Set();
|
|
23
|
+
r?.forEach((r, c) => {
|
|
24
|
+
r.type === "item" && o(r.page, [
|
|
25
|
+
"navigation",
|
|
26
|
+
t,
|
|
27
|
+
c,
|
|
28
|
+
"page"
|
|
29
|
+
]), (r.type === "heading" || r.type === "action") && (s.has(r.id) && a.push({
|
|
30
|
+
code: e.ZodIssueCode.custom,
|
|
31
|
+
path: [
|
|
32
|
+
"navigation",
|
|
33
|
+
t,
|
|
34
|
+
c,
|
|
35
|
+
"id"
|
|
36
|
+
],
|
|
37
|
+
message: `Duplicate navigation id "${r.id}".`
|
|
38
|
+
}), s.add(r.id)), "permission" in r && r.permission && !n(r.permission, i.permissions) && a.push({
|
|
39
|
+
code: e.ZodIssueCode.custom,
|
|
40
|
+
path: [
|
|
41
|
+
"navigation",
|
|
42
|
+
t,
|
|
43
|
+
c,
|
|
44
|
+
"permission"
|
|
45
|
+
],
|
|
46
|
+
message: `Unknown permission "${r.permission}".`
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}), r.sitemap.routes.forEach((t, r) => {
|
|
50
|
+
let o = t.node.permission;
|
|
51
|
+
o && !n(o, i.permissions) && a.push({
|
|
52
|
+
code: e.ZodIssueCode.custom,
|
|
53
|
+
path: [
|
|
54
|
+
"sitemap",
|
|
55
|
+
"routes",
|
|
56
|
+
r,
|
|
57
|
+
"permission"
|
|
58
|
+
],
|
|
59
|
+
message: `Unknown permission "${o}".`
|
|
60
|
+
});
|
|
61
|
+
}), a.length > 0) throw new e.ZodError(a);
|
|
62
|
+
return Object.freeze(r);
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/navigation/authRedirect.ts
|
|
66
|
+
function i(e) {
|
|
67
|
+
if (!e.startsWith("/") || e.startsWith("//") || e.includes("\\")) return !1;
|
|
68
|
+
try {
|
|
69
|
+
return new URL(e, "https://starter.invalid").origin === "https://starter.invalid";
|
|
70
|
+
} catch {
|
|
71
|
+
return !1;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function a(e, t) {
|
|
75
|
+
let n = `${e.pathname}${e.search ?? ""}${e.hash ?? ""}`;
|
|
76
|
+
if (!(e.pathname === t || !i(n))) return { from: n };
|
|
77
|
+
}
|
|
78
|
+
function o(e, t) {
|
|
79
|
+
if (typeof e != "object" || !e || !("from" in e)) return t;
|
|
80
|
+
let n = e.from;
|
|
81
|
+
return typeof n == "string" && i(n) ? n : t;
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/navigation/shellNavigation.ts
|
|
85
|
+
var s = e.string().trim().min(1, "Navigation identifiers cannot be blank."), c = {
|
|
86
|
+
item(e, t = {}) {
|
|
87
|
+
return {
|
|
88
|
+
type: "item",
|
|
89
|
+
page: e,
|
|
90
|
+
...t
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
heading(e, t) {
|
|
94
|
+
return {
|
|
95
|
+
type: "heading",
|
|
96
|
+
id: s.parse(e),
|
|
97
|
+
label: t
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
action(e, t, n = {}) {
|
|
101
|
+
return {
|
|
102
|
+
type: "action",
|
|
103
|
+
id: s.parse(e),
|
|
104
|
+
label: t,
|
|
105
|
+
...n
|
|
106
|
+
};
|
|
107
|
+
},
|
|
108
|
+
divider() {
|
|
109
|
+
return { type: "divider" };
|
|
110
|
+
}
|
|
111
|
+
}, l = "__starterShellOverlayStack";
|
|
112
|
+
function u(e) {
|
|
113
|
+
return Array.isArray(e) && e.every((e) => typeof e == "string");
|
|
114
|
+
}
|
|
115
|
+
function d(e) {
|
|
116
|
+
if (typeof e != "object" || !e) return [];
|
|
117
|
+
let t = e[l];
|
|
118
|
+
return u(t) ? [...t] : [];
|
|
119
|
+
}
|
|
120
|
+
function f(e, t) {
|
|
121
|
+
let n = typeof e == "object" && e ? { ...e } : {};
|
|
122
|
+
return t.length === 0 ? delete n[l] : n[l] = [...t], n;
|
|
123
|
+
}
|
|
124
|
+
function p(e, t) {
|
|
125
|
+
let n = Math.min(e.length, t.length), r = 0;
|
|
126
|
+
for (; r < n && e[r] === t[r];) r += 1;
|
|
127
|
+
return r;
|
|
128
|
+
}
|
|
129
|
+
function m(e) {
|
|
130
|
+
let { desired: t, actual: n, previousActual: r, navigationType: i, locationChanged: a, urlChanged: o } = e;
|
|
131
|
+
if (a && o) return { type: "idle" };
|
|
132
|
+
if (a && i === "POP") {
|
|
133
|
+
let e = [...r].reverse().find((e) => !n.includes(e) && t.includes(e));
|
|
134
|
+
if (e !== void 0) return {
|
|
135
|
+
type: "requestClose",
|
|
136
|
+
id: e
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
let s = p(t, n);
|
|
140
|
+
return n.length > s ? { type: "consume" } : t.length > s ? {
|
|
141
|
+
type: "push",
|
|
142
|
+
stack: [...n, t[s]]
|
|
143
|
+
} : { type: "idle" };
|
|
144
|
+
}
|
|
145
|
+
function h() {
|
|
146
|
+
let e = [], t = /* @__PURE__ */ new Set(), n = () => t.forEach((e) => e());
|
|
147
|
+
return {
|
|
148
|
+
register(t) {
|
|
149
|
+
return e = e.some((e) => e.id === t.id) ? e.map((e) => e.id === t.id ? t : e) : [...e, t], n(), () => this.unregister(t.id);
|
|
150
|
+
},
|
|
151
|
+
unregister(t) {
|
|
152
|
+
e.some((e) => e.id === t) && (e = e.filter((e) => e.id !== t), n());
|
|
153
|
+
},
|
|
154
|
+
getSnapshot() {
|
|
155
|
+
return e;
|
|
156
|
+
},
|
|
157
|
+
subscribe(e) {
|
|
158
|
+
return t.add(e), () => t.delete(e);
|
|
159
|
+
},
|
|
160
|
+
clear() {
|
|
161
|
+
e.length !== 0 && (e = [], n());
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
//#endregion
|
|
166
|
+
//#region src/sitemap/routePath.ts
|
|
167
|
+
function g(e) {
|
|
168
|
+
return e.replace(/^\/+|\/+$/gu, "");
|
|
169
|
+
}
|
|
170
|
+
function _(e, t) {
|
|
171
|
+
let n = g(e), r = g(t);
|
|
172
|
+
return !n && !r ? "/" : `/${[n, r].filter(Boolean).join("/")}`;
|
|
173
|
+
}
|
|
174
|
+
function v(e) {
|
|
175
|
+
return Array.from(e.matchAll(/:([^/?*]+)([?*]?)/gu)).filter((e) => e[2] === "").map((e) => e[1]);
|
|
176
|
+
}
|
|
177
|
+
function y(e, t) {
|
|
178
|
+
let n = String(e);
|
|
179
|
+
return t ? n.split("/").map(encodeURIComponent).join("/") : encodeURIComponent(n);
|
|
180
|
+
}
|
|
181
|
+
function b(e, t = {}) {
|
|
182
|
+
return e.split("/").flatMap((n) => {
|
|
183
|
+
let r = n.match(/^:([^?*]+)([?*]?)$/u);
|
|
184
|
+
if (!r) return [n];
|
|
185
|
+
let [, i, a] = r, o = t[i];
|
|
186
|
+
if (o == null) {
|
|
187
|
+
if (a === "?" || a === "*") return [];
|
|
188
|
+
throw Error(`Missing required route parameter "${i}" for pattern "${e}".`);
|
|
189
|
+
}
|
|
190
|
+
return [y(o, a === "*")];
|
|
191
|
+
}).join("/") || "/";
|
|
192
|
+
}
|
|
193
|
+
//#endregion
|
|
194
|
+
//#region src/sitemap/shellSitemap.ts
|
|
195
|
+
var x = e.string().trim().min(1, "Route keys cannot be blank."), S = e.string().refine((e) => !e.startsWith("/"), "Route paths must be local segments without a leading slash.").refine((e) => !e.includes("//"), "Route paths cannot contain empty segments.");
|
|
196
|
+
function C(e, t) {
|
|
197
|
+
return Object.fromEntries(Object.entries(e).map(([e, n]) => {
|
|
198
|
+
let r = x.parse(e);
|
|
199
|
+
return S.parse(n.routePath), [e, Object.freeze({
|
|
200
|
+
...n,
|
|
201
|
+
kind: t,
|
|
202
|
+
key: r
|
|
203
|
+
})];
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
206
|
+
function w(e) {
|
|
207
|
+
return C(e, "page");
|
|
208
|
+
}
|
|
209
|
+
function T(e) {
|
|
210
|
+
return C(e, "section");
|
|
211
|
+
}
|
|
212
|
+
function E(e) {
|
|
213
|
+
return "kind" in e;
|
|
214
|
+
}
|
|
215
|
+
function D(e) {
|
|
216
|
+
let t = /* @__PURE__ */ new Map(), n = /* @__PURE__ */ new Map(), r = /* @__PURE__ */ new Map(), i = [];
|
|
217
|
+
function a(e, o, s) {
|
|
218
|
+
e.forEach((e) => {
|
|
219
|
+
let c = E(e) ? e : e.node;
|
|
220
|
+
if (x.parse(c.key), S.parse(c.routePath), n.has(c.key)) throw Error(`Route key "${c.key}" is mounted more than once.`);
|
|
221
|
+
let l = _(o, c.routePath), u = r.get(l);
|
|
222
|
+
if (u) throw Error(`Route pattern "${l}" is shared by "${u}" and "${c.key}".`);
|
|
223
|
+
let d = {
|
|
224
|
+
key: c.key,
|
|
225
|
+
kind: c.kind,
|
|
226
|
+
pathPattern: l,
|
|
227
|
+
parentKey: s,
|
|
228
|
+
node: c
|
|
229
|
+
};
|
|
230
|
+
n.set(c.key, d), r.set(l, c.key), c.kind === "page" && t.set(c.key, l), E(e) || (e.redirectTo && i.push({
|
|
231
|
+
route: d,
|
|
232
|
+
target: e.redirectTo
|
|
233
|
+
}), e.children && a(e.children, l, c.key));
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
a(e, ""), i.forEach(({ route: e, target: n }) => {
|
|
237
|
+
let r = t.get(n.key);
|
|
238
|
+
if (!r) throw Error(`Redirect target "${n.key}" is not mounted in the sitemap.`);
|
|
239
|
+
e.redirectToPattern = r;
|
|
240
|
+
});
|
|
241
|
+
let o = {
|
|
242
|
+
routes: Object.freeze([...n.values()]),
|
|
243
|
+
getPath(e, ...t) {
|
|
244
|
+
return b(o.getPathPattern(e), t[0]);
|
|
245
|
+
},
|
|
246
|
+
getPathPattern(e) {
|
|
247
|
+
let n = t.get(e.key);
|
|
248
|
+
if (!n) throw Error(`Page "${e.key}" is not mounted in the sitemap.`);
|
|
249
|
+
return n;
|
|
250
|
+
},
|
|
251
|
+
getRoute(e) {
|
|
252
|
+
return n.get(e);
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
return o;
|
|
256
|
+
}
|
|
257
|
+
//#endregion
|
|
258
|
+
export { l as OVERLAY_HISTORY_STATE_KEY, t as ShellModeSchema, s as ShellNavigationIdSchema, x as ShellRouteKeySchema, S as ShellRoutePathSchema, a as createAuthRedirectState, h as createOverlayHistoryRegistry, D as createShellSitemap, r as defineShellConfig, w as defineShellPages, T as defineShellSections, b as generateShellPath, p as getCommonOverlayPrefixLength, v as getRequiredRouteParamNames, i as isSafeInternalPath, _ as joinRoutePattern, g as normalizeRoutePath, d as readOverlayStack, m as resolveOverlayHistoryAction, o as resolvePostLoginPath, c as shellNavigation, f as withOverlayStack };
|
|
259
|
+
|
|
260
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/config/shellConfig.ts","../src/navigation/authRedirect.ts","../src/navigation/shellNavigation.ts","../src/overlay-history/overlayHistory.ts","../src/sitemap/routePath.ts","../src/sitemap/shellSitemap.ts"],"sourcesContent":["import { z } from \"zod\";\nimport type { ShellNavigationEntry } from \"../navigation/shellNavigation\";\nimport type { ShellPage, ShellPermission, ShellSitemap } from \"../sitemap/shellSitemap.types\";\n\nexport const ShellModeSchema = z.enum([\"dashboard\", \"public\", \"bare\"]);\nexport type ShellMode = z.infer<typeof ShellModeSchema>;\n\nexport type ShellConfig<TPermission extends ShellPermission = ShellPermission, TContext = unknown> = {\n mode: ShellMode;\n sitemap: ShellSitemap;\n entryPage: ShellPage<string, string, TPermission, TContext>;\n loginPage?: ShellPage<string, string, TPermission, TContext>;\n unauthorizedPage?: ShellPage<string, string, TPermission, TContext>;\n navigation?: {\n authenticated?: readonly ShellNavigationEntry<TPermission, TContext>[];\n public?: readonly ShellNavigationEntry<TPermission, TContext>[];\n };\n};\n\nexport type ShellConfigValidationOptions<TPermission extends ShellPermission = ShellPermission> = {\n permissions?: ReadonlySet<TPermission> | readonly TPermission[] | Readonly<Record<TPermission, unknown>>;\n};\n\nfunction hasPermission<TPermission extends ShellPermission>(\n permission: TPermission,\n registry: ShellConfigValidationOptions<TPermission>[\"permissions\"],\n): boolean {\n if (!registry) return true;\n if (registry instanceof Set) return registry.has(permission);\n if (Array.isArray(registry)) return registry.includes(permission);\n return Object.prototype.hasOwnProperty.call(registry, permission);\n}\n\nexport function defineShellConfig<TPermission extends ShellPermission = ShellPermission, TContext = unknown>(\n config: ShellConfig<TPermission, TContext>,\n options: ShellConfigValidationOptions<TPermission> = {},\n): ShellConfig<TPermission, TContext> {\n const issues: z.ZodIssue[] = [];\n ShellModeSchema.parse(config.mode);\n\n const assertMountedPage = (page: { key: string } | undefined, path: (string | number)[]) => {\n if (!page) return;\n const route = config.sitemap.getRoute(page.key);\n if (route?.kind !== \"page\") {\n issues.push({\n code: z.ZodIssueCode.custom,\n path,\n message: `Page \"${page.key}\" is not mounted in the sitemap.`,\n });\n }\n };\n\n assertMountedPage(config.entryPage, [\"entryPage\"]);\n assertMountedPage(config.loginPage, [\"loginPage\"]);\n assertMountedPage(config.unauthorizedPage, [\"unauthorizedPage\"]);\n\n Object.entries(config.navigation ?? {}).forEach(([group, entries]) => {\n const seenIds = new Set<string>();\n entries?.forEach((entry, index) => {\n if (entry.type === \"item\") assertMountedPage(entry.page, [\"navigation\", group, index, \"page\"]);\n if (entry.type === \"heading\" || entry.type === \"action\") {\n if (seenIds.has(entry.id)) {\n issues.push({\n code: z.ZodIssueCode.custom,\n path: [\"navigation\", group, index, \"id\"],\n message: `Duplicate navigation id \"${entry.id}\".`,\n });\n }\n seenIds.add(entry.id);\n }\n if (\"permission\" in entry && entry.permission && !hasPermission(entry.permission, options.permissions)) {\n issues.push({\n code: z.ZodIssueCode.custom,\n path: [\"navigation\", group, index, \"permission\"],\n message: `Unknown permission \"${entry.permission}\".`,\n });\n }\n });\n });\n\n config.sitemap.routes.forEach((route, index) => {\n const permission = route.node.permission as TPermission | undefined;\n if (permission && !hasPermission(permission, options.permissions)) {\n issues.push({\n code: z.ZodIssueCode.custom,\n path: [\"sitemap\", \"routes\", index, \"permission\"],\n message: `Unknown permission \"${permission}\".`,\n });\n }\n });\n\n if (issues.length > 0) throw new z.ZodError(issues);\n return Object.freeze(config);\n}\n","export type ShellLocationSnapshot = { pathname: string; search?: string; hash?: string };\nexport type ShellAuthRedirectState = { from: string };\n\nexport function isSafeInternalPath(path: string): boolean {\n if (!path.startsWith(\"/\") || path.startsWith(\"//\") || path.includes(\"\\\\\")) return false;\n try {\n return new URL(path, \"https://starter.invalid\").origin === \"https://starter.invalid\";\n } catch {\n return false;\n }\n}\n\nexport function createAuthRedirectState(\n location: ShellLocationSnapshot,\n loginPath: string,\n): ShellAuthRedirectState | undefined {\n const returnPath = `${location.pathname}${location.search ?? \"\"}${location.hash ?? \"\"}`;\n if (location.pathname === loginPath || !isSafeInternalPath(returnPath)) return undefined;\n return { from: returnPath };\n}\n\nexport function resolvePostLoginPath(state: unknown, fallbackPath: string): string {\n if (typeof state !== \"object\" || state == null || !(\"from\" in state)) return fallbackPath;\n const from = (state as { from?: unknown }).from;\n return typeof from === \"string\" && isSafeInternalPath(from) ? from : fallbackPath;\n}\n","import { z } from \"zod\";\nimport type { ShellLabel, ShellPage, ShellPermission, ShellPermissionScope } from \"../sitemap/shellSitemap.types\";\n\nexport const ShellNavigationIdSchema = z.string().trim().min(1, \"Navigation identifiers cannot be blank.\");\n\nexport type ShellNavigationEntry<TPermission extends ShellPermission = ShellPermission, TContext = unknown> =\n | {\n type: \"item\";\n page: ShellPage<string, string, TPermission, TContext>;\n label?: ShellLabel<TContext>;\n icon?: string;\n disabled?: boolean;\n disabledReason?: ShellLabel<TContext>;\n permission?: TPermission;\n permissionScope?: ShellPermissionScope;\n }\n | { type: \"heading\"; id: string; label: ShellLabel<TContext> }\n | { type: \"action\"; id: string; label: ShellLabel<TContext>; icon?: string; permission?: TPermission }\n | { type: \"divider\" };\n\nexport const shellNavigation = {\n item<TPermission extends ShellPermission = ShellPermission, TContext = unknown>(\n page: ShellPage<string, string, TPermission, TContext>,\n overrides: Omit<ShellNavigationEntry<TPermission, TContext> & { type: \"item\" }, \"page\" | \"type\"> = {},\n ): ShellNavigationEntry<TPermission, TContext> {\n return { type: \"item\", page, ...overrides };\n },\n heading<TContext = unknown>(\n id: string,\n label: ShellLabel<TContext>,\n ): ShellNavigationEntry<ShellPermission, TContext> {\n return { type: \"heading\", id: ShellNavigationIdSchema.parse(id), label };\n },\n action<TPermission extends ShellPermission = ShellPermission, TContext = unknown>(\n id: string,\n label: ShellLabel<TContext>,\n options: { icon?: string; permission?: TPermission } = {},\n ): ShellNavigationEntry<TPermission, TContext> {\n return { type: \"action\", id: ShellNavigationIdSchema.parse(id), label, ...options };\n },\n divider(): ShellNavigationEntry {\n return { type: \"divider\" };\n },\n};\n","export const OVERLAY_HISTORY_STATE_KEY = \"__starterShellOverlayStack\";\nexport type OverlayHistoryEntryId = string;\nexport type OverlayHistoryEntry = { id: OverlayHistoryEntryId; requestClose: () => void };\nexport type OverlayHistoryAction =\n | { type: \"idle\" }\n | { type: \"push\"; stack: readonly OverlayHistoryEntryId[] }\n | { type: \"consume\" }\n | { type: \"requestClose\"; id: OverlayHistoryEntryId };\n\nexport type OverlayHistoryResolveInput = {\n desired: readonly OverlayHistoryEntryId[];\n actual: readonly OverlayHistoryEntryId[];\n previousActual: readonly OverlayHistoryEntryId[];\n navigationType: \"POP\" | \"PUSH\" | \"REPLACE\";\n locationChanged: boolean;\n urlChanged: boolean;\n};\n\nfunction isEntryIdList(value: unknown): value is OverlayHistoryEntryId[] {\n return Array.isArray(value) && value.every(item => typeof item === \"string\");\n}\n\nexport function readOverlayStack(state: unknown): OverlayHistoryEntryId[] {\n if (typeof state !== \"object\" || state === null) return [];\n const stack = (state as Record<string, unknown>)[OVERLAY_HISTORY_STATE_KEY];\n return isEntryIdList(stack) ? [...stack] : [];\n}\n\nexport function withOverlayStack(state: unknown, stack: readonly OverlayHistoryEntryId[]): Record<string, unknown> {\n const nextState = typeof state === \"object\" && state !== null ? { ...(state as Record<string, unknown>) } : {};\n if (stack.length === 0) delete nextState[OVERLAY_HISTORY_STATE_KEY];\n else nextState[OVERLAY_HISTORY_STATE_KEY] = [...stack];\n return nextState;\n}\n\nexport function getCommonOverlayPrefixLength(\n left: readonly OverlayHistoryEntryId[],\n right: readonly OverlayHistoryEntryId[],\n): number {\n const max = Math.min(left.length, right.length);\n let index = 0;\n while (index < max && left[index] === right[index]) index += 1;\n return index;\n}\n\nexport function resolveOverlayHistoryAction(input: OverlayHistoryResolveInput): OverlayHistoryAction {\n const { desired, actual, previousActual, navigationType, locationChanged, urlChanged } = input;\n if (locationChanged && urlChanged) return { type: \"idle\" };\n\n if (locationChanged && navigationType === \"POP\") {\n const poppedStillOpen = [...previousActual].reverse().find(id => !actual.includes(id) && desired.includes(id));\n if (poppedStillOpen !== undefined) return { type: \"requestClose\", id: poppedStillOpen };\n }\n\n const prefixLength = getCommonOverlayPrefixLength(desired, actual);\n if (actual.length > prefixLength) return { type: \"consume\" };\n if (desired.length > prefixLength) return { type: \"push\", stack: [...actual, desired[prefixLength]] };\n return { type: \"idle\" };\n}\n\nexport type OverlayHistoryRegistry = {\n register(entry: OverlayHistoryEntry): () => void;\n unregister(id: OverlayHistoryEntryId): void;\n getSnapshot(): readonly OverlayHistoryEntry[];\n subscribe(listener: () => void): () => void;\n clear(): void;\n};\n\nexport function createOverlayHistoryRegistry(): OverlayHistoryRegistry {\n let entries: readonly OverlayHistoryEntry[] = [];\n const listeners = new Set<() => void>();\n const publish = () => listeners.forEach(listener => listener());\n\n return {\n register(entry) {\n const exists = entries.some(candidate => candidate.id === entry.id);\n entries = exists\n ? entries.map(candidate => (candidate.id === entry.id ? entry : candidate))\n : [...entries, entry];\n publish();\n return () => this.unregister(entry.id);\n },\n unregister(id) {\n if (!entries.some(entry => entry.id === id)) return;\n entries = entries.filter(entry => entry.id !== id);\n publish();\n },\n getSnapshot() {\n return entries;\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n clear() {\n if (entries.length === 0) return;\n entries = [];\n publish();\n },\n };\n}\n","import type { ShellRouteParams } from \"./shellSitemap.types\";\n\nexport function normalizeRoutePath(path: string): string {\n return path.replace(/^\\/+|\\/+$/gu, \"\");\n}\n\nexport function joinRoutePattern(parentPath: string, routePath: string): string {\n const parent = normalizeRoutePath(parentPath);\n const child = normalizeRoutePath(routePath);\n\n if (!parent && !child) return \"/\";\n return `/${[parent, child].filter(Boolean).join(\"/\")}`;\n}\n\nexport function getRequiredRouteParamNames(pattern: string): string[] {\n return Array.from(pattern.matchAll(/:([^/?*]+)([?*]?)/gu))\n .filter(match => match[2] === \"\")\n .map(match => match[1]);\n}\n\nfunction encodeRouteValue(value: string | number, preserveSlashes: boolean): string {\n const text = String(value);\n return preserveSlashes ? text.split(\"/\").map(encodeURIComponent).join(\"/\") : encodeURIComponent(text);\n}\n\nexport function generateShellPath(pattern: string, params: ShellRouteParams = {}): string {\n const segments = pattern.split(\"/\");\n const generated = segments.flatMap(segment => {\n const match = segment.match(/^:([^?*]+)([?*]?)$/u);\n if (!match) return [segment];\n\n const [, name, modifier] = match;\n const value = params[name];\n if (value == null) {\n if (modifier === \"?\" || modifier === \"*\") return [];\n throw new Error(`Missing required route parameter \"${name}\" for pattern \"${pattern}\".`);\n }\n\n return [encodeRouteValue(value, modifier === \"*\")];\n });\n\n return generated.join(\"/\") || \"/\";\n}\n","import { z } from \"zod\";\nimport { generateShellPath, joinRoutePattern } from \"./routePath\";\nimport type {\n ResolvedShellRoute,\n ShellPage,\n ShellPageDefinition,\n ShellPermission,\n ShellRouteNode,\n ShellRouteTreeNode,\n ShellSection,\n ShellSectionDefinition,\n ShellSitemap,\n} from \"./shellSitemap.types\";\n\nexport const ShellRouteKeySchema = z.string().trim().min(1, \"Route keys cannot be blank.\");\nexport const ShellRoutePathSchema = z\n .string()\n .refine(path => !path.startsWith(\"/\"), \"Route paths must be local segments without a leading slash.\")\n .refine(path => !path.includes(\"//\"), \"Route paths cannot contain empty segments.\");\n\ntype StrictConfig<TConfig, TShape> = TConfig & Record<Exclude<keyof TConfig, keyof TShape>, never>;\ntype StrictConfigRecord<TRecord, TShape> = {\n readonly [K in keyof TRecord]: StrictConfig<TRecord[K], TShape>;\n};\n\ntype ShellPageRecord<\n TPages extends Record<string, ShellPageDefinition<TPermission, TContext>>,\n TPermission extends ShellPermission,\n TContext,\n> = {\n readonly [K in keyof TPages]: ShellPage<K & string, TPages[K][\"routePath\"], TPermission, TContext> & TPages[K];\n};\n\ntype ShellSectionRecord<\n TSections extends Record<string, ShellSectionDefinition<TPermission, TContext>>,\n TPermission extends ShellPermission,\n TContext,\n> = {\n readonly [K in keyof TSections]: ShellSection<K & string, TSections[K][\"routePath\"], TPermission, TContext> &\n TSections[K];\n};\n\nfunction defineNodes<TKind extends \"page\" | \"section\">(\n definitions: Readonly<Record<string, { routePath: string }>>,\n kind: TKind,\n): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(definitions).map(([key, definition]) => {\n const parsedKey = ShellRouteKeySchema.parse(key);\n ShellRoutePathSchema.parse(definition.routePath);\n return [key, Object.freeze({ ...definition, kind, key: parsedKey })];\n }),\n );\n}\n\nexport function defineShellPages<\n TPermission extends ShellPermission = ShellPermission,\n TContext = unknown,\n const TPages extends Record<string, ShellPageDefinition<TPermission, TContext>> = Record<\n string,\n ShellPageDefinition<TPermission, TContext>\n >,\n>(\n pages: StrictConfigRecord<TPages, ShellPageDefinition<TPermission, TContext>>,\n): ShellPageRecord<TPages, TPermission, TContext> {\n return defineNodes(pages, \"page\") as unknown as ShellPageRecord<TPages, TPermission, TContext>;\n}\n\nexport function defineShellSections<\n TPermission extends ShellPermission = ShellPermission,\n TContext = unknown,\n const TSections extends Record<string, ShellSectionDefinition<TPermission, TContext>> = Record<\n string,\n ShellSectionDefinition<TPermission, TContext>\n >,\n>(\n sections: StrictConfigRecord<TSections, ShellSectionDefinition<TPermission, TContext>>,\n): ShellSectionRecord<TSections, TPermission, TContext> {\n return defineNodes(sections, \"section\") as unknown as ShellSectionRecord<TSections, TPermission, TContext>;\n}\n\nfunction isDirectNode(node: ShellRouteTreeNode): node is ShellPage {\n return \"kind\" in node;\n}\n\nexport function createShellSitemap<const TRoutes extends readonly ShellRouteTreeNode[]>(\n tree: TRoutes,\n): ShellSitemap<TRoutes> {\n const pagePatterns = new Map<string, string>();\n const routesByKey = new Map<string, ResolvedShellRoute>();\n const pathOwners = new Map<string, string>();\n const pendingRedirects: Array<{ route: ResolvedShellRoute; target: ShellPage }> = [];\n\n function visit(nodes: readonly ShellRouteTreeNode[], parentPattern: string, parentKey?: string): void {\n nodes.forEach(treeNode => {\n const node: ShellRouteNode = isDirectNode(treeNode) ? treeNode : treeNode.node;\n ShellRouteKeySchema.parse(node.key);\n ShellRoutePathSchema.parse(node.routePath);\n\n if (routesByKey.has(node.key)) throw new Error(`Route key \"${node.key}\" is mounted more than once.`);\n const pathPattern = joinRoutePattern(parentPattern, node.routePath);\n const existingPathOwner = pathOwners.get(pathPattern);\n if (existingPathOwner) {\n throw new Error(`Route pattern \"${pathPattern}\" is shared by \"${existingPathOwner}\" and \"${node.key}\".`);\n }\n\n const route: ResolvedShellRoute = { key: node.key, kind: node.kind, pathPattern, parentKey, node };\n routesByKey.set(node.key, route);\n pathOwners.set(pathPattern, node.key);\n if (node.kind === \"page\") pagePatterns.set(node.key, pathPattern);\n\n if (!isDirectNode(treeNode)) {\n if (treeNode.redirectTo) pendingRedirects.push({ route, target: treeNode.redirectTo });\n if (treeNode.children) visit(treeNode.children, pathPattern, node.key);\n }\n });\n }\n\n visit(tree, \"\");\n pendingRedirects.forEach(({ route, target }) => {\n const targetPattern = pagePatterns.get(target.key);\n if (!targetPattern) throw new Error(`Redirect target \"${target.key}\" is not mounted in the sitemap.`);\n route.redirectToPattern = targetPattern;\n });\n\n const sitemap: ShellSitemap<TRoutes> = {\n routes: Object.freeze([...routesByKey.values()]),\n getPath(page, ...args) {\n const pattern = sitemap.getPathPattern(page);\n return generateShellPath(pattern, args[0]);\n },\n getPathPattern(page) {\n const pattern = pagePatterns.get(page.key);\n if (!pattern) throw new Error(`Page \"${page.key}\" is not mounted in the sitemap.`);\n return pattern as never;\n },\n getRoute(key) {\n return routesByKey.get(key);\n },\n };\n\n return sitemap;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type ShellLocationSnapshot = {
|
|
2
|
+
pathname: string;
|
|
3
|
+
search?: string;
|
|
4
|
+
hash?: string;
|
|
5
|
+
};
|
|
6
|
+
export type ShellAuthRedirectState = {
|
|
7
|
+
from: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function isSafeInternalPath(path: string): boolean;
|
|
10
|
+
export declare function createAuthRedirectState(location: ShellLocationSnapshot, loginPath: string): ShellAuthRedirectState | undefined;
|
|
11
|
+
export declare function resolvePostLoginPath(state: unknown, fallbackPath: string): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ShellLabel, ShellPage, ShellPermission, ShellPermissionScope } from '../sitemap/shellSitemap.types';
|
|
3
|
+
export declare const ShellNavigationIdSchema: z.ZodString;
|
|
4
|
+
export type ShellNavigationEntry<TPermission extends ShellPermission = ShellPermission, TContext = unknown> = {
|
|
5
|
+
type: "item";
|
|
6
|
+
page: ShellPage<string, string, TPermission, TContext>;
|
|
7
|
+
label?: ShellLabel<TContext>;
|
|
8
|
+
icon?: string;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
disabledReason?: ShellLabel<TContext>;
|
|
11
|
+
permission?: TPermission;
|
|
12
|
+
permissionScope?: ShellPermissionScope;
|
|
13
|
+
} | {
|
|
14
|
+
type: "heading";
|
|
15
|
+
id: string;
|
|
16
|
+
label: ShellLabel<TContext>;
|
|
17
|
+
} | {
|
|
18
|
+
type: "action";
|
|
19
|
+
id: string;
|
|
20
|
+
label: ShellLabel<TContext>;
|
|
21
|
+
icon?: string;
|
|
22
|
+
permission?: TPermission;
|
|
23
|
+
} | {
|
|
24
|
+
type: "divider";
|
|
25
|
+
};
|
|
26
|
+
export declare const shellNavigation: {
|
|
27
|
+
item<TPermission extends ShellPermission = string, TContext = unknown>(page: ShellPage<string, string, TPermission, TContext>, overrides?: Omit<ShellNavigationEntry<TPermission, TContext> & {
|
|
28
|
+
type: "item";
|
|
29
|
+
}, "page" | "type">): ShellNavigationEntry<TPermission, TContext>;
|
|
30
|
+
heading<TContext = unknown>(id: string, label: ShellLabel<TContext>): ShellNavigationEntry<ShellPermission, TContext>;
|
|
31
|
+
action<TPermission extends ShellPermission = string, TContext = unknown>(id: string, label: ShellLabel<TContext>, options?: {
|
|
32
|
+
icon?: string;
|
|
33
|
+
permission?: TPermission;
|
|
34
|
+
}): ShellNavigationEntry<TPermission, TContext>;
|
|
35
|
+
divider(): ShellNavigationEntry;
|
|
36
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export declare const OVERLAY_HISTORY_STATE_KEY = "__starterShellOverlayStack";
|
|
2
|
+
export type OverlayHistoryEntryId = string;
|
|
3
|
+
export type OverlayHistoryEntry = {
|
|
4
|
+
id: OverlayHistoryEntryId;
|
|
5
|
+
requestClose: () => void;
|
|
6
|
+
};
|
|
7
|
+
export type OverlayHistoryAction = {
|
|
8
|
+
type: "idle";
|
|
9
|
+
} | {
|
|
10
|
+
type: "push";
|
|
11
|
+
stack: readonly OverlayHistoryEntryId[];
|
|
12
|
+
} | {
|
|
13
|
+
type: "consume";
|
|
14
|
+
} | {
|
|
15
|
+
type: "requestClose";
|
|
16
|
+
id: OverlayHistoryEntryId;
|
|
17
|
+
};
|
|
18
|
+
export type OverlayHistoryResolveInput = {
|
|
19
|
+
desired: readonly OverlayHistoryEntryId[];
|
|
20
|
+
actual: readonly OverlayHistoryEntryId[];
|
|
21
|
+
previousActual: readonly OverlayHistoryEntryId[];
|
|
22
|
+
navigationType: "POP" | "PUSH" | "REPLACE";
|
|
23
|
+
locationChanged: boolean;
|
|
24
|
+
urlChanged: boolean;
|
|
25
|
+
};
|
|
26
|
+
export declare function readOverlayStack(state: unknown): OverlayHistoryEntryId[];
|
|
27
|
+
export declare function withOverlayStack(state: unknown, stack: readonly OverlayHistoryEntryId[]): Record<string, unknown>;
|
|
28
|
+
export declare function getCommonOverlayPrefixLength(left: readonly OverlayHistoryEntryId[], right: readonly OverlayHistoryEntryId[]): number;
|
|
29
|
+
export declare function resolveOverlayHistoryAction(input: OverlayHistoryResolveInput): OverlayHistoryAction;
|
|
30
|
+
export type OverlayHistoryRegistry = {
|
|
31
|
+
register(entry: OverlayHistoryEntry): () => void;
|
|
32
|
+
unregister(id: OverlayHistoryEntryId): void;
|
|
33
|
+
getSnapshot(): readonly OverlayHistoryEntry[];
|
|
34
|
+
subscribe(listener: () => void): () => void;
|
|
35
|
+
clear(): void;
|
|
36
|
+
};
|
|
37
|
+
export declare function createOverlayHistoryRegistry(): OverlayHistoryRegistry;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ShellRouteParams } from './shellSitemap.types';
|
|
2
|
+
export declare function normalizeRoutePath(path: string): string;
|
|
3
|
+
export declare function joinRoutePattern(parentPath: string, routePath: string): string;
|
|
4
|
+
export declare function getRequiredRouteParamNames(pattern: string): string[];
|
|
5
|
+
export declare function generateShellPath(pattern: string, params?: ShellRouteParams): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ShellPage, ShellPageDefinition, ShellPermission, ShellRouteTreeNode, ShellSection, ShellSectionDefinition, ShellSitemap } from './shellSitemap.types';
|
|
3
|
+
export declare const ShellRouteKeySchema: z.ZodString;
|
|
4
|
+
export declare const ShellRoutePathSchema: z.ZodString;
|
|
5
|
+
type StrictConfig<TConfig, TShape> = TConfig & Record<Exclude<keyof TConfig, keyof TShape>, never>;
|
|
6
|
+
type StrictConfigRecord<TRecord, TShape> = {
|
|
7
|
+
readonly [K in keyof TRecord]: StrictConfig<TRecord[K], TShape>;
|
|
8
|
+
};
|
|
9
|
+
type ShellPageRecord<TPages extends Record<string, ShellPageDefinition<TPermission, TContext>>, TPermission extends ShellPermission, TContext> = {
|
|
10
|
+
readonly [K in keyof TPages]: ShellPage<K & string, TPages[K]["routePath"], TPermission, TContext> & TPages[K];
|
|
11
|
+
};
|
|
12
|
+
type ShellSectionRecord<TSections extends Record<string, ShellSectionDefinition<TPermission, TContext>>, TPermission extends ShellPermission, TContext> = {
|
|
13
|
+
readonly [K in keyof TSections]: ShellSection<K & string, TSections[K]["routePath"], TPermission, TContext> & TSections[K];
|
|
14
|
+
};
|
|
15
|
+
export declare function defineShellPages<TPermission extends ShellPermission = ShellPermission, TContext = unknown, const TPages extends Record<string, ShellPageDefinition<TPermission, TContext>> = Record<string, ShellPageDefinition<TPermission, TContext>>>(pages: StrictConfigRecord<TPages, ShellPageDefinition<TPermission, TContext>>): ShellPageRecord<TPages, TPermission, TContext>;
|
|
16
|
+
export declare function defineShellSections<TPermission extends ShellPermission = ShellPermission, TContext = unknown, const TSections extends Record<string, ShellSectionDefinition<TPermission, TContext>> = Record<string, ShellSectionDefinition<TPermission, TContext>>>(sections: StrictConfigRecord<TSections, ShellSectionDefinition<TPermission, TContext>>): ShellSectionRecord<TSections, TPermission, TContext>;
|
|
17
|
+
export declare function createShellSitemap<const TRoutes extends readonly ShellRouteTreeNode[]>(tree: TRoutes): ShellSitemap<TRoutes>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export type ShellRouteParamValue = string | number;
|
|
2
|
+
export type ShellRouteParams = Readonly<Record<string, ShellRouteParamValue | undefined>>;
|
|
3
|
+
export type ShellPermission = string;
|
|
4
|
+
export type ShellPermissionScope = Readonly<Record<string, unknown>>;
|
|
5
|
+
export type ShellRouteMetadata = Readonly<Record<string, unknown>>;
|
|
6
|
+
export type ShellLabel<TContext = unknown> = string | ((context: TContext, params: ShellRouteParams) => string);
|
|
7
|
+
export type ShellPageDefinition<TPermission extends ShellPermission = ShellPermission, TContext = unknown, TRoutePath extends string = string> = {
|
|
8
|
+
routePath: TRoutePath;
|
|
9
|
+
label: ShellLabel<TContext>;
|
|
10
|
+
icon?: string;
|
|
11
|
+
permission?: TPermission;
|
|
12
|
+
permissionScope?: ShellPermissionScope;
|
|
13
|
+
metadata?: ShellRouteMetadata;
|
|
14
|
+
};
|
|
15
|
+
export type ShellSectionDefinition<TPermission extends ShellPermission = ShellPermission, TContext = unknown, TRoutePath extends string = string> = Omit<ShellPageDefinition<TPermission, TContext, TRoutePath>, "icon"> & {
|
|
16
|
+
icon?: string;
|
|
17
|
+
};
|
|
18
|
+
export type ShellPage<TKey extends string = string, TRoutePath extends string = string, TPermission extends ShellPermission = ShellPermission, TContext = unknown> = ShellPageDefinition<TPermission, TContext, TRoutePath> & {
|
|
19
|
+
readonly kind: "page";
|
|
20
|
+
readonly key: TKey;
|
|
21
|
+
};
|
|
22
|
+
export type ShellSection<TKey extends string = string, TRoutePath extends string = string, TPermission extends ShellPermission = ShellPermission, TContext = unknown> = ShellSectionDefinition<TPermission, TContext, TRoutePath> & {
|
|
23
|
+
readonly kind: "section";
|
|
24
|
+
readonly key: TKey;
|
|
25
|
+
};
|
|
26
|
+
export type ShellRouteNode<TPermission extends ShellPermission = ShellPermission, TContext = unknown> = ShellPage<string, string, TPermission, TContext> | ShellSection<string, string, TPermission, TContext>;
|
|
27
|
+
export type ShellRouteTreeNode<TPermission extends ShellPermission = ShellPermission, TContext = unknown> = ShellPage<string, string, TPermission, TContext> | {
|
|
28
|
+
node: ShellRouteNode<TPermission, TContext>;
|
|
29
|
+
redirectTo?: ShellPage<string, string, TPermission, TContext>;
|
|
30
|
+
children?: readonly ShellRouteTreeNode<TPermission, TContext>[];
|
|
31
|
+
};
|
|
32
|
+
export type ResolvedShellRoute<TPermission extends ShellPermission = ShellPermission, TContext = unknown> = {
|
|
33
|
+
key: string;
|
|
34
|
+
kind: "page" | "section";
|
|
35
|
+
pathPattern: string;
|
|
36
|
+
parentKey?: string;
|
|
37
|
+
redirectToPattern?: string;
|
|
38
|
+
node: ShellRouteNode<TPermission, TContext>;
|
|
39
|
+
};
|
|
40
|
+
type NormalizePath<TPath extends string> = TPath extends `/${infer Rest}` ? NormalizePath<Rest> : TPath extends `${infer Rest}/` ? NormalizePath<Rest> : TPath;
|
|
41
|
+
type JoinRoutePath<TParent extends string, TChild extends string> = NormalizePath<TParent> extends "" ? NormalizePath<TChild> extends "" ? "/" : `/${NormalizePath<TChild>}` : NormalizePath<TChild> extends "" ? `/${NormalizePath<TParent>}` : `/${NormalizePath<TParent>}/${NormalizePath<TChild>}`;
|
|
42
|
+
type RouteNodePath<TNode> = TNode extends {
|
|
43
|
+
routePath: infer TRoutePath extends string;
|
|
44
|
+
} ? TRoutePath : never;
|
|
45
|
+
type PreviousDepth = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
|
46
|
+
type RouteNodePageEntry<TNode, TParentPath extends string, TDepth extends number> = [TDepth] extends [never] ? never : TNode extends ShellPage ? {
|
|
47
|
+
page: TNode;
|
|
48
|
+
path: JoinRoutePath<TParentPath, RouteNodePath<TNode>>;
|
|
49
|
+
} : TNode extends {
|
|
50
|
+
node: infer TConfig;
|
|
51
|
+
children?: infer TChildren;
|
|
52
|
+
} ? TConfig extends ShellRouteNode ? TConfig extends ShellPage ? {
|
|
53
|
+
page: TConfig;
|
|
54
|
+
path: JoinRoutePath<TParentPath, RouteNodePath<TConfig>>;
|
|
55
|
+
} | ShellRouteTreePageEntries<Extract<TChildren, readonly ShellRouteTreeNode[]>, JoinRoutePath<TParentPath, RouteNodePath<TConfig>>, PreviousDepth[TDepth]> : ShellRouteTreePageEntries<Extract<TChildren, readonly ShellRouteTreeNode[]>, JoinRoutePath<TParentPath, RouteNodePath<TConfig>>, PreviousDepth[TDepth]> : never : never;
|
|
56
|
+
export type ShellRouteTreePageEntries<TNodes, TParentPath extends string = "", TDepth extends number = 8> = [
|
|
57
|
+
TDepth
|
|
58
|
+
] extends [never] ? never : TNodes extends readonly unknown[] ? RouteNodePageEntry<TNodes[number], TParentPath, TDepth> : never;
|
|
59
|
+
export type ShellPathForPage<TNodes extends readonly ShellRouteTreeNode[], TPage extends ShellPage> = Extract<ShellRouteTreePageEntries<TNodes>, {
|
|
60
|
+
page: TPage;
|
|
61
|
+
}> extends {
|
|
62
|
+
path: infer TPath extends string;
|
|
63
|
+
} ? TPath : never;
|
|
64
|
+
type StripParamSuffix<TParam extends string> = TParam extends `${infer TName}?` ? TName : TParam extends `${infer TName}*` ? TName : TParam;
|
|
65
|
+
type ExtractRouteTokens<TPath extends string> = TPath extends `${string}:${infer TParam}/${infer TRest}` ? TParam | ExtractRouteTokens<TRest> : TPath extends `${string}:${infer TParam}` ? TParam : never;
|
|
66
|
+
type OptionalRouteParamNames<TPath extends string> = ExtractRouteTokens<TPath> extends infer TToken extends string ? TToken extends `${string}?` | `${string}*` ? StripParamSuffix<TToken> : never : never;
|
|
67
|
+
type RequiredRouteParamNames<TPath extends string> = Exclude<StripParamSuffix<ExtractRouteTokens<TPath>>, OptionalRouteParamNames<TPath>>;
|
|
68
|
+
type RoutePathParams<TPath extends string> = {
|
|
69
|
+
[K in RequiredRouteParamNames<TPath>]: ShellRouteParamValue;
|
|
70
|
+
} & {
|
|
71
|
+
[K in OptionalRouteParamNames<TPath>]?: ShellRouteParamValue;
|
|
72
|
+
};
|
|
73
|
+
type RoutePathParamArgs<TPath extends string> = [ExtractRouteTokens<TPath>] extends [never] ? [] : [params: RoutePathParams<TPath>];
|
|
74
|
+
export type ShellSitemap<TRoutes extends readonly ShellRouteTreeNode[] = readonly ShellRouteTreeNode[]> = {
|
|
75
|
+
readonly routes: readonly ResolvedShellRoute[];
|
|
76
|
+
getPath<TPage extends ShellRouteTreePageEntries<TRoutes>["page"]>(page: TPage, ...args: RoutePathParamArgs<ShellPathForPage<TRoutes, TPage>>): string;
|
|
77
|
+
getPathPattern<TPage extends ShellRouteTreePageEntries<TRoutes>["page"]>(page: TPage): ShellPathForPage<TRoutes, TPage>;
|
|
78
|
+
getRoute(key: string): ResolvedShellRoute | undefined;
|
|
79
|
+
};
|
|
80
|
+
export {};
|
package/package.json
CHANGED