| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 | |
| 6 | |
| 7 | |
| 8 | |
| 9 | |
| 10 |
|
| 11 | import { createPath, invariant } from "./history.js";
|
| 12 | import { RouterContextProvider, createContext } from "./utils.js";
|
| 13 |
|
| 14 | const UninstrumentedSymbol = Symbol("Uninstrumented");
|
| 15 | const instrumentationResultMetaContext = createContext();
|
| 16 | let instrumentationClientResultMetaReceivers = new WeakMap();
|
| 17 | function getRouteInstrumentationUpdates(fns, route) {
|
| 18 | let aggregated = {
|
| 19 | lazy: [],
|
| 20 | "lazy.loader": [],
|
| 21 | "lazy.action": [],
|
| 22 | "lazy.middleware": [],
|
| 23 | middleware: [],
|
| 24 | loader: [],
|
| 25 | action: []
|
| 26 | };
|
| 27 | fns.forEach((fn) => fn({
|
| 28 | id: route.id,
|
| 29 | index: route.index,
|
| 30 | path: route.path,
|
| 31 | instrument(i) {
|
| 32 | if (i.lazy != null) aggregated.lazy.push(i.lazy);
|
| 33 | if (i["lazy.loader"] != null) aggregated["lazy.loader"].push(i["lazy.loader"]);
|
| 34 | if (i["lazy.action"] != null) aggregated["lazy.action"].push(i["lazy.action"]);
|
| 35 | if (i["lazy.middleware"] != null) aggregated["lazy.middleware"].push(i["lazy.middleware"]);
|
| 36 | if (i.middleware != null) aggregated.middleware.push(i.middleware);
|
| 37 | if (i.loader != null) aggregated.loader.push(i.loader);
|
| 38 | if (i.action != null) aggregated.action.push(i.action);
|
| 39 | }
|
| 40 | }));
|
| 41 | let updates = {};
|
| 42 | if (typeof route.lazy === "function" && aggregated.lazy.length > 0) {
|
| 43 | let lazy = route.lazy;
|
| 44 | updates.lazy = async (...args) => {
|
| 45 | return throwOrReturnResult(await recurseRight(aggregated.lazy, void 0, () => lazy(...args), getInstrumentationInnerResult));
|
| 46 | };
|
| 47 | }
|
| 48 | if (typeof route.lazy === "object") {
|
| 49 | let lazyObject = route.lazy;
|
| 50 | if (typeof lazyObject.middleware === "function" && aggregated["lazy.middleware"].length > 0) {
|
| 51 | let middleware = lazyObject.middleware;
|
| 52 | updates.lazy = Object.assign(updates.lazy || {}, { middleware: async (...args) => {
|
| 53 | return throwOrReturnResult(await recurseRight(aggregated["lazy.middleware"], void 0, () => middleware(...args), getInstrumentationInnerResult));
|
| 54 | } });
|
| 55 | }
|
| 56 | if (typeof lazyObject.loader === "function" && aggregated["lazy.loader"].length > 0) {
|
| 57 | let loader = lazyObject.loader;
|
| 58 | updates.lazy = Object.assign(updates.lazy || {}, { loader: async (...args) => {
|
| 59 | return throwOrReturnResult(await recurseRight(aggregated["lazy.loader"], void 0, () => loader(...args), getInstrumentationInnerResult));
|
| 60 | } });
|
| 61 | }
|
| 62 | if (typeof lazyObject.action === "function" && aggregated["lazy.action"].length > 0) {
|
| 63 | let action = lazyObject.action;
|
| 64 | updates.lazy = Object.assign(updates.lazy || {}, { action: async (...args) => {
|
| 65 | return throwOrReturnResult(await recurseRight(aggregated["lazy.action"], void 0, () => action(...args), getInstrumentationInnerResult));
|
| 66 | } });
|
| 67 | }
|
| 68 | }
|
| 69 | if (typeof route.loader === "function" && aggregated.loader.length > 0) {
|
| 70 | let original = getUninstrumentedHandler(route.loader);
|
| 71 | let instrumented = async (...args) => {
|
| 72 | return throwOrReturnResult(await recurseRight(aggregated.loader, getHandlerInfo(args[0]), () => original(...args), getInstrumentationInnerResult));
|
| 73 | };
|
| 74 | if (original.hydrate === true) instrumented.hydrate = true;
|
| 75 | setUninstrumentedHandler(instrumented, original);
|
| 76 | updates.loader = instrumented;
|
| 77 | }
|
| 78 | if (typeof route.action === "function" && aggregated.action.length > 0) {
|
| 79 | let original = getUninstrumentedHandler(route.action);
|
| 80 | let instrumented = async (...args) => {
|
| 81 | return throwOrReturnResult(await recurseRight(aggregated.action, getHandlerInfo(args[0]), () => original(...args), getInstrumentationInnerResult));
|
| 82 | };
|
| 83 | setUninstrumentedHandler(instrumented, original);
|
| 84 | updates.action = instrumented;
|
| 85 | }
|
| 86 | if (route.middleware && route.middleware.length > 0 && aggregated.middleware.length > 0) updates.middleware = route.middleware.map((middleware) => {
|
| 87 | let original = getUninstrumentedHandler(middleware);
|
| 88 | let instrumented = async (...args) => {
|
| 89 | return throwOrReturnResult(await recurseRight(aggregated.middleware, getHandlerInfo(args[0]), () => original(...args), getInstrumentationInnerResult));
|
| 90 | };
|
| 91 | setUninstrumentedHandler(instrumented, original);
|
| 92 | return instrumented;
|
| 93 | });
|
| 94 | return updates;
|
| 95 | }
|
| 96 | function instrumentClientSideRouter(router, fns) {
|
| 97 | let aggregated = {
|
| 98 | navigate: [],
|
| 99 | fetch: []
|
| 100 | };
|
| 101 | fns.forEach((fn) => fn({ instrument(i) {
|
| 102 | if (i.navigate != null) aggregated.navigate.push(i.navigate);
|
| 103 | if (i.fetch != null) aggregated.fetch.push(i.fetch);
|
| 104 | } }));
|
| 105 | if (aggregated.navigate.length > 0) {
|
| 106 | let navigate = getUninstrumentedHandler(router.navigate);
|
| 107 | let instrumentedNavigate = async (...args) => {
|
| 108 | let [to, opts] = args;
|
| 109 | let meta;
|
| 110 | let info = {
|
| 111 | to: typeof to === "number" || typeof to === "string" ? to : to ? createPath(to) : ".",
|
| 112 | ...getRouterInfo(router, opts ?? {})
|
| 113 | };
|
| 114 | return throwOrReturnResult(await recurseRight(aggregated.navigate, info, async () => {
|
| 115 | if (typeof to === "number") return await navigate(...args);
|
| 116 | let cleanup = setInstrumentationClientResultMetaReceiver(router, (value) => {
|
| 117 | meta = value;
|
| 118 | });
|
| 119 | try {
|
| 120 | return await navigate(...args);
|
| 121 | } finally {
|
| 122 | cleanup();
|
| 123 | }
|
| 124 | }, (result) => ({
|
| 125 | ...getInstrumentationInnerResult(result),
|
| 126 | meta
|
| 127 | })));
|
| 128 | };
|
| 129 | setUninstrumentedHandler(instrumentedNavigate, navigate);
|
| 130 | router.navigate = instrumentedNavigate;
|
| 131 | }
|
| 132 | if (aggregated.fetch.length > 0) {
|
| 133 | let fetch = getUninstrumentedHandler(router.fetch);
|
| 134 | let instrumentedFetch = async (...args) => {
|
| 135 | let [key, _, href, opts] = args;
|
| 136 | let meta;
|
| 137 | return throwOrReturnResult(await recurseRight(aggregated.fetch, {
|
| 138 | href: href ?? ".",
|
| 139 | fetcherKey: key,
|
| 140 | ...getRouterInfo(router, opts ?? {})
|
| 141 | }, async () => {
|
| 142 | let cleanup = setInstrumentationClientResultMetaReceiver(router, (value) => {
|
| 143 | meta = value;
|
| 144 | });
|
| 145 | try {
|
| 146 | return await fetch(...args);
|
| 147 | } finally {
|
| 148 | cleanup();
|
| 149 | }
|
| 150 | }, (result) => ({
|
| 151 | ...getInstrumentationInnerResult(result),
|
| 152 | meta
|
| 153 | })));
|
| 154 | };
|
| 155 | setUninstrumentedHandler(instrumentedFetch, fetch);
|
| 156 | router.fetch = instrumentedFetch;
|
| 157 | }
|
| 158 | return router;
|
| 159 | }
|
| 160 | function instrumentHandler(handler, fns) {
|
| 161 | let aggregated = { request: [] };
|
| 162 | fns.forEach((fn) => fn({ instrument(i) {
|
| 163 | if (i.request != null) aggregated.request.push(i.request);
|
| 164 | } }));
|
| 165 | let instrumentedHandler = handler;
|
| 166 | if (aggregated.request.length > 0) instrumentedHandler = async (...args) => {
|
| 167 | let [request, context] = args;
|
| 168 | let instrumentationContext = context ?? new RouterContextProvider();
|
| 169 | return throwOrReturnResult(await recurseRight(aggregated.request, {
|
| 170 | request: getReadonlyRequest(request),
|
| 171 | context: getReadonlyContext(instrumentationContext)
|
| 172 | }, () => handler(request, instrumentationContext), (result, info) => {
|
| 173 | let meta;
|
| 174 | try {
|
| 175 | meta = info.context?.get(instrumentationResultMetaContext);
|
| 176 | } catch {}
|
| 177 | invariant(result.value instanceof Response, "Expected a Response from the request handler");
|
| 178 | return {
|
| 179 | ...getInstrumentationInnerResult(result),
|
| 180 | statusCode: result.value.status,
|
| 181 | meta
|
| 182 | };
|
| 183 | }));
|
| 184 | };
|
| 185 | return instrumentedHandler;
|
| 186 | }
|
| 187 | function getUninstrumentedHandler(handler) {
|
| 188 | return handler[UninstrumentedSymbol] ?? handler;
|
| 189 | }
|
| 190 | function setUninstrumentedHandler(handler, uninstrumentedHandler) {
|
| 191 | handler[UninstrumentedSymbol] = uninstrumentedHandler;
|
| 192 | }
|
| 193 | function setInstrumentationClientResultMetaReceiver(router, receiver) {
|
| 194 | instrumentationClientResultMetaReceivers.set(router, receiver);
|
| 195 | return () => {
|
| 196 | if (instrumentationClientResultMetaReceivers.get(router) === receiver) instrumentationClientResultMetaReceivers.delete(router);
|
| 197 | };
|
| 198 | }
|
| 199 | function consumeInstrumentationClientResultMetaReceiver(router) {
|
| 200 | let receiver = instrumentationClientResultMetaReceivers.get(router);
|
| 201 | instrumentationClientResultMetaReceivers.delete(router);
|
| 202 | return receiver;
|
| 203 | }
|
| 204 | function throwOrReturnResult(result) {
|
| 205 | if (result.type === "error") throw result.value;
|
| 206 | return result.value;
|
| 207 | }
|
| 208 | async function recurseRight(impls, info, handler, getInnerResult, state = {
|
| 209 | result: null,
|
| 210 | innerResult: null
|
| 211 | }, index = impls.length - 1) {
|
| 212 | let impl = impls[index];
|
| 213 | if (!impl) {
|
| 214 | try {
|
| 215 | state.result = {
|
| 216 | type: "success",
|
| 217 | value: await handler()
|
| 218 | };
|
| 219 | } catch (e) {
|
| 220 | state.result = {
|
| 221 | type: "error",
|
| 222 | value: e
|
| 223 | };
|
| 224 | }
|
| 225 | state.innerResult = getInnerResult(state.result, info);
|
| 226 | } else {
|
| 227 | let handlerPromise = void 0;
|
| 228 | let callHandler = async () => {
|
| 229 | if (handlerPromise) console.error("You cannot call instrumented handlers more than once");
|
| 230 | else handlerPromise = recurseRight(impls, info, handler, getInnerResult, state, index - 1);
|
| 231 | await handlerPromise;
|
| 232 | invariant(state.innerResult, "Expected an inner result");
|
| 233 | return state.innerResult;
|
| 234 | };
|
| 235 | try {
|
| 236 | await impl(callHandler, info);
|
| 237 | } catch (e) {
|
| 238 | console.error("An instrumentation function threw an error:", e);
|
| 239 | }
|
| 240 | if (!handlerPromise) await callHandler();
|
| 241 | await handlerPromise;
|
| 242 | }
|
| 243 | if (state.result) return state.result;
|
| 244 | state.result = {
|
| 245 | type: "error",
|
| 246 | value: new Error("No result assigned in instrumentation chain.")
|
| 247 | };
|
| 248 | state.innerResult = getInnerResult(state.result, info);
|
| 249 | return state.result;
|
| 250 | }
|
| 251 | function getInstrumentationInnerResult(result) {
|
| 252 | if (result.type === "error" && result.value instanceof Error) return {
|
| 253 | status: "error",
|
| 254 | error: result.value
|
| 255 | };
|
| 256 | return {
|
| 257 | status: "success",
|
| 258 | error: void 0
|
| 259 | };
|
| 260 | }
|
| 261 | function getHandlerInfo(args) {
|
| 262 | let { request, context, params } = args;
|
| 263 | return {
|
| 264 | ...args,
|
| 265 | request: getReadonlyRequest(request),
|
| 266 | params: { ...params },
|
| 267 | context: getReadonlyContext(context)
|
| 268 | };
|
| 269 | }
|
| 270 | function getRouterInfo(router, opts) {
|
| 271 | return {
|
| 272 | currentUrl: createPath(router.state.location),
|
| 273 | ..."formMethod" in opts ? { formMethod: opts.formMethod } : {},
|
| 274 | ..."formEncType" in opts ? { formEncType: opts.formEncType } : {},
|
| 275 | ..."formData" in opts ? { formData: opts.formData } : {},
|
| 276 | ..."body" in opts ? { body: opts.body } : {}
|
| 277 | };
|
| 278 | }
|
| 279 | function getReadonlyRequest(request) {
|
| 280 | return {
|
| 281 | method: request.method,
|
| 282 | url: request.url,
|
| 283 | headers: { get: (...args) => request.headers.get(...args) }
|
| 284 | };
|
| 285 | }
|
| 286 | function getReadonlyContext(context) {
|
| 287 | return { get: (ctx) => context.get(ctx) };
|
| 288 | }
|
| 289 |
|
| 290 | export { consumeInstrumentationClientResultMetaReceiver, getRouteInstrumentationUpdates, instrumentClientSideRouter, instrumentHandler, instrumentationResultMetaContext };
|