@rangojs/router 0.0.0-experimental.128 → 0.0.0-experimental.129
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/vite/index.js +1 -1
- package/package.json +1 -1
- package/src/cloudflare/tracing.ts +5 -5
- package/src/router/instrument.ts +46 -4
- package/src/router/segment-resolution/fresh.ts +18 -4
- package/src/router/segment-resolution/helpers.ts +10 -5
- package/src/router/segment-resolution/revalidation.ts +10 -4
- package/src/router/tracing.ts +7 -1
package/dist/vite/index.js
CHANGED
|
@@ -2133,7 +2133,7 @@ import { resolve } from "node:path";
|
|
|
2133
2133
|
// package.json
|
|
2134
2134
|
var package_default = {
|
|
2135
2135
|
name: "@rangojs/router",
|
|
2136
|
-
version: "0.0.0-experimental.
|
|
2136
|
+
version: "0.0.0-experimental.129",
|
|
2137
2137
|
description: "Django-inspired RSC router with composable URL patterns",
|
|
2138
2138
|
keywords: [
|
|
2139
2139
|
"react",
|
package/package.json
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* Cloudflare custom-spans integration.
|
|
3
3
|
*
|
|
4
4
|
* Bridges the router's performance phases (request, middleware, action,
|
|
5
|
-
* loaders, render, ssr) onto Cloudflare Workers custom spans so they show
|
|
6
|
-
* trace waterfall and OpenTelemetry exports next to the platform's
|
|
7
|
-
* spans (KV reads, D1 queries, fetch calls), with correct
|
|
5
|
+
* loaders, handler, render, ssr) onto Cloudflare Workers custom spans so they show
|
|
6
|
+
* up in the trace waterfall and OpenTelemetry exports next to the platform's
|
|
7
|
+
* automatic spans (KV reads, D1 queries, fetch calls), with correct nesting.
|
|
8
8
|
*
|
|
9
9
|
* Usage (Cloudflare preset only):
|
|
10
10
|
*
|
|
@@ -95,8 +95,8 @@ const cloudflareSpanRunner: SpanRunner = (name, fn) => {
|
|
|
95
95
|
/**
|
|
96
96
|
* Create the tracing config for a Cloudflare router. Pass the result to
|
|
97
97
|
* `createRouter({ tracing })`. Spans are emitted for the request, middleware,
|
|
98
|
-
* action, loaders, render, and ssr phases; pass `spans` to turn
|
|
99
|
-
* phases off.
|
|
98
|
+
* action, loaders, handler, render, and ssr phases; pass `spans` to turn
|
|
99
|
+
* individual phases off.
|
|
100
100
|
*
|
|
101
101
|
* @see createOTelTracing (`@rangojs/router`) for the same slot on any platform
|
|
102
102
|
* with an OpenTelemetry SDK.
|
package/src/router/instrument.ts
CHANGED
|
@@ -66,6 +66,25 @@ export interface PhaseSpec {
|
|
|
66
66
|
spanName: string;
|
|
67
67
|
/** Span attributes set automatically when the span opens. */
|
|
68
68
|
attributes?: Record<string, string | number | boolean>;
|
|
69
|
+
/**
|
|
70
|
+
* Span attributes resolved AFTER the wrapped work runs (so they can read state
|
|
71
|
+
* that only exists once the work is underway, e.g. the matched route name).
|
|
72
|
+
* Applied for streaming phases once fn has constructed its value. Return
|
|
73
|
+
* undefined to add nothing.
|
|
74
|
+
*/
|
|
75
|
+
lazyAttributes?: () => Record<string, string | number | boolean> | undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The matched route name for the current request, or undefined when there is no
|
|
80
|
+
* named route (unmatched / auto-generated). Shared by the render phase's metric
|
|
81
|
+
* label and its rango.route span attribute so the two can't disagree.
|
|
82
|
+
*/
|
|
83
|
+
function currentRouteName(): string | undefined {
|
|
84
|
+
const routeName = _getRequestContext()?._routeName;
|
|
85
|
+
return routeName && !isAutoGeneratedRouteName(routeName)
|
|
86
|
+
? routeName
|
|
87
|
+
: undefined;
|
|
69
88
|
}
|
|
70
89
|
|
|
71
90
|
/**
|
|
@@ -115,6 +134,18 @@ export const PHASES = {
|
|
|
115
134
|
attributes: { "rango.loader_id": id },
|
|
116
135
|
}),
|
|
117
136
|
|
|
137
|
+
/** One segment route/layout handler execution (the component/handler that
|
|
138
|
+
* produces a segment). Span only — the perf metric (handler:<id>) is owned by
|
|
139
|
+
* the legacy track() at the same call site, so observePhase here adds the
|
|
140
|
+
* rango.handler span without double-recording. `id` is the segment id, carried
|
|
141
|
+
* as the rango.segment_id attribute to match the handler:<id> perf row. */
|
|
142
|
+
handler: (id: string): PhaseSpec => ({
|
|
143
|
+
metric: false,
|
|
144
|
+
tracePhase: "handler",
|
|
145
|
+
spanName: "rango.handler",
|
|
146
|
+
attributes: { "rango.segment_id": id },
|
|
147
|
+
}),
|
|
148
|
+
|
|
118
149
|
/** Whole render phase: match + serialize + SSR. The metric label is resolved
|
|
119
150
|
* lazily at record time (after match has set the route name) so the perf
|
|
120
151
|
* timeline shows WHICH route rendered: `render:total:<routeName>`, falling back
|
|
@@ -122,14 +153,20 @@ export const PHASES = {
|
|
|
122
153
|
render: {
|
|
123
154
|
metric: {
|
|
124
155
|
label: () => {
|
|
125
|
-
const routeName =
|
|
126
|
-
return routeName
|
|
127
|
-
? `render:total:${routeName}`
|
|
128
|
-
: "render:total";
|
|
156
|
+
const routeName = currentRouteName();
|
|
157
|
+
return routeName ? `render:total:${routeName}` : "render:total";
|
|
129
158
|
},
|
|
130
159
|
},
|
|
131
160
|
tracePhase: "render",
|
|
132
161
|
spanName: "rango.render",
|
|
162
|
+
// Tag the render span with the matched route so the Cloudflare/OTel waterfall
|
|
163
|
+
// shows WHICH route rendered (rango.render + rango.route=index), resolved
|
|
164
|
+
// after match has run. Kept an attribute (not baked into the span name) so the
|
|
165
|
+
// span name stays low-cardinality and aggregatable across routes.
|
|
166
|
+
lazyAttributes: () => {
|
|
167
|
+
const routeName = currentRouteName();
|
|
168
|
+
return routeName ? { "rango.route": routeName } : undefined;
|
|
169
|
+
},
|
|
133
170
|
} as PhaseSpec,
|
|
134
171
|
|
|
135
172
|
/** SSR HTML render from the RSC stream. Colon-delimited like the other ssr:*
|
|
@@ -313,6 +350,11 @@ function runDrainBoundPhase<R>(
|
|
|
313
350
|
reject(error);
|
|
314
351
|
throw error; // settle the span with the error, at construction
|
|
315
352
|
}
|
|
353
|
+
// Late attributes (e.g. rango.route) — resolved now that the work has run,
|
|
354
|
+
// so they can read state like the matched route name that match sets midway.
|
|
355
|
+
const lazy =
|
|
356
|
+
tracing && spec.lazyAttributes ? spec.lazyAttributes() : undefined;
|
|
357
|
+
if (lazy) applyAttributes(span, lazy);
|
|
316
358
|
record(); // construction-bound metric, before the response/header is built
|
|
317
359
|
deliver(onDeliver(value));
|
|
318
360
|
await drain; // hold the span open until the response body drains
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
import { applyViewTransitionDefault } from "./view-transition-default.js";
|
|
31
31
|
import { getRouterContext } from "../router-context.js";
|
|
32
32
|
import { observeStreamedHandler } from "./streamed-handler-telemetry.js";
|
|
33
|
+
import { observePhase, PHASES } from "../instrument.js";
|
|
33
34
|
import {
|
|
34
35
|
track,
|
|
35
36
|
RangoContext,
|
|
@@ -258,7 +259,9 @@ export async function resolveSegment<TEnv>(
|
|
|
258
259
|
!context.build && entry.liveHandler ? entry.liveHandler : entry.handler;
|
|
259
260
|
const doneRouteHandler = track(`handler:${entry.id}`, 2);
|
|
260
261
|
if (entry.loading) {
|
|
261
|
-
const result = handleHandlerResult(
|
|
262
|
+
const result = handleHandlerResult(
|
|
263
|
+
observePhase(PHASES.handler(entry.id), () => handler(context)),
|
|
264
|
+
);
|
|
262
265
|
if (result instanceof Promise) {
|
|
263
266
|
warnOnStreamedResponse(result, entry.id);
|
|
264
267
|
result.finally(doneRouteHandler).catch(() => {});
|
|
@@ -280,7 +283,9 @@ export async function resolveSegment<TEnv>(
|
|
|
280
283
|
component = result;
|
|
281
284
|
}
|
|
282
285
|
} else {
|
|
283
|
-
component = handleHandlerResult(
|
|
286
|
+
component = handleHandlerResult(
|
|
287
|
+
await observePhase(PHASES.handler(entry.id), () => handler(context)),
|
|
288
|
+
);
|
|
284
289
|
doneRouteHandler();
|
|
285
290
|
}
|
|
286
291
|
}
|
|
@@ -505,7 +510,11 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
505
510
|
parallelEntry.loading !== undefined && parallelEntry.loading !== false;
|
|
506
511
|
if (hasLoadingFallback) {
|
|
507
512
|
const result =
|
|
508
|
-
typeof handler === "function"
|
|
513
|
+
typeof handler === "function"
|
|
514
|
+
? observePhase(PHASES.handler(`${parallelEntry.id}.${slot}`), () =>
|
|
515
|
+
handler(context),
|
|
516
|
+
)
|
|
517
|
+
: handler;
|
|
509
518
|
if (result instanceof Promise) {
|
|
510
519
|
result.finally(doneParallelHandler).catch(() => {});
|
|
511
520
|
const tracked = deps.trackHandler(result, {
|
|
@@ -527,7 +536,12 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
527
536
|
}
|
|
528
537
|
} else {
|
|
529
538
|
component =
|
|
530
|
-
typeof handler === "function"
|
|
539
|
+
typeof handler === "function"
|
|
540
|
+
? await observePhase(
|
|
541
|
+
PHASES.handler(`${parallelEntry.id}.${slot}`),
|
|
542
|
+
() => handler(context),
|
|
543
|
+
)
|
|
544
|
+
: handler;
|
|
531
545
|
doneParallelHandler();
|
|
532
546
|
}
|
|
533
547
|
}
|
|
@@ -23,6 +23,7 @@ import type { ResolvedSegment, ErrorInfo, HandlerContext } from "../../types";
|
|
|
23
23
|
import type { SegmentResolutionDeps } from "../types.js";
|
|
24
24
|
import { debugLog } from "../logging.js";
|
|
25
25
|
import { tryStaticLookup } from "./static-store.js";
|
|
26
|
+
import { observePhase, PHASES } from "../instrument.js";
|
|
26
27
|
import type { TelemetrySink } from "../telemetry.js";
|
|
27
28
|
import { resolveSink, safeEmit, getRequestId } from "../telemetry.js";
|
|
28
29
|
|
|
@@ -130,11 +131,15 @@ export async function resolveLayoutComponent<TEnv>(
|
|
|
130
131
|
entry: EntryData,
|
|
131
132
|
context: HandlerContext<any, TEnv>,
|
|
132
133
|
): Promise<ReactNode> {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
return
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
// rango.handler span for this layout/cache handler (the perf metric is owned
|
|
135
|
+
// by the track("handler:<id>") at the call site; this adds the span only).
|
|
136
|
+
return observePhase(PHASES.handler(entry.id), async () => {
|
|
137
|
+
const component = await tryStaticHandler(entry, entry.shortCode);
|
|
138
|
+
if (component !== undefined) return component;
|
|
139
|
+
return typeof entry.handler === "function"
|
|
140
|
+
? handleHandlerResult(await entry.handler(context))
|
|
141
|
+
: (entry.handler as ReactNode);
|
|
142
|
+
});
|
|
138
143
|
}
|
|
139
144
|
|
|
140
145
|
// ---------------------------------------------------------------------------
|
|
@@ -43,7 +43,7 @@ import {
|
|
|
43
43
|
} from "./helpers.js";
|
|
44
44
|
import { applyViewTransitionDefault } from "./view-transition-default.js";
|
|
45
45
|
import { getRouterContext } from "../router-context.js";
|
|
46
|
-
import { observeEvent } from "../instrument.js";
|
|
46
|
+
import { observeEvent, observePhase, PHASES } from "../instrument.js";
|
|
47
47
|
import { observeStreamedHandler } from "./streamed-handler-telemetry.js";
|
|
48
48
|
import {
|
|
49
49
|
track,
|
|
@@ -793,12 +793,16 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
793
793
|
? routeEntry.liveHandler
|
|
794
794
|
: routeEntry.handler;
|
|
795
795
|
if (!routeEntry.loading) {
|
|
796
|
-
const result = handleHandlerResult(
|
|
796
|
+
const result = handleHandlerResult(
|
|
797
|
+
await observePhase(PHASES.handler(entry.id), () => handler(context)),
|
|
798
|
+
);
|
|
797
799
|
doneHandler();
|
|
798
800
|
return result;
|
|
799
801
|
}
|
|
800
802
|
if (!actionContext) {
|
|
801
|
-
const result = handleHandlerResult(
|
|
803
|
+
const result = handleHandlerResult(
|
|
804
|
+
observePhase(PHASES.handler(entry.id), () => handler(context)),
|
|
805
|
+
);
|
|
802
806
|
if (result instanceof Promise) {
|
|
803
807
|
warnOnStreamedResponse(result, routeEntry.id);
|
|
804
808
|
result.finally(doneHandler).catch(() => {});
|
|
@@ -822,7 +826,9 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
822
826
|
debugLog("segment.action", "resolving action route with awaited value", {
|
|
823
827
|
entryId: entry.id,
|
|
824
828
|
});
|
|
825
|
-
const actionResult = handleHandlerResult(
|
|
829
|
+
const actionResult = handleHandlerResult(
|
|
830
|
+
await observePhase(PHASES.handler(entry.id), () => handler(context)),
|
|
831
|
+
);
|
|
826
832
|
doneHandler();
|
|
827
833
|
return {
|
|
828
834
|
content: Promise.resolve(actionResult),
|
package/src/router/tracing.ts
CHANGED
|
@@ -26,7 +26,9 @@
|
|
|
26
26
|
* metered directly), rango.middleware (span-only incl. intercept middleware;
|
|
27
27
|
* pre/post metered directly), rango.action (action:<id>; server-action
|
|
28
28
|
* execution, JS + no-JS/PE), rango.loader (loader:<id>; single metering site at
|
|
29
|
-
* useLoader, plus the fetchable path), rango.
|
|
29
|
+
* useLoader, plus the fetchable path), rango.handler (span-only, one per segment
|
|
30
|
+
* route/layout handler execution; the handler:<id> perf metric is owned by the
|
|
31
|
+
* track() at the call site), rango.render (render:total:<route>; normal AND
|
|
30
32
|
* action-revalidation renders), rango.ssr (ssr:render-html).
|
|
31
33
|
*
|
|
32
34
|
* Streaming-phase span lifetime: a span ends when its callback's value (or
|
|
@@ -67,6 +69,7 @@ export type TracePhase =
|
|
|
67
69
|
| "middleware"
|
|
68
70
|
| "action"
|
|
69
71
|
| "loader"
|
|
72
|
+
| "handler"
|
|
70
73
|
| "render"
|
|
71
74
|
| "ssr";
|
|
72
75
|
|
|
@@ -76,6 +79,7 @@ export interface TracePhaseToggles {
|
|
|
76
79
|
middleware?: boolean;
|
|
77
80
|
action?: boolean;
|
|
78
81
|
loader?: boolean;
|
|
82
|
+
handler?: boolean;
|
|
79
83
|
render?: boolean;
|
|
80
84
|
ssr?: boolean;
|
|
81
85
|
}
|
|
@@ -112,6 +116,7 @@ const ALL_PHASES_ON: Record<TracePhase, boolean> = {
|
|
|
112
116
|
middleware: true,
|
|
113
117
|
action: true,
|
|
114
118
|
loader: true,
|
|
119
|
+
handler: true,
|
|
115
120
|
render: true,
|
|
116
121
|
ssr: true,
|
|
117
122
|
};
|
|
@@ -139,6 +144,7 @@ export function resolveTracing(
|
|
|
139
144
|
middleware: spans.middleware ?? true,
|
|
140
145
|
action: spans.action ?? true,
|
|
141
146
|
loader: spans.loader ?? true,
|
|
147
|
+
handler: spans.handler ?? true,
|
|
142
148
|
render: spans.render ?? true,
|
|
143
149
|
ssr: spans.ssr ?? true,
|
|
144
150
|
}
|