@octanejs/remix-router 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -6
- package/package.json +12 -7
- package/src/index.ts +16 -14
- package/src/lib/Await.tsrx +1 -1
- package/src/lib/DefaultErrorComponent.tsrx +3 -3
- package/src/lib/RenderErrorBoundary.tsrx +1 -1
- package/src/lib/actions.ts +1 -1
- package/src/lib/components/MemoryRouter.tsrx +1 -1
- package/src/lib/components/Navigate.ts +1 -1
- package/src/lib/components/Router.tsrx +1 -1
- package/src/lib/components/RouterProvider.tsrx +1 -1
- package/src/lib/components/Routes.tsrx +1 -1
- package/src/lib/components/routes-collector.ts +1 -8
- package/src/lib/components/utils.ts +4 -65
- package/src/lib/components/with-props.ts +1 -1
- package/src/lib/context.ts +2 -4
- package/src/lib/dom/Form.tsrx +1 -1
- package/src/lib/dom/Link.tsrx +1 -1
- package/src/lib/dom/NavLink.tsrx +1 -1
- package/src/lib/dom/dom.ts +1 -1
- package/src/lib/dom/hooks.ts +1 -1
- package/src/lib/dom/lib.ts +8 -7
- package/src/lib/dom/routers.tsrx +1 -1
- package/src/lib/dom/server.tsrx +6 -28
- package/src/lib/errors.ts +1 -1
- package/src/lib/hooks.ts +6 -16
- package/src/lib/href.ts +9 -4
- package/src/lib/router/history.ts +2 -2
- package/src/lib/router/instrumentation.ts +391 -187
- package/src/lib/router/links.ts +1 -1
- package/src/lib/router/router.ts +128 -78
- package/src/lib/router/server-runtime-types.ts +8 -11
- package/src/lib/router/url.ts +1 -1
- package/src/lib/router/utils.ts +107 -31
- package/src/lib/server-runtime/cookies.ts +7 -7
- package/src/lib/server-runtime/crypto.ts +2 -2
- package/src/lib/server-runtime/mode.ts +1 -1
- package/src/lib/server-runtime/sessions/cookieStorage.ts +1 -1
- package/src/lib/server-runtime/sessions/memoryStorage.ts +1 -1
- package/src/lib/server-runtime/sessions.ts +8 -5
- package/src/lib/server-runtime/warnings.ts +1 -1
- package/src/lib/types/future.ts +1 -7
- package/src/lib/types/params.ts +1 -1
- package/src/lib/types/register.ts +1 -1
- package/src/lib/types/route-module.ts +1 -1
- package/src/lib/types/utils.ts +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// Vendored from react-router@
|
|
1
|
+
// Vendored from react-router@8.2.0 packages/react-router/lib/router/instrumentation.ts — unmodified except: type-only ../server-runtime/server import → local ./server-runtime-types stub.
|
|
2
2
|
// Re-vendor with `node scripts/vendor-remix-router.mjs`; never hand-edit.
|
|
3
|
-
import type {
|
|
4
|
-
import type { MiddlewareEnabled } from '../types/future';
|
|
3
|
+
import type { RequestHandler } from './server-runtime-types';
|
|
5
4
|
import { createPath, invariant } from './history';
|
|
6
5
|
import type { Router } from './router';
|
|
6
|
+
import { createContext, RouterContextProvider } from './utils';
|
|
7
7
|
import type {
|
|
8
8
|
ActionFunctionArgs,
|
|
9
9
|
DataRouteObject,
|
|
@@ -15,7 +15,6 @@ import type {
|
|
|
15
15
|
MaybePromise,
|
|
16
16
|
MiddlewareFunction,
|
|
17
17
|
RouterContext,
|
|
18
|
-
RouterContextProvider,
|
|
19
18
|
} from './utils';
|
|
20
19
|
|
|
21
20
|
// Public APIs
|
|
@@ -35,13 +34,45 @@ export type InstrumentRouterFunction = (router: InstrumentableRouter) => void;
|
|
|
35
34
|
|
|
36
35
|
export type InstrumentRouteFunction = (route: InstrumentableRoute) => void;
|
|
37
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Route metadata available after React Router has matched an instrumented
|
|
39
|
+
* request, navigation, or fetcher call.
|
|
40
|
+
*/
|
|
41
|
+
export type InstrumentationResultMeta = {
|
|
42
|
+
url: LoaderFunctionArgs['url'];
|
|
43
|
+
pattern: string;
|
|
44
|
+
params: LoaderFunctionArgs['params'];
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Result returned by route-level instrumented handler calls, such as
|
|
49
|
+
* instrumented loaders, actions, middleware, and lazy route functions.
|
|
50
|
+
*/
|
|
38
51
|
export type InstrumentationHandlerResult =
|
|
39
52
|
| { status: 'success'; error: undefined }
|
|
40
53
|
| { status: 'error'; error: Error };
|
|
41
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Result returned by client-side router instrumented navigation and fetcher
|
|
57
|
+
* calls.
|
|
58
|
+
*/
|
|
59
|
+
export type InstrumentationClientRouterResult = InstrumentationHandlerResult & {
|
|
60
|
+
meta: InstrumentationResultMeta | undefined;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Result returned by server request handler instrumentation.
|
|
65
|
+
*/
|
|
66
|
+
export type InstrumentationServerHandlerResult = InstrumentationHandlerResult & {
|
|
67
|
+
statusCode: number;
|
|
68
|
+
meta: InstrumentationResultMeta | undefined;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export type InstrumentationMetaReceiver = (meta: InstrumentationResultMeta | undefined) => void;
|
|
72
|
+
|
|
42
73
|
// Shared
|
|
43
|
-
type InstrumentFunction<T> = (
|
|
44
|
-
handler: () => Promise<
|
|
74
|
+
type InstrumentFunction<T, TInnerResult = InstrumentationHandlerResult> = (
|
|
75
|
+
handler: () => Promise<TInnerResult>,
|
|
45
76
|
info: T,
|
|
46
77
|
) => Promise<void>;
|
|
47
78
|
|
|
@@ -58,9 +89,7 @@ type ReadonlyRequest = {
|
|
|
58
89
|
headers: Pick<Headers, 'get'>;
|
|
59
90
|
};
|
|
60
91
|
|
|
61
|
-
type ReadonlyContext =
|
|
62
|
-
? Pick<RouterContextProvider, 'get'>
|
|
63
|
-
: Readonly<AppLoadContext>;
|
|
92
|
+
type ReadonlyContext = Pick<RouterContextProvider, 'get'>;
|
|
64
93
|
|
|
65
94
|
// Route Instrumentation
|
|
66
95
|
type InstrumentableRoute = {
|
|
@@ -82,12 +111,12 @@ type RouteInstrumentations = {
|
|
|
82
111
|
|
|
83
112
|
type RouteLazyInstrumentationInfo = undefined;
|
|
84
113
|
|
|
85
|
-
type RouteHandlerInstrumentationInfo = Readonly<
|
|
86
|
-
request
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
114
|
+
type RouteHandlerInstrumentationInfo = Readonly<
|
|
115
|
+
Omit<LoaderFunctionArgs, 'request' | 'context'> & {
|
|
116
|
+
request: ReadonlyRequest;
|
|
117
|
+
context: ReadonlyContext;
|
|
118
|
+
}
|
|
119
|
+
>;
|
|
91
120
|
|
|
92
121
|
// Router Instrumentation
|
|
93
122
|
type InstrumentableRouter = {
|
|
@@ -95,8 +124,11 @@ type InstrumentableRouter = {
|
|
|
95
124
|
};
|
|
96
125
|
|
|
97
126
|
type RouterInstrumentations = {
|
|
98
|
-
navigate?: InstrumentFunction<
|
|
99
|
-
|
|
127
|
+
navigate?: InstrumentFunction<
|
|
128
|
+
RouterNavigationInstrumentationInfo,
|
|
129
|
+
InstrumentationClientRouterResult
|
|
130
|
+
>;
|
|
131
|
+
fetch?: InstrumentFunction<RouterFetchInstrumentationInfo, InstrumentationClientRouterResult>;
|
|
100
132
|
};
|
|
101
133
|
|
|
102
134
|
type RouterNavigationInstrumentationInfo = Readonly<{
|
|
@@ -124,7 +156,10 @@ type InstrumentableRequestHandler = {
|
|
|
124
156
|
};
|
|
125
157
|
|
|
126
158
|
type RequestHandlerInstrumentations = {
|
|
127
|
-
request?: InstrumentFunction<
|
|
159
|
+
request?: InstrumentFunction<
|
|
160
|
+
RequestHandlerInstrumentationInfo,
|
|
161
|
+
InstrumentationServerHandlerResult
|
|
162
|
+
>;
|
|
128
163
|
};
|
|
129
164
|
|
|
130
165
|
type RequestHandlerInstrumentationInfo = Readonly<{
|
|
@@ -134,18 +169,31 @@ type RequestHandlerInstrumentationInfo = Readonly<{
|
|
|
134
169
|
|
|
135
170
|
const UninstrumentedSymbol = Symbol('Uninstrumented');
|
|
136
171
|
|
|
172
|
+
type InstrumentableFunction = (...args: never[]) => MaybePromise<unknown>;
|
|
173
|
+
type InstrumentedFunction<T extends InstrumentableFunction> = T & {
|
|
174
|
+
[UninstrumentedSymbol]?: T;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export const instrumentationResultMetaContext = createContext<InstrumentationResultMeta>();
|
|
178
|
+
|
|
179
|
+
// Client router instrumentations need route metadata captured inside the router
|
|
180
|
+
// after matching, but exposing this on public router state/options would make it
|
|
181
|
+
// part of the API. Keep a private, one-shot receiver keyed by router instance so
|
|
182
|
+
// navigate/fetch instrumentation can receive the metadata without leaking it.
|
|
183
|
+
let instrumentationClientResultMetaReceivers = new WeakMap<Router, InstrumentationMetaReceiver>();
|
|
184
|
+
|
|
137
185
|
export function getRouteInstrumentationUpdates(
|
|
138
186
|
fns: InstrumentRouteFunction[],
|
|
139
187
|
route: Readonly<DataRouteObject>,
|
|
140
188
|
) {
|
|
141
189
|
let aggregated: {
|
|
142
|
-
lazy:
|
|
143
|
-
'lazy.loader':
|
|
144
|
-
'lazy.action':
|
|
145
|
-
'lazy.middleware':
|
|
146
|
-
middleware:
|
|
147
|
-
loader:
|
|
148
|
-
action:
|
|
190
|
+
lazy: NonNullable<RouteInstrumentations['lazy']>[];
|
|
191
|
+
'lazy.loader': NonNullable<RouteInstrumentations['lazy.loader']>[];
|
|
192
|
+
'lazy.action': NonNullable<RouteInstrumentations['lazy.action']>[];
|
|
193
|
+
'lazy.middleware': NonNullable<RouteInstrumentations['lazy.middleware']>[];
|
|
194
|
+
middleware: NonNullable<RouteInstrumentations['middleware']>[];
|
|
195
|
+
loader: NonNullable<RouteInstrumentations['loader']>[];
|
|
196
|
+
action: NonNullable<RouteInstrumentations['action']>[];
|
|
149
197
|
} = {
|
|
150
198
|
lazy: [],
|
|
151
199
|
'lazy.loader': [],
|
|
@@ -162,11 +210,26 @@ export function getRouteInstrumentationUpdates(
|
|
|
162
210
|
index: route.index,
|
|
163
211
|
path: route.path,
|
|
164
212
|
instrument(i) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
213
|
+
if (i.lazy != null) {
|
|
214
|
+
aggregated.lazy.push(i.lazy);
|
|
215
|
+
}
|
|
216
|
+
if (i['lazy.loader'] != null) {
|
|
217
|
+
aggregated['lazy.loader'].push(i['lazy.loader']);
|
|
218
|
+
}
|
|
219
|
+
if (i['lazy.action'] != null) {
|
|
220
|
+
aggregated['lazy.action'].push(i['lazy.action']);
|
|
221
|
+
}
|
|
222
|
+
if (i['lazy.middleware'] != null) {
|
|
223
|
+
aggregated['lazy.middleware'].push(i['lazy.middleware']);
|
|
224
|
+
}
|
|
225
|
+
if (i.middleware != null) {
|
|
226
|
+
aggregated.middleware.push(i.middleware);
|
|
227
|
+
}
|
|
228
|
+
if (i.loader != null) {
|
|
229
|
+
aggregated.loader.push(i.loader);
|
|
230
|
+
}
|
|
231
|
+
if (i.action != null) {
|
|
232
|
+
aggregated.action.push(i.action);
|
|
170
233
|
}
|
|
171
234
|
},
|
|
172
235
|
}),
|
|
@@ -181,63 +244,123 @@ export function getRouteInstrumentationUpdates(
|
|
|
181
244
|
|
|
182
245
|
// Instrument lazy functions
|
|
183
246
|
if (typeof route.lazy === 'function' && aggregated.lazy.length > 0) {
|
|
184
|
-
let
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
247
|
+
let lazy = route.lazy;
|
|
248
|
+
updates.lazy = async (...args) => {
|
|
249
|
+
let result = await recurseRight(
|
|
250
|
+
aggregated.lazy,
|
|
251
|
+
undefined,
|
|
252
|
+
() => lazy(...args),
|
|
253
|
+
getInstrumentationInnerResult,
|
|
254
|
+
);
|
|
255
|
+
return throwOrReturnResult(result);
|
|
256
|
+
};
|
|
188
257
|
}
|
|
189
258
|
|
|
190
259
|
// Instrument the lazy object format
|
|
191
260
|
if (typeof route.lazy === 'object') {
|
|
192
261
|
let lazyObject: LazyRouteObject<DataRouteObject> = route.lazy;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
let
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
262
|
+
|
|
263
|
+
if (typeof lazyObject.middleware === 'function' && aggregated['lazy.middleware'].length > 0) {
|
|
264
|
+
let middleware = lazyObject.middleware;
|
|
265
|
+
updates.lazy = Object.assign(updates.lazy || {}, {
|
|
266
|
+
middleware: async (
|
|
267
|
+
...args: Parameters<NonNullable<LazyRouteObject<DataRouteObject>['middleware']>>
|
|
268
|
+
) => {
|
|
269
|
+
let result = await recurseRight(
|
|
270
|
+
aggregated['lazy.middleware'],
|
|
271
|
+
undefined,
|
|
272
|
+
() => middleware(...args),
|
|
273
|
+
getInstrumentationInnerResult,
|
|
274
|
+
);
|
|
275
|
+
return throwOrReturnResult(result);
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (typeof lazyObject.loader === 'function' && aggregated['lazy.loader'].length > 0) {
|
|
281
|
+
let loader = lazyObject.loader;
|
|
282
|
+
updates.lazy = Object.assign(updates.lazy || {}, {
|
|
283
|
+
loader: async (
|
|
284
|
+
...args: Parameters<NonNullable<LazyRouteObject<DataRouteObject>['loader']>>
|
|
285
|
+
) => {
|
|
286
|
+
let result = await recurseRight(
|
|
287
|
+
aggregated['lazy.loader'],
|
|
288
|
+
undefined,
|
|
289
|
+
() => loader(...args),
|
|
290
|
+
getInstrumentationInnerResult,
|
|
291
|
+
);
|
|
292
|
+
return throwOrReturnResult(result);
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (typeof lazyObject.action === 'function' && aggregated['lazy.action'].length > 0) {
|
|
298
|
+
let action = lazyObject.action;
|
|
299
|
+
updates.lazy = Object.assign(updates.lazy || {}, {
|
|
300
|
+
action: async (
|
|
301
|
+
...args: Parameters<NonNullable<LazyRouteObject<DataRouteObject>['action']>>
|
|
302
|
+
) => {
|
|
303
|
+
let result = await recurseRight(
|
|
304
|
+
aggregated['lazy.action'],
|
|
305
|
+
undefined,
|
|
306
|
+
() => action(...args),
|
|
307
|
+
getInstrumentationInnerResult,
|
|
308
|
+
);
|
|
309
|
+
return throwOrReturnResult(result);
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
}
|
|
205
313
|
}
|
|
206
314
|
|
|
207
315
|
// Instrument loader/action functions
|
|
208
|
-
(
|
|
209
|
-
let
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
316
|
+
if (typeof route.loader === 'function' && aggregated.loader.length > 0) {
|
|
317
|
+
let original = getUninstrumentedHandler(route.loader);
|
|
318
|
+
let instrumented = async (...args: Parameters<typeof original>) => {
|
|
319
|
+
let result = await recurseRight(
|
|
320
|
+
aggregated.loader,
|
|
321
|
+
getHandlerInfo(args[0]),
|
|
322
|
+
() => original(...args),
|
|
323
|
+
getInstrumentationInnerResult,
|
|
215
324
|
);
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
// @ts-expect-error
|
|
221
|
-
instrumented[UninstrumentedSymbol] = original;
|
|
222
|
-
updates[key] = instrumented;
|
|
223
|
-
}
|
|
325
|
+
return throwOrReturnResult(result);
|
|
326
|
+
};
|
|
327
|
+
if (original.hydrate === true) {
|
|
328
|
+
(instrumented as LoaderFunction).hydrate = true;
|
|
224
329
|
}
|
|
225
|
-
|
|
330
|
+
setUninstrumentedHandler(instrumented, original);
|
|
331
|
+
updates.loader = instrumented;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (typeof route.action === 'function' && aggregated.action.length > 0) {
|
|
335
|
+
let original = getUninstrumentedHandler(route.action);
|
|
336
|
+
let instrumented = async (...args: Parameters<typeof original>) => {
|
|
337
|
+
let result = await recurseRight(
|
|
338
|
+
aggregated.action,
|
|
339
|
+
getHandlerInfo(args[0]),
|
|
340
|
+
() => original(...args),
|
|
341
|
+
getInstrumentationInnerResult,
|
|
342
|
+
);
|
|
343
|
+
return throwOrReturnResult(result);
|
|
344
|
+
};
|
|
345
|
+
setUninstrumentedHandler(instrumented, original);
|
|
346
|
+
updates.action = instrumented;
|
|
347
|
+
}
|
|
226
348
|
|
|
227
349
|
// Instrument middleware functions
|
|
228
350
|
if (route.middleware && route.middleware.length > 0 && aggregated.middleware.length > 0) {
|
|
229
351
|
updates.middleware = route.middleware.map((middleware) => {
|
|
230
|
-
|
|
231
|
-
let
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
return
|
|
239
|
-
}
|
|
240
|
-
|
|
352
|
+
let original = getUninstrumentedHandler(middleware);
|
|
353
|
+
let instrumented = async (...args: Parameters<typeof original>) => {
|
|
354
|
+
let result = await recurseRight(
|
|
355
|
+
aggregated.middleware,
|
|
356
|
+
getHandlerInfo(args[0]),
|
|
357
|
+
() => original(...args),
|
|
358
|
+
getInstrumentationInnerResult,
|
|
359
|
+
);
|
|
360
|
+
return throwOrReturnResult(result);
|
|
361
|
+
};
|
|
362
|
+
setUninstrumentedHandler(instrumented, original);
|
|
363
|
+
return instrumented;
|
|
241
364
|
});
|
|
242
365
|
}
|
|
243
366
|
|
|
@@ -249,8 +372,8 @@ export function instrumentClientSideRouter(
|
|
|
249
372
|
fns: InstrumentRouterFunction[],
|
|
250
373
|
): Router {
|
|
251
374
|
let aggregated: {
|
|
252
|
-
navigate:
|
|
253
|
-
fetch:
|
|
375
|
+
navigate: NonNullable<RouterInstrumentations['navigate']>[];
|
|
376
|
+
fetch: NonNullable<RouterInstrumentations['fetch']>[];
|
|
254
377
|
} = {
|
|
255
378
|
navigate: [],
|
|
256
379
|
fetch: [],
|
|
@@ -259,49 +382,85 @@ export function instrumentClientSideRouter(
|
|
|
259
382
|
fns.forEach((fn) =>
|
|
260
383
|
fn({
|
|
261
384
|
instrument(i) {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
385
|
+
if (i.navigate != null) {
|
|
386
|
+
aggregated.navigate.push(i.navigate);
|
|
387
|
+
}
|
|
388
|
+
if (i.fetch != null) {
|
|
389
|
+
aggregated.fetch.push(i.fetch);
|
|
267
390
|
}
|
|
268
391
|
},
|
|
269
392
|
}),
|
|
270
393
|
);
|
|
271
394
|
|
|
272
395
|
if (aggregated.navigate.length > 0) {
|
|
273
|
-
|
|
274
|
-
let
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
396
|
+
let navigate = getUninstrumentedHandler(router.navigate);
|
|
397
|
+
let instrumentedNavigate = async (
|
|
398
|
+
...args: Parameters<typeof navigate>
|
|
399
|
+
): Promise<Awaited<ReturnType<typeof navigate>>> => {
|
|
400
|
+
let [to, opts] = args;
|
|
401
|
+
let meta: InstrumentationResultMeta | undefined;
|
|
402
|
+
let info: RouterNavigationInstrumentationInfo = {
|
|
278
403
|
to: typeof to === 'number' || typeof to === 'string' ? to : to ? createPath(to) : '.',
|
|
279
404
|
...getRouterInfo(router, opts ?? {}),
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
405
|
+
};
|
|
406
|
+
let result = await recurseRight(
|
|
407
|
+
aggregated.navigate,
|
|
408
|
+
info,
|
|
409
|
+
async () => {
|
|
410
|
+
if (typeof to === 'number') {
|
|
411
|
+
return await navigate(...args);
|
|
412
|
+
}
|
|
413
|
+
let cleanup = setInstrumentationClientResultMetaReceiver(router, (value) => {
|
|
414
|
+
meta = value;
|
|
415
|
+
});
|
|
416
|
+
try {
|
|
417
|
+
return await navigate(...args);
|
|
418
|
+
} finally {
|
|
419
|
+
cleanup();
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
(result): InstrumentationClientRouterResult => ({
|
|
423
|
+
...getInstrumentationInnerResult(result),
|
|
424
|
+
meta,
|
|
425
|
+
}),
|
|
426
|
+
);
|
|
427
|
+
return throwOrReturnResult(result);
|
|
428
|
+
};
|
|
429
|
+
setUninstrumentedHandler(instrumentedNavigate, navigate);
|
|
430
|
+
router.navigate = instrumentedNavigate as Router['navigate'];
|
|
287
431
|
}
|
|
288
432
|
|
|
289
433
|
if (aggregated.fetch.length > 0) {
|
|
290
|
-
|
|
291
|
-
let
|
|
292
|
-
|
|
293
|
-
let
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
434
|
+
let fetch = getUninstrumentedHandler(router.fetch);
|
|
435
|
+
let instrumentedFetch = async (...args: Parameters<typeof fetch>) => {
|
|
436
|
+
let [key, _, href, opts] = args;
|
|
437
|
+
let meta: InstrumentationResultMeta | undefined;
|
|
438
|
+
let result = await recurseRight(
|
|
439
|
+
aggregated.fetch,
|
|
440
|
+
{
|
|
441
|
+
href: href ?? '.',
|
|
442
|
+
fetcherKey: key,
|
|
443
|
+
...getRouterInfo(router, opts ?? {}),
|
|
444
|
+
} satisfies RouterFetchInstrumentationInfo,
|
|
445
|
+
async () => {
|
|
446
|
+
let cleanup = setInstrumentationClientResultMetaReceiver(router, (value) => {
|
|
447
|
+
meta = value;
|
|
448
|
+
});
|
|
449
|
+
try {
|
|
450
|
+
return await fetch(...args);
|
|
451
|
+
} finally {
|
|
452
|
+
cleanup();
|
|
453
|
+
}
|
|
454
|
+
},
|
|
455
|
+
(result): InstrumentationClientRouterResult => ({
|
|
456
|
+
...getInstrumentationInnerResult(result),
|
|
457
|
+
meta,
|
|
458
|
+
}),
|
|
459
|
+
);
|
|
460
|
+
return throwOrReturnResult(result);
|
|
461
|
+
};
|
|
462
|
+
setUninstrumentedHandler(instrumentedFetch, fetch);
|
|
463
|
+
router.fetch = instrumentedFetch;
|
|
305
464
|
}
|
|
306
465
|
|
|
307
466
|
return router;
|
|
@@ -312,7 +471,10 @@ export function instrumentHandler(
|
|
|
312
471
|
fns: InstrumentRequestHandlerFunction[],
|
|
313
472
|
): RequestHandler {
|
|
314
473
|
let aggregated: {
|
|
315
|
-
request: InstrumentFunction<
|
|
474
|
+
request: InstrumentFunction<
|
|
475
|
+
RequestHandlerInstrumentationInfo,
|
|
476
|
+
InstrumentationServerHandlerResult
|
|
477
|
+
>[];
|
|
316
478
|
} = {
|
|
317
479
|
request: [],
|
|
318
480
|
};
|
|
@@ -320,11 +482,8 @@ export function instrumentHandler(
|
|
|
320
482
|
fns.forEach((fn) =>
|
|
321
483
|
fn({
|
|
322
484
|
instrument(i) {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
if (i[key]) {
|
|
326
|
-
aggregated[key].push(i[key] as any);
|
|
327
|
-
}
|
|
485
|
+
if (i.request != null) {
|
|
486
|
+
aggregated.request.push(i.request);
|
|
328
487
|
}
|
|
329
488
|
},
|
|
330
489
|
}),
|
|
@@ -333,73 +492,122 @@ export function instrumentHandler(
|
|
|
333
492
|
let instrumentedHandler = handler;
|
|
334
493
|
|
|
335
494
|
if (aggregated.request.length > 0) {
|
|
336
|
-
instrumentedHandler =
|
|
337
|
-
let [request, context] = args
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
495
|
+
instrumentedHandler = async (...args) => {
|
|
496
|
+
let [request, context] = args;
|
|
497
|
+
let instrumentationContext = context ?? new RouterContextProvider();
|
|
498
|
+
let result = await recurseRight(
|
|
499
|
+
aggregated.request,
|
|
500
|
+
{
|
|
501
|
+
request: getReadonlyRequest(request),
|
|
502
|
+
context: getReadonlyContext(instrumentationContext),
|
|
503
|
+
} satisfies RequestHandlerInstrumentationInfo,
|
|
504
|
+
() => handler(request, instrumentationContext),
|
|
505
|
+
(result, info) => {
|
|
506
|
+
let meta: InstrumentationResultMeta | undefined;
|
|
507
|
+
try {
|
|
508
|
+
meta = info.context?.get(instrumentationResultMetaContext);
|
|
509
|
+
} catch {
|
|
510
|
+
// Not all instrumentation contexts have request/route metadata.
|
|
511
|
+
}
|
|
512
|
+
invariant(
|
|
513
|
+
result.value instanceof Response,
|
|
514
|
+
'Expected a Response from the request handler',
|
|
515
|
+
);
|
|
516
|
+
return {
|
|
517
|
+
...getInstrumentationInnerResult(result),
|
|
518
|
+
statusCode: result.value.status,
|
|
519
|
+
meta,
|
|
520
|
+
};
|
|
521
|
+
},
|
|
522
|
+
);
|
|
523
|
+
return throwOrReturnResult(result);
|
|
524
|
+
};
|
|
343
525
|
}
|
|
344
526
|
|
|
345
527
|
return instrumentedHandler;
|
|
346
528
|
}
|
|
347
529
|
|
|
348
|
-
function
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
530
|
+
function getUninstrumentedHandler<T extends InstrumentableFunction>(handler: T): T {
|
|
531
|
+
return (handler as InstrumentedFunction<T>)[UninstrumentedSymbol] ?? handler;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function setUninstrumentedHandler<TArgs extends unknown[], TResult>(
|
|
535
|
+
handler: (...args: TArgs) => MaybePromise<TResult>,
|
|
536
|
+
uninstrumentedHandler: (...args: TArgs) => MaybePromise<TResult>,
|
|
352
537
|
) {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
538
|
+
(handler as InstrumentedFunction<(...args: TArgs) => MaybePromise<TResult>>)[
|
|
539
|
+
UninstrumentedSymbol
|
|
540
|
+
] = uninstrumentedHandler;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
export function setInstrumentationClientResultMetaReceiver(
|
|
544
|
+
router: Router,
|
|
545
|
+
receiver: InstrumentationMetaReceiver,
|
|
546
|
+
): () => void {
|
|
547
|
+
instrumentationClientResultMetaReceivers.set(router, receiver);
|
|
548
|
+
return () => {
|
|
549
|
+
if (instrumentationClientResultMetaReceivers.get(router) === receiver) {
|
|
550
|
+
instrumentationClientResultMetaReceivers.delete(router);
|
|
365
551
|
}
|
|
366
|
-
return result.value;
|
|
367
552
|
};
|
|
368
553
|
}
|
|
369
554
|
|
|
370
|
-
|
|
555
|
+
export function consumeInstrumentationClientResultMetaReceiver(
|
|
556
|
+
router: Router,
|
|
557
|
+
): InstrumentationMetaReceiver | undefined {
|
|
558
|
+
let receiver = instrumentationClientResultMetaReceivers.get(router);
|
|
559
|
+
instrumentationClientResultMetaReceivers.delete(router);
|
|
560
|
+
return receiver;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
type RecurseResult<TResult> =
|
|
564
|
+
| { type: 'success'; value: TResult }
|
|
565
|
+
| { type: 'error'; value: unknown };
|
|
371
566
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
567
|
+
function throwOrReturnResult<TResult>(result: RecurseResult<TResult>): TResult {
|
|
568
|
+
if (result.type === 'error') {
|
|
569
|
+
throw result.value;
|
|
570
|
+
}
|
|
571
|
+
return result.value;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
async function recurseRight<
|
|
575
|
+
TResult,
|
|
576
|
+
TInfo extends InstrumentationInfo,
|
|
577
|
+
TInnerResult extends InstrumentationHandlerResult,
|
|
578
|
+
>(
|
|
579
|
+
impls: InstrumentFunction<TInfo, TInnerResult>[],
|
|
580
|
+
info: TInfo,
|
|
581
|
+
handler: () => MaybePromise<TResult>,
|
|
582
|
+
getInnerResult: (result: RecurseResult<TResult>, info: TInfo) => TInnerResult,
|
|
583
|
+
state: RecurseState<TResult, TInnerResult> = {
|
|
584
|
+
result: null,
|
|
585
|
+
innerResult: null,
|
|
586
|
+
},
|
|
587
|
+
index = impls.length - 1,
|
|
588
|
+
): Promise<RecurseResult<TResult>> {
|
|
378
589
|
let impl = impls[index];
|
|
379
|
-
let result: RecurseResult | undefined;
|
|
380
590
|
if (!impl) {
|
|
381
591
|
try {
|
|
382
592
|
let value = await handler();
|
|
383
|
-
result = { type: 'success', value };
|
|
593
|
+
state.result = { type: 'success', value };
|
|
384
594
|
} catch (e) {
|
|
385
|
-
result = { type: 'error', value: e };
|
|
595
|
+
state.result = { type: 'error', value: e };
|
|
386
596
|
}
|
|
597
|
+
state.innerResult = getInnerResult(state.result, info);
|
|
387
598
|
} else {
|
|
388
599
|
// If they forget to call the handler, or if they throw before calling the
|
|
389
600
|
// handler, we need to ensure the handlers still gets called
|
|
390
|
-
let handlerPromise:
|
|
391
|
-
let callHandler = async (): Promise<
|
|
601
|
+
let handlerPromise: Promise<RecurseResult<TResult>> | undefined = undefined;
|
|
602
|
+
let callHandler = async (): Promise<TInnerResult> => {
|
|
392
603
|
if (handlerPromise) {
|
|
393
604
|
console.error('You cannot call instrumented handlers more than once');
|
|
394
605
|
} else {
|
|
395
|
-
handlerPromise = recurseRight(impls, info, handler, index - 1);
|
|
606
|
+
handlerPromise = recurseRight(impls, info, handler, getInnerResult, state, index - 1);
|
|
396
607
|
}
|
|
397
|
-
|
|
398
|
-
invariant(
|
|
399
|
-
|
|
400
|
-
return { status: 'error', error: result.value };
|
|
401
|
-
}
|
|
402
|
-
return { status: 'success', error: undefined };
|
|
608
|
+
await handlerPromise;
|
|
609
|
+
invariant(state.innerResult, 'Expected an inner result');
|
|
610
|
+
return state.innerResult;
|
|
403
611
|
};
|
|
404
612
|
|
|
405
613
|
try {
|
|
@@ -416,24 +624,40 @@ async function recurseRight<T extends InstrumentationInfo>(
|
|
|
416
624
|
await handlerPromise;
|
|
417
625
|
}
|
|
418
626
|
|
|
419
|
-
if (result) {
|
|
420
|
-
return result;
|
|
627
|
+
if (state.result) {
|
|
628
|
+
return state.result;
|
|
421
629
|
}
|
|
422
630
|
|
|
423
|
-
|
|
631
|
+
state.result = {
|
|
424
632
|
type: 'error',
|
|
425
633
|
value: new Error('No result assigned in instrumentation chain.'),
|
|
426
634
|
};
|
|
635
|
+
state.innerResult = getInnerResult(state.result, info);
|
|
636
|
+
return state.result;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
type RecurseState<TResult, TInnerResult> = {
|
|
640
|
+
result: RecurseResult<TResult> | null;
|
|
641
|
+
innerResult: TInnerResult | null;
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
function getInstrumentationInnerResult<TResult>(
|
|
645
|
+
result: RecurseResult<TResult>,
|
|
646
|
+
): InstrumentationHandlerResult {
|
|
647
|
+
if (result.type === 'error' && result.value instanceof Error) {
|
|
648
|
+
return { status: 'error', error: result.value };
|
|
649
|
+
}
|
|
650
|
+
return { status: 'success', error: undefined };
|
|
427
651
|
}
|
|
428
652
|
|
|
429
653
|
function getHandlerInfo(
|
|
430
654
|
args: LoaderFunctionArgs | ActionFunctionArgs | Parameters<MiddlewareFunction>[0],
|
|
431
655
|
): RouteHandlerInstrumentationInfo {
|
|
432
|
-
let { request, context, params
|
|
656
|
+
let { request, context, params } = args;
|
|
433
657
|
return {
|
|
658
|
+
...args,
|
|
434
659
|
request: getReadonlyRequest(request),
|
|
435
660
|
params: { ...params },
|
|
436
|
-
pattern,
|
|
437
661
|
context: getReadonlyContext(context),
|
|
438
662
|
};
|
|
439
663
|
}
|
|
@@ -450,6 +674,7 @@ function getRouterInfo(
|
|
|
450
674
|
...('body' in opts ? { body: opts.body } : {}),
|
|
451
675
|
};
|
|
452
676
|
}
|
|
677
|
+
|
|
453
678
|
// Return a shallow readonly "clone" of the Request with the info they may
|
|
454
679
|
// want to read from during instrumentation
|
|
455
680
|
function getReadonlyRequest(request: Request): {
|
|
@@ -467,30 +692,9 @@ function getReadonlyRequest(request: Request): {
|
|
|
467
692
|
}
|
|
468
693
|
|
|
469
694
|
function getReadonlyContext(
|
|
470
|
-
context:
|
|
471
|
-
):
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
return frozen;
|
|
476
|
-
} else {
|
|
477
|
-
return {
|
|
478
|
-
get: <T>(ctx: RouterContext<T>) => (context as unknown as RouterContextProvider).get(ctx),
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// From turbo-stream-v2/flatten.ts
|
|
484
|
-
const objectProtoNames = Object.getOwnPropertyNames(Object.prototype).sort().join('\0');
|
|
485
|
-
|
|
486
|
-
function isPlainObject(thing: unknown): thing is Record<string | number | symbol, unknown> {
|
|
487
|
-
if (thing === null || typeof thing !== 'object') {
|
|
488
|
-
return false;
|
|
489
|
-
}
|
|
490
|
-
const proto = Object.getPrototypeOf(thing);
|
|
491
|
-
return (
|
|
492
|
-
proto === Object.prototype ||
|
|
493
|
-
proto === null ||
|
|
494
|
-
Object.getOwnPropertyNames(proto).sort().join('\0') === objectProtoNames
|
|
495
|
-
);
|
|
695
|
+
context: Readonly<RouterContextProvider>,
|
|
696
|
+
): Pick<RouterContextProvider, 'get'> {
|
|
697
|
+
return {
|
|
698
|
+
get: <T>(ctx: RouterContext<T>) => context.get(ctx),
|
|
699
|
+
};
|
|
496
700
|
}
|