@reckona/mreact-router 0.0.65 → 0.0.67
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/package.json +13 -12
- package/src/actions.ts +1130 -0
- package/src/adapters/aws-lambda.ts +993 -0
- package/src/adapters/cloudflare.ts +1286 -0
- package/src/adapters/devtools.ts +5 -0
- package/src/adapters/edge.ts +70 -0
- package/src/adapters/node.ts +126 -0
- package/src/adapters/static.ts +61 -0
- package/src/app-router-globals.ts +19 -0
- package/src/assets.ts +113 -0
- package/src/build.ts +2948 -0
- package/src/bundle-pipeline.ts +496 -0
- package/src/cache-config.ts +35 -0
- package/src/cache-stats.ts +54 -0
- package/src/cache.ts +418 -0
- package/src/cli-options.ts +296 -0
- package/src/cli.ts +94 -0
- package/src/client.ts +3398 -0
- package/src/config.ts +146 -0
- package/src/cookies.ts +113 -0
- package/src/csp.ts +103 -0
- package/src/csrf.ts +132 -0
- package/src/deferred.ts +52 -0
- package/src/dev-server.ts +262 -0
- package/src/file-conventions.ts +88 -0
- package/src/http.ts +128 -0
- package/src/i18n.ts +98 -0
- package/src/import-policy.ts +261 -0
- package/src/index.ts +221 -0
- package/src/link.ts +47 -0
- package/src/logger.ts +149 -0
- package/src/module-runner.ts +554 -0
- package/src/multipart.ts +577 -0
- package/src/native-escape.ts +53 -0
- package/src/native-route-matcher.ts +173 -0
- package/src/navigation-state.ts +98 -0
- package/src/navigation.ts +215 -0
- package/src/prerender-store.ts +233 -0
- package/src/render.ts +5187 -0
- package/src/route-path.ts +5 -0
- package/src/route-shells.ts +47 -0
- package/src/route-source.ts +224 -0
- package/src/route-styles.ts +91 -0
- package/src/routes.ts +362 -0
- package/src/runtime-cache.ts +8 -0
- package/src/runtime-state.ts +37 -0
- package/src/security-headers.ts +134 -0
- package/src/serve.ts +1265 -0
- package/src/session.ts +238 -0
- package/src/source-jsx.ts +30 -0
- package/src/source-modules.ts +69 -0
- package/src/stream-list.ts +45 -0
- package/src/trace.ts +114 -0
- package/src/types.ts +155 -0
- package/src/upgrade.ts +8 -0
- package/src/vite-config.ts +63 -0
- package/src/vite-plugin-cache-key.ts +53 -0
- package/src/vite.ts +690 -0
- package/src/workspace-packages.ts +67 -0
package/src/session.ts
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { deleteCookie, parseCookieHeader, setCookie } from "./cookies.js";
|
|
2
|
+
|
|
3
|
+
export interface SessionRecord<TData = unknown> {
|
|
4
|
+
createdAt: number;
|
|
5
|
+
data: TData;
|
|
6
|
+
expiresAt: number;
|
|
7
|
+
id: string;
|
|
8
|
+
rotatedAt?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SessionStore<TData = unknown> {
|
|
12
|
+
delete(id: string): void | Promise<void>;
|
|
13
|
+
get(id: string): SessionRecord<TData> | undefined | Promise<SessionRecord<TData> | undefined>;
|
|
14
|
+
set(record: SessionRecord<TData>): void | Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface MemorySessionStoreOptions {
|
|
18
|
+
maxEntries?: number;
|
|
19
|
+
sweepIntervalMs?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SessionCookieOptions {
|
|
23
|
+
cookieName?: string;
|
|
24
|
+
maxAgeSeconds?: number;
|
|
25
|
+
path?: string;
|
|
26
|
+
sameSite?: "Strict" | "Lax" | "None";
|
|
27
|
+
secure?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const DEFAULT_MAX_AGE_SECONDS = 7 * 24 * 60 * 60;
|
|
31
|
+
const SESSION_COOKIE_DEV = "mreact.session";
|
|
32
|
+
const SESSION_COOKIE_PROD = "__Host-mreact.session";
|
|
33
|
+
|
|
34
|
+
function isProduction(): boolean {
|
|
35
|
+
return process.env.NODE_ENV === "production";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function defaultCookieName(): string {
|
|
39
|
+
return isProduction() ? SESSION_COOKIE_PROD : SESSION_COOKIE_DEV;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function sessionCookieOptions(options: SessionCookieOptions = {}) {
|
|
43
|
+
const production = isProduction();
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
httpOnly: true,
|
|
47
|
+
maxAge: options.maxAgeSeconds ?? DEFAULT_MAX_AGE_SECONDS,
|
|
48
|
+
path: production ? "/" : options.path ?? "/",
|
|
49
|
+
sameSite: options.sameSite ?? "Lax",
|
|
50
|
+
secure: options.secure ?? production,
|
|
51
|
+
} as const;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function readSessionId(
|
|
55
|
+
request: Request,
|
|
56
|
+
options: SessionCookieOptions = {},
|
|
57
|
+
): string | undefined {
|
|
58
|
+
const values = parseCookieHeader(request.headers.get("cookie"));
|
|
59
|
+
return values.get(options.cookieName ?? defaultCookieName());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function createSessionId(): string {
|
|
63
|
+
const bytes = new Uint8Array(32);
|
|
64
|
+
|
|
65
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
66
|
+
|
|
67
|
+
let binary = "";
|
|
68
|
+
for (const byte of bytes) {
|
|
69
|
+
binary += String.fromCharCode(byte);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function createMemorySessionStore<TData>(
|
|
76
|
+
options: MemorySessionStoreOptions = {},
|
|
77
|
+
): SessionStore<TData> {
|
|
78
|
+
const maxEntries = positiveIntegerOrDefault(options.maxEntries, 100_000);
|
|
79
|
+
const sweepIntervalMs = nonNegativeIntegerOrDefault(options.sweepIntervalMs, 60_000);
|
|
80
|
+
const records = new Map<string, SessionRecord<TData>>();
|
|
81
|
+
let nextSweepAt = 0;
|
|
82
|
+
|
|
83
|
+
function sweepExpired(now: number): void {
|
|
84
|
+
for (const [id, value] of records) {
|
|
85
|
+
if (value.expiresAt <= now) {
|
|
86
|
+
records.delete(id);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
nextSweepAt = now + sweepIntervalMs;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function maybeSweepExpired(now: number): void {
|
|
94
|
+
if (sweepIntervalMs === 0 || now >= nextSweepAt) {
|
|
95
|
+
sweepExpired(now);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function evictOldestEntries(): void {
|
|
100
|
+
while (records.size > maxEntries) {
|
|
101
|
+
const oldestId = records.keys().next().value;
|
|
102
|
+
|
|
103
|
+
if (oldestId === undefined) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
records.delete(oldestId);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
delete(id) {
|
|
113
|
+
records.delete(id);
|
|
114
|
+
},
|
|
115
|
+
get(id) {
|
|
116
|
+
const now = Date.now();
|
|
117
|
+
maybeSweepExpired(now);
|
|
118
|
+
const record = records.get(id);
|
|
119
|
+
|
|
120
|
+
if (record !== undefined && record.expiresAt <= now) {
|
|
121
|
+
records.delete(id);
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (record !== undefined) {
|
|
126
|
+
records.delete(id);
|
|
127
|
+
records.set(id, record);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return record;
|
|
131
|
+
},
|
|
132
|
+
set(record) {
|
|
133
|
+
const now = Date.now();
|
|
134
|
+
maybeSweepExpired(now);
|
|
135
|
+
|
|
136
|
+
records.delete(record.id);
|
|
137
|
+
records.set(record.id, record);
|
|
138
|
+
|
|
139
|
+
if (records.size > maxEntries) {
|
|
140
|
+
sweepExpired(now);
|
|
141
|
+
evictOldestEntries();
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function positiveIntegerOrDefault(value: number | undefined, fallback: number): number {
|
|
148
|
+
return value === undefined || !Number.isInteger(value) || value < 1 ? fallback : value;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function nonNegativeIntegerOrDefault(value: number | undefined, fallback: number): number {
|
|
152
|
+
return value === undefined || !Number.isInteger(value) || value < 0 ? fallback : value;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function getSession<TData>(
|
|
156
|
+
request: Request,
|
|
157
|
+
store: SessionStore<TData>,
|
|
158
|
+
options: SessionCookieOptions = {},
|
|
159
|
+
): Promise<SessionRecord<TData> | undefined> {
|
|
160
|
+
const id = readSessionId(request, options);
|
|
161
|
+
|
|
162
|
+
if (id === undefined || id.length === 0) {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const record = await store.get(id);
|
|
167
|
+
|
|
168
|
+
if (record === undefined) {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (record.expiresAt <= Date.now()) {
|
|
173
|
+
await store.delete(id);
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return record;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function createSession<TData>(
|
|
181
|
+
response: Response,
|
|
182
|
+
store: SessionStore<TData>,
|
|
183
|
+
data: TData,
|
|
184
|
+
options: SessionCookieOptions = {},
|
|
185
|
+
): Promise<SessionRecord<TData>> {
|
|
186
|
+
const now = Date.now();
|
|
187
|
+
const maxAgeSeconds = options.maxAgeSeconds ?? DEFAULT_MAX_AGE_SECONDS;
|
|
188
|
+
const record: SessionRecord<TData> = {
|
|
189
|
+
createdAt: now,
|
|
190
|
+
data,
|
|
191
|
+
expiresAt: now + maxAgeSeconds * 1000,
|
|
192
|
+
id: createSessionId(),
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
await store.set(record);
|
|
196
|
+
setCookie(response, options.cookieName ?? defaultCookieName(), record.id, {
|
|
197
|
+
...sessionCookieOptions({ ...options, maxAgeSeconds }),
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
return record;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export async function destroySession<TData>(
|
|
204
|
+
request: Request,
|
|
205
|
+
response: Response,
|
|
206
|
+
store: SessionStore<TData>,
|
|
207
|
+
options: SessionCookieOptions = {},
|
|
208
|
+
): Promise<void> {
|
|
209
|
+
const id = readSessionId(request, options);
|
|
210
|
+
|
|
211
|
+
if (id !== undefined) {
|
|
212
|
+
await store.delete(id);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
deleteCookie(response, options.cookieName ?? defaultCookieName(), {
|
|
216
|
+
path: isProduction() ? "/" : options.path ?? "/",
|
|
217
|
+
sameSite: options.sameSite ?? "Lax",
|
|
218
|
+
secure: options.secure ?? isProduction(),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export async function rotateSession<TData>(
|
|
223
|
+
request: Request,
|
|
224
|
+
response: Response,
|
|
225
|
+
store: SessionStore<TData>,
|
|
226
|
+
options: SessionCookieOptions = {},
|
|
227
|
+
): Promise<SessionRecord<TData> | undefined> {
|
|
228
|
+
const current = await getSession(request, store, options);
|
|
229
|
+
|
|
230
|
+
if (current === undefined) {
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
await store.delete(current.id);
|
|
235
|
+
const next = await createSession(response, store, current.data, options);
|
|
236
|
+
|
|
237
|
+
return { ...next, rotatedAt: Date.now() };
|
|
238
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function hasJsxSyntax(node: unknown): boolean {
|
|
2
|
+
if (Array.isArray(node)) {
|
|
3
|
+
return node.some(hasJsxSyntax);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
if (node === null || typeof node !== "object") {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const object = node as Record<string, unknown>;
|
|
11
|
+
if (
|
|
12
|
+
object.type === "JSXElement" ||
|
|
13
|
+
object.type === "JSXFragment" ||
|
|
14
|
+
object.type === "JSXExpressionContainer"
|
|
15
|
+
) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
for (const [key, value] of Object.entries(object)) {
|
|
20
|
+
if (key === "type" || key === "start" || key === "end" || key === "loc") {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (hasJsxSyntax(value)) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { extname, join } from "node:path";
|
|
2
|
+
|
|
3
|
+
export function sourceModuleCandidates(base: string): string[] {
|
|
4
|
+
if (hasSourceModuleExtension(base)) {
|
|
5
|
+
return [base, ...typescriptSourceModuleCandidates(base)];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (/\.(?:client|compat)$/.test(base)) {
|
|
9
|
+
return [
|
|
10
|
+
`${base}.ts`,
|
|
11
|
+
`${base}.tsx`,
|
|
12
|
+
`${base}.js`,
|
|
13
|
+
`${base}.jsx`,
|
|
14
|
+
`${base}.mjs`,
|
|
15
|
+
`${base}.mts`,
|
|
16
|
+
`${base}.cjs`,
|
|
17
|
+
`${base}.cts`,
|
|
18
|
+
];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (extname(base) !== "") {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return [
|
|
26
|
+
`${base}.ts`,
|
|
27
|
+
`${base}.tsx`,
|
|
28
|
+
`${base}.mreact.tsx`,
|
|
29
|
+
`${base}.js`,
|
|
30
|
+
`${base}.jsx`,
|
|
31
|
+
`${base}.mjs`,
|
|
32
|
+
`${base}.mts`,
|
|
33
|
+
`${base}.cjs`,
|
|
34
|
+
`${base}.cts`,
|
|
35
|
+
join(base, "index.ts"),
|
|
36
|
+
join(base, "index.tsx"),
|
|
37
|
+
join(base, "index.mreact.tsx"),
|
|
38
|
+
join(base, "index.js"),
|
|
39
|
+
join(base, "index.jsx"),
|
|
40
|
+
join(base, "index.mjs"),
|
|
41
|
+
join(base, "index.mts"),
|
|
42
|
+
join(base, "index.cjs"),
|
|
43
|
+
join(base, "index.cts"),
|
|
44
|
+
];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function hasSourceModuleExtension(path: string): boolean {
|
|
48
|
+
return /\.(?:mreact\.tsx|tsx?|jsx?|mjs|mts|cjs|cts)$/.test(path);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function typescriptSourceModuleCandidates(path: string): string[] {
|
|
52
|
+
if (path.endsWith(".js")) {
|
|
53
|
+
return [`${path.slice(0, -3)}.ts`, `${path.slice(0, -3)}.tsx`];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (path.endsWith(".jsx")) {
|
|
57
|
+
return [`${path.slice(0, -4)}.tsx`];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (path.endsWith(".mjs")) {
|
|
61
|
+
return [`${path.slice(0, -4)}.mts`];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (path.endsWith(".cjs")) {
|
|
65
|
+
return [`${path.slice(0, -4)}.cts`];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface StreamListBatch<TItem> {
|
|
2
|
+
index: number;
|
|
3
|
+
items: TItem[];
|
|
4
|
+
size: number;
|
|
5
|
+
start: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface StreamListPendingBatch<TItem> {
|
|
9
|
+
index: number;
|
|
10
|
+
size: number;
|
|
11
|
+
start: number;
|
|
12
|
+
value: Promise<StreamListBatch<TItem>>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface StreamListOptions<TInput, TItem> {
|
|
16
|
+
batchSize: number;
|
|
17
|
+
loadBatch: (
|
|
18
|
+
items: readonly TInput[],
|
|
19
|
+
batch: { index: number; size: number; start: number },
|
|
20
|
+
) => PromiseLike<readonly TItem[]> | readonly TItem[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function streamList<TInput, TItem = TInput>(
|
|
24
|
+
items: readonly TInput[],
|
|
25
|
+
options: StreamListOptions<TInput, TItem>,
|
|
26
|
+
): Array<StreamListPendingBatch<TItem>> {
|
|
27
|
+
const requestedBatchSize = Math.floor(options.batchSize);
|
|
28
|
+
const batchSize = Number.isFinite(requestedBatchSize) ? Math.max(1, requestedBatchSize) : 1;
|
|
29
|
+
const batches: Array<StreamListPendingBatch<TItem>> = [];
|
|
30
|
+
|
|
31
|
+
for (let start = 0; start < items.length; start += batchSize) {
|
|
32
|
+
const batchItems = items.slice(start, start + batchSize);
|
|
33
|
+
const index = batches.length;
|
|
34
|
+
const meta = { index, size: batchItems.length, start };
|
|
35
|
+
batches.push({
|
|
36
|
+
...meta,
|
|
37
|
+
value: Promise.resolve(options.loadBatch(batchItems, meta)).then((loaded) => ({
|
|
38
|
+
...meta,
|
|
39
|
+
items: [...loaded],
|
|
40
|
+
})),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return batches;
|
|
45
|
+
}
|
package/src/trace.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export interface RouterTraceContext {
|
|
2
|
+
parentSpanId: string;
|
|
3
|
+
sampled: boolean;
|
|
4
|
+
traceId: string;
|
|
5
|
+
traceparent: string;
|
|
6
|
+
tracestate?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface RouterRequestInstrumentationEvent {
|
|
10
|
+
method: string;
|
|
11
|
+
path: string;
|
|
12
|
+
request: Request;
|
|
13
|
+
trace?: RouterTraceContext;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface RouterRequestEndInstrumentationEvent
|
|
17
|
+
extends RouterRequestInstrumentationEvent {
|
|
18
|
+
status: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface RouterRouteInstrumentationEvent
|
|
22
|
+
extends RouterRequestInstrumentationEvent {
|
|
23
|
+
routeId: string;
|
|
24
|
+
routePath: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface RouterRouteEndInstrumentationEvent
|
|
28
|
+
extends RouterRouteInstrumentationEvent {
|
|
29
|
+
error?: unknown;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface RouterMiddlewareInstrumentationEvent
|
|
33
|
+
extends RouterRequestInstrumentationEvent {
|
|
34
|
+
name: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface RouterMiddlewareEndInstrumentationEvent
|
|
38
|
+
extends RouterMiddlewareInstrumentationEvent {
|
|
39
|
+
error?: unknown;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface RouterInstrumentation {
|
|
43
|
+
onLoaderEnd?: (event: RouterRouteEndInstrumentationEvent) => void | Promise<void>;
|
|
44
|
+
onLoaderStart?: (event: RouterRouteInstrumentationEvent) => void | Promise<void>;
|
|
45
|
+
onMiddlewareEnd?: (event: RouterMiddlewareEndInstrumentationEvent) => void | Promise<void>;
|
|
46
|
+
onMiddlewareStart?: (event: RouterMiddlewareInstrumentationEvent) => void | Promise<void>;
|
|
47
|
+
onRequestEnd?: (event: RouterRequestEndInstrumentationEvent) => void | Promise<void>;
|
|
48
|
+
onRequestStart?: (event: RouterRequestInstrumentationEvent) => void | Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function parseTraceContext(
|
|
52
|
+
traceparent: string | null | undefined,
|
|
53
|
+
tracestate: string | null | undefined,
|
|
54
|
+
): RouterTraceContext | undefined {
|
|
55
|
+
if (traceparent === null || traceparent === undefined) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const match = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/i.exec(
|
|
60
|
+
traceparent,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (match === null) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const traceId = match[2]?.toLowerCase() ?? "";
|
|
68
|
+
const parentSpanId = match[3]?.toLowerCase() ?? "";
|
|
69
|
+
const flags = Number.parseInt(match[4] ?? "00", 16);
|
|
70
|
+
|
|
71
|
+
if (/^0+$/.test(traceId) || /^0+$/.test(parentSpanId)) {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
parentSpanId,
|
|
77
|
+
sampled: (flags & 1) === 1,
|
|
78
|
+
traceId,
|
|
79
|
+
traceparent: traceparent.toLowerCase(),
|
|
80
|
+
...(tracestate === null || tracestate === undefined ? {} : { tracestate }),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function traceContextFromRequest(request: Request): RouterTraceContext | undefined {
|
|
85
|
+
return parseTraceContext(request.headers.get("traceparent"), request.headers.get("tracestate"));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function invokeRouterInstrumentation<Event>(
|
|
89
|
+
callback: ((event: Event) => void | Promise<void>) | undefined,
|
|
90
|
+
event: Event,
|
|
91
|
+
): void {
|
|
92
|
+
if (callback === undefined) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const result = callback(event);
|
|
98
|
+
|
|
99
|
+
if (isPromiseLike(result)) {
|
|
100
|
+
result.catch(() => {});
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
// Instrumentation is best-effort and must never affect request handling.
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function isPromiseLike(value: unknown): value is Promise<void> {
|
|
108
|
+
return (
|
|
109
|
+
typeof value === "object" &&
|
|
110
|
+
value !== null &&
|
|
111
|
+
"catch" in value &&
|
|
112
|
+
typeof value.catch === "function"
|
|
113
|
+
);
|
|
114
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { ReactCompatNode } from "@reckona/mreact-compat";
|
|
2
|
+
import type { QueryClient } from "@reckona/mreact-query";
|
|
3
|
+
|
|
4
|
+
export type InferLoaderData<TLoader extends (...args: never[]) => unknown> = Awaited<
|
|
5
|
+
ReturnType<TLoader>
|
|
6
|
+
>;
|
|
7
|
+
|
|
8
|
+
export type RouteParams = Record<string, readonly string[] | string>;
|
|
9
|
+
|
|
10
|
+
export interface LoaderContext<TParams extends RouteParams = RouteParams> {
|
|
11
|
+
params: TParams;
|
|
12
|
+
queryClient: QueryClient;
|
|
13
|
+
request: Request;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface GenerateMetadataContext<
|
|
17
|
+
TData = unknown,
|
|
18
|
+
TParams extends RouteParams = RouteParams,
|
|
19
|
+
> {
|
|
20
|
+
data: TData;
|
|
21
|
+
params: TParams;
|
|
22
|
+
request: Request;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface RouteHandlerContext<TParams extends RouteParams = RouteParams> {
|
|
26
|
+
params: TParams;
|
|
27
|
+
request: Request;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface PageProps<TData = unknown, TParams extends RouteParams = RouteParams> {
|
|
31
|
+
data: TData;
|
|
32
|
+
params: TParams;
|
|
33
|
+
request: Request;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface LayoutProps<TParams extends RouteParams = RouteParams> {
|
|
37
|
+
children: ReactCompatNode;
|
|
38
|
+
params: TParams;
|
|
39
|
+
request: Request;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type MReactNode = ReactCompatNode;
|
|
43
|
+
|
|
44
|
+
export type MetadataScalar = boolean | number | string;
|
|
45
|
+
|
|
46
|
+
export interface MetadataImage {
|
|
47
|
+
alt?: MetadataScalar;
|
|
48
|
+
height?: MetadataScalar;
|
|
49
|
+
type?: MetadataScalar;
|
|
50
|
+
url: MetadataScalar;
|
|
51
|
+
width?: MetadataScalar;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface RouteMetadata {
|
|
55
|
+
alternates?: {
|
|
56
|
+
canonical?: MetadataScalar;
|
|
57
|
+
};
|
|
58
|
+
csp?: {
|
|
59
|
+
disable?: boolean;
|
|
60
|
+
directives?: Record<string, readonly string[] | string>;
|
|
61
|
+
nonce?: string;
|
|
62
|
+
remove?: readonly string[];
|
|
63
|
+
replace?: Record<string, readonly string[] | string>;
|
|
64
|
+
};
|
|
65
|
+
description?: MetadataScalar;
|
|
66
|
+
head?: readonly RouteHeadDescriptor[];
|
|
67
|
+
icons?: {
|
|
68
|
+
apple?: MetadataScalar;
|
|
69
|
+
icon?: MetadataScalar;
|
|
70
|
+
};
|
|
71
|
+
openGraph?: {
|
|
72
|
+
description?: MetadataScalar;
|
|
73
|
+
image?: MetadataImage | MetadataScalar;
|
|
74
|
+
images?: readonly (MetadataImage | MetadataScalar)[];
|
|
75
|
+
title?: MetadataScalar;
|
|
76
|
+
};
|
|
77
|
+
lang?: MetadataScalar;
|
|
78
|
+
robots?:
|
|
79
|
+
| string
|
|
80
|
+
| {
|
|
81
|
+
follow?: boolean;
|
|
82
|
+
index?: boolean;
|
|
83
|
+
};
|
|
84
|
+
security?: RouteSecurityHeaders;
|
|
85
|
+
themeColor?: MetadataScalar | MetadataThemeColor;
|
|
86
|
+
title?: MetadataScalar;
|
|
87
|
+
viewport?: MetadataScalar | MetadataViewport;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export type MetadataViewport = Record<string, MetadataScalar | null | undefined>;
|
|
91
|
+
|
|
92
|
+
export interface MetadataThemeColor {
|
|
93
|
+
color?: MetadataScalar;
|
|
94
|
+
media?: MetadataScalar;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface RouteHeadDescriptor {
|
|
98
|
+
attrs?: Record<string, boolean | number | string | undefined>;
|
|
99
|
+
content?: string;
|
|
100
|
+
nonce?: boolean | string;
|
|
101
|
+
tag: "base" | "link" | "meta" | "script" | "style";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface RouteSecurityHeaders {
|
|
105
|
+
contentTypeOptions?: "nosniff" | null | undefined;
|
|
106
|
+
frameOptions?: "DENY" | "SAMEORIGIN" | null | undefined;
|
|
107
|
+
hsts?: RouteStrictTransportSecurity | false | null | undefined;
|
|
108
|
+
permissionsPolicy?: Record<string, readonly string[] | null | undefined> | null | undefined;
|
|
109
|
+
referrerPolicy?: string | null | undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface RouteStrictTransportSecurity {
|
|
113
|
+
includeSubDomains?: boolean | undefined;
|
|
114
|
+
maxAge: number;
|
|
115
|
+
preload?: boolean | undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface RobotsContext {
|
|
119
|
+
baseUrl: string;
|
|
120
|
+
host: string;
|
|
121
|
+
request: Request;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface RobotsManifest {
|
|
125
|
+
host?: string | undefined;
|
|
126
|
+
rules?: RobotsRule | readonly RobotsRule[] | undefined;
|
|
127
|
+
sitemap?: string | readonly string[] | undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface RobotsRule {
|
|
131
|
+
allow?: string | readonly string[] | undefined;
|
|
132
|
+
disallow?: string | readonly string[] | undefined;
|
|
133
|
+
userAgent: string | readonly string[];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface SitemapContext {
|
|
137
|
+
baseUrl: string;
|
|
138
|
+
host: string;
|
|
139
|
+
request: Request;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface SitemapEntry {
|
|
143
|
+
changeFrequency?: string | undefined;
|
|
144
|
+
lastModified?: Date | string | number | undefined;
|
|
145
|
+
priority?: number | undefined;
|
|
146
|
+
url: string;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface ManifestContext {
|
|
150
|
+
baseUrl: string;
|
|
151
|
+
host: string;
|
|
152
|
+
request: Request;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export type ManifestDescriptor = Record<string, unknown>;
|