@rangojs/router 0.0.0-experimental.55 → 0.0.0-experimental.56

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.
Files changed (46) hide show
  1. package/dist/bin/rango.js +128 -46
  2. package/dist/vite/index.js +119 -43
  3. package/package.json +1 -1
  4. package/skills/links/SKILL.md +3 -1
  5. package/skills/middleware/SKILL.md +2 -0
  6. package/skills/router-setup/SKILL.md +35 -0
  7. package/src/browser/navigation-bridge.ts +6 -0
  8. package/src/browser/navigation-client.ts +4 -0
  9. package/src/browser/navigation-store.ts +43 -8
  10. package/src/browser/partial-update.ts +17 -1
  11. package/src/browser/prefetch/fetch.ts +8 -2
  12. package/src/browser/react/Link.tsx +43 -8
  13. package/src/browser/react/NavigationProvider.tsx +7 -0
  14. package/src/browser/react/context.ts +6 -0
  15. package/src/browser/react/use-router.ts +20 -8
  16. package/src/browser/rsc-router.tsx +8 -0
  17. package/src/browser/server-action-bridge.ts +5 -0
  18. package/src/browser/types.ts +18 -4
  19. package/src/build/generate-manifest.ts +3 -0
  20. package/src/build/generate-route-types.ts +3 -0
  21. package/src/build/route-types/include-resolution.ts +8 -1
  22. package/src/build/route-types/router-processing.ts +211 -72
  23. package/src/route-definition/redirect.ts +9 -1
  24. package/src/router/handler-context.ts +5 -9
  25. package/src/router/intercept-resolution.ts +6 -2
  26. package/src/router/loader-resolution.ts +3 -2
  27. package/src/router/middleware-types.ts +0 -6
  28. package/src/router/middleware.ts +0 -3
  29. package/src/router/prerender-match.ts +2 -2
  30. package/src/router/router-interfaces.ts +25 -4
  31. package/src/router/router-options.ts +37 -11
  32. package/src/router.ts +40 -4
  33. package/src/rsc/handler.ts +10 -1
  34. package/src/rsc/manifest-init.ts +5 -1
  35. package/src/rsc/progressive-enhancement.ts +4 -0
  36. package/src/rsc/rsc-rendering.ts +5 -0
  37. package/src/rsc/server-action.ts +2 -0
  38. package/src/rsc/ssr-setup.ts +1 -1
  39. package/src/rsc/types.ts +5 -0
  40. package/src/server/request-context.ts +8 -4
  41. package/src/ssr/index.tsx +3 -0
  42. package/src/types/cache-types.ts +4 -4
  43. package/src/types/handler-context.ts +5 -9
  44. package/src/types/loader-types.ts +0 -1
  45. package/src/urls/pattern-types.ts +12 -0
  46. package/src/vite/discovery/discover-routers.ts +5 -1
@@ -28,9 +28,15 @@ const DEFAULT_ACTION_STATE: TrackedActionState = {
28
28
  // Maximum number of history entries to cache (URLs visited)
29
29
  const HISTORY_CACHE_SIZE = 20;
30
30
 
31
- // Cache entry: [url-key, segments, stale, handleData?]
31
+ // Cache entry: [url-key, segments, stale, handleData?, routerId?]
32
32
  // stale=true means the data may be outdated and should be revalidated on access
33
- type HistoryCacheEntry = [string, ResolvedSegment[], boolean, HandleData?];
33
+ type HistoryCacheEntry = [
34
+ string,
35
+ ResolvedSegment[],
36
+ boolean,
37
+ HandleData?,
38
+ string?,
39
+ ];
34
40
 
35
41
  /**
36
42
  * Shallow clone handleData to avoid reference sharing between cache entries.
@@ -258,6 +264,11 @@ export function createNavigationStore(
258
264
  // Used to maintain intercept context during action revalidation
259
265
  let interceptSourceUrl: string | null = null;
260
266
 
267
+ // Router identity - tracks which router is currently active.
268
+ // When this changes on a partial response, the client forces a full
269
+ // tree replacement instead of reconciling with stale segments.
270
+ let currentRouterId: string | undefined;
271
+
261
272
  // Action state tracking (for useAction hook)
262
273
  // Maps action function ID to its tracked state
263
274
  const actionStates = new Map<string, TrackedActionState>();
@@ -571,10 +582,17 @@ export function createNavigationStore(
571
582
  segments,
572
583
  false,
573
584
  clonedHandleData,
585
+ currentRouterId,
574
586
  ];
575
587
  } else {
576
588
  // Add new entry at the end (not stale)
577
- historyCache.push([historyKey, segments, false, clonedHandleData]);
589
+ historyCache.push([
590
+ historyKey,
591
+ segments,
592
+ false,
593
+ clonedHandleData,
594
+ currentRouterId,
595
+ ]);
578
596
  // Remove oldest entries if over limit
579
597
  while (historyCache.length > cacheSize) {
580
598
  historyCache.shift();
@@ -586,14 +604,22 @@ export function createNavigationStore(
586
604
  * Get cached segments for a history entry
587
605
  * Returns { segments, stale, handleData } or undefined if not cached
588
606
  */
589
- getCachedSegments(
590
- historyKey: string,
591
- ):
592
- | { segments: ResolvedSegment[]; stale: boolean; handleData?: HandleData }
607
+ getCachedSegments(historyKey: string):
608
+ | {
609
+ segments: ResolvedSegment[];
610
+ stale: boolean;
611
+ handleData?: HandleData;
612
+ routerId?: string;
613
+ }
593
614
  | undefined {
594
615
  const entry = historyCache.find(([key]) => key === historyKey);
595
616
  if (!entry) return undefined;
596
- return { segments: entry[1], stale: entry[2], handleData: entry[3] };
617
+ return {
618
+ segments: entry[1],
619
+ stale: entry[2],
620
+ handleData: entry[3],
621
+ routerId: entry[4],
622
+ };
597
623
  },
598
624
 
599
625
  /**
@@ -621,6 +647,7 @@ export function createNavigationStore(
621
647
  entry[1],
622
648
  entry[2],
623
649
  clonedHandleData,
650
+ entry[4], // preserve routerId
624
651
  ];
625
652
  }
626
653
  },
@@ -687,6 +714,14 @@ export function createNavigationStore(
687
714
  interceptSourceUrl = url;
688
715
  },
689
716
 
717
+ getRouterId(): string | undefined {
718
+ return currentRouterId;
719
+ },
720
+
721
+ setRouterId(id: string): void {
722
+ currentRouterId = id;
723
+ },
724
+
690
725
  // ========================================================================
691
726
  // UI Update Notifications
692
727
  // ========================================================================
@@ -200,6 +200,7 @@ export function createPartialUpdater(
200
200
  staleRevalidation:
201
201
  mode.type === "stale-revalidation" || segments.length === 0,
202
202
  version: getVersion(),
203
+ routerId: store.getRouterId?.(),
203
204
  });
204
205
  // Mark navigation as streaming (response received, now parsing RSC).
205
206
  // Called after fetchPartial so pendingUrl stays set during the network wait,
@@ -212,6 +213,21 @@ export function createPartialUpdater(
212
213
  streamingToken.end();
213
214
  });
214
215
 
216
+ // Detect app switch: if routerId changed, the navigation crossed into
217
+ // a different router (e.g., via host router path mount). Downgrade
218
+ // partial to full so the entire tree is replaced without reconciliation
219
+ // against stale segments from the previous app.
220
+ if (payload.metadata?.routerId) {
221
+ const prevRouterId = store.getRouterId?.();
222
+ if (prevRouterId && prevRouterId !== payload.metadata.routerId) {
223
+ debugLog(
224
+ `[Browser] App switch detected (${prevRouterId} → ${payload.metadata.routerId}), forcing full update`,
225
+ );
226
+ payload.metadata.isPartial = false;
227
+ }
228
+ store.setRouterId?.(payload.metadata.routerId);
229
+ }
230
+
215
231
  // Handle server-side redirect with state
216
232
  if (payload.metadata?.redirect) {
217
233
  if (signal?.aborted) {
@@ -265,7 +281,7 @@ export function createPartialUpdater(
265
281
  existingSegments,
266
282
  );
267
283
 
268
- // Fix: tx.commit() cached the source page's handleData because
284
+ // tx.commit() cached the source page's handleData because
269
285
  // eventController hasn't been updated yet. Overwrite with the
270
286
  // correct cached handleData to prevent cache corruption on
271
287
  // subsequent navigations to this same URL.
@@ -34,6 +34,7 @@ function buildPrefetchUrl(
34
34
  url: string,
35
35
  segmentIds: string[],
36
36
  version?: string,
37
+ routerId?: string,
37
38
  ): URL | null {
38
39
  let targetUrl: URL;
39
40
  try {
@@ -51,6 +52,9 @@ function buildPrefetchUrl(
51
52
  if (version) {
52
53
  targetUrl.searchParams.set("_rsc_v", version);
53
54
  }
55
+ if (routerId) {
56
+ targetUrl.searchParams.set("_rsc_rid", routerId);
57
+ }
54
58
  return targetUrl;
55
59
  }
56
60
 
@@ -108,10 +112,11 @@ export function prefetchDirect(
108
112
  url: string,
109
113
  segmentIds: string[],
110
114
  version?: string,
115
+ routerId?: string,
111
116
  ): void {
112
117
  if (!shouldPrefetch()) return;
113
118
 
114
- const targetUrl = buildPrefetchUrl(url, segmentIds, version);
119
+ const targetUrl = buildPrefetchUrl(url, segmentIds, version, routerId);
115
120
  if (!targetUrl) return;
116
121
  const key = buildPrefetchKey(window.location.href, targetUrl);
117
122
  if (hasPrefetch(key)) return;
@@ -127,9 +132,10 @@ export function prefetchQueued(
127
132
  url: string,
128
133
  segmentIds: string[],
129
134
  version?: string,
135
+ routerId?: string,
130
136
  ): string {
131
137
  if (!shouldPrefetch()) return "";
132
- const targetUrl = buildPrefetchUrl(url, segmentIds, version);
138
+ const targetUrl = buildPrefetchUrl(url, segmentIds, version, routerId);
133
139
  if (!targetUrl) return "";
134
140
  const key = buildPrefetchKey(window.location.href, targetUrl);
135
141
  if (hasPrefetch(key)) return key;
@@ -5,6 +5,7 @@ import React, {
5
5
  useCallback,
6
6
  useContext,
7
7
  useEffect,
8
+ useMemo,
8
9
  useRef,
9
10
  type ForwardRefExoticComponent,
10
11
  type RefAttributes,
@@ -193,6 +194,16 @@ export const Link: ForwardRefExoticComponent<
193
194
  const ctx = useContext(NavigationStoreContext);
194
195
  const isExternal = isExternalUrl(to);
195
196
 
197
+ // Auto-prefix with basename for app-local paths.
198
+ // Skip if external, already prefixed, or not a root-relative path.
199
+ const resolvedTo = useMemo(() => {
200
+ if (isExternal) return to;
201
+ const bn = ctx?.basename;
202
+ if (!bn || !to.startsWith("/") || to.startsWith(bn + "/") || to === bn)
203
+ return to;
204
+ return to === "/" ? bn : bn + to;
205
+ }, [to, isExternal, ctx?.basename]);
206
+
196
207
  // Resolve adaptive: viewport on touch devices, hover on pointer devices
197
208
  const resolvedStrategy =
198
209
  prefetch === "adaptive" ? (isTouchDevice ? "viewport" : "hover") : prefetch;
@@ -274,9 +285,23 @@ export const Link: ForwardRefExoticComponent<
274
285
  resolvedState = currentState;
275
286
  }
276
287
 
277
- ctx.navigate(to, { replace, scroll, state: resolvedState, revalidate });
288
+ ctx.navigate(resolvedTo, {
289
+ replace,
290
+ scroll,
291
+ state: resolvedState,
292
+ revalidate,
293
+ });
278
294
  },
279
- [to, isExternal, reloadDocument, replace, scroll, revalidate, ctx, onClick],
295
+ [
296
+ resolvedTo,
297
+ isExternal,
298
+ reloadDocument,
299
+ replace,
300
+ scroll,
301
+ revalidate,
302
+ ctx,
303
+ onClick,
304
+ ],
280
305
  );
281
306
 
282
307
  const handleMouseEnter = useCallback(() => {
@@ -290,9 +315,14 @@ export const Link: ForwardRefExoticComponent<
290
315
  // prefetch — prefetchDirect bypasses the queue, and hasPrefetch
291
316
  // deduplicates if the viewport prefetch already completed.
292
317
  const segmentState = ctx.store.getSegmentState();
293
- prefetchDirect(to, segmentState.currentSegmentIds, getAppVersion());
318
+ prefetchDirect(
319
+ resolvedTo,
320
+ segmentState.currentSegmentIds,
321
+ getAppVersion(),
322
+ ctx.store.getRouterId?.(),
323
+ );
294
324
  }
295
- }, [resolvedStrategy, to, isExternal, ctx]);
325
+ }, [resolvedStrategy, resolvedTo, isExternal, ctx]);
296
326
 
297
327
  // Viewport/render prefetch: waits for idle before starting,
298
328
  // uses concurrency-limited queue to avoid flooding.
@@ -309,7 +339,12 @@ export const Link: ForwardRefExoticComponent<
309
339
  const triggerPrefetch = () => {
310
340
  if (cancelled) return;
311
341
  const segmentState = ctx.store.getSegmentState();
312
- prefetchQueued(to, segmentState.currentSegmentIds, getAppVersion());
342
+ prefetchQueued(
343
+ resolvedTo,
344
+ segmentState.currentSegmentIds,
345
+ getAppVersion(),
346
+ ctx.store.getRouterId?.(),
347
+ );
313
348
  };
314
349
 
315
350
  // Schedule prefetch only when the app is idle (no navigation/streaming).
@@ -348,12 +383,12 @@ export const Link: ForwardRefExoticComponent<
348
383
  unobserveForPrefetch(observedElement);
349
384
  }
350
385
  };
351
- }, [resolvedStrategy, to, isExternal, ctx]);
386
+ }, [resolvedStrategy, resolvedTo, isExternal, ctx]);
352
387
 
353
388
  return (
354
389
  <a
355
390
  ref={setRef}
356
- href={to}
391
+ href={resolvedTo}
357
392
  onClick={handleClick}
358
393
  onMouseEnter={handleMouseEnter}
359
394
  data-link-component
@@ -363,7 +398,7 @@ export const Link: ForwardRefExoticComponent<
363
398
  data-revalidate={revalidate === false ? "false" : undefined}
364
399
  {...props}
365
400
  >
366
- <LinkContext.Provider value={to}>{children}</LinkContext.Provider>
401
+ <LinkContext.Provider value={resolvedTo}>{children}</LinkContext.Provider>
367
402
  </a>
368
403
  );
369
404
  });
@@ -137,6 +137,11 @@ export interface NavigationProviderProps {
137
137
  * Forwarded to context for cache key building.
138
138
  */
139
139
  version?: string;
140
+
141
+ /**
142
+ * URL prefix for all routes (from createRouter({ basename })).
143
+ */
144
+ basename?: string;
140
145
  }
141
146
 
142
147
  /**
@@ -169,6 +174,7 @@ export function NavigationProvider({
169
174
  initialTheme,
170
175
  warmupEnabled,
171
176
  version,
177
+ basename,
172
178
  }: NavigationProviderProps): ReactNode {
173
179
  // Track current payload for rendering (this triggers re-renders)
174
180
  const [payload, setPayload] = useState(initialPayload);
@@ -198,6 +204,7 @@ export function NavigationProvider({
198
204
  navigate,
199
205
  refresh,
200
206
  version,
207
+ basename,
201
208
  }),
202
209
  [],
203
210
  );
@@ -46,6 +46,12 @@ export interface NavigationStoreContextValue {
46
46
  * App version from the initial server payload.
47
47
  */
48
48
  version: string | undefined;
49
+
50
+ /**
51
+ * URL prefix for all routes (from createRouter({ basename })).
52
+ * Used by Link and useRouter() to auto-prefix app-local paths.
53
+ */
54
+ basename: string | undefined;
49
55
  }
50
56
 
51
57
  /**
@@ -30,14 +30,22 @@ export function useRouter(): RouterInstance {
30
30
  }
31
31
 
32
32
  // Stable reference: ctx is itself stable (NavigationProvider memoizes with [])
33
- return useMemo<RouterInstance>(
34
- () => ({
33
+ return useMemo<RouterInstance>(() => {
34
+ /** Prefix a root-relative path with basename if not already prefixed. */
35
+ function withBasename(url: string): string {
36
+ const bn = ctx!.basename;
37
+ if (!bn || !url.startsWith("/") || url.startsWith(bn + "/") || url === bn)
38
+ return url;
39
+ return url === "/" ? bn : bn + url;
40
+ }
41
+
42
+ return {
35
43
  push(url: string, options?: RouterNavigateOptions): Promise<void> {
36
- return ctx.navigate(url, { ...options, replace: false });
44
+ return ctx.navigate(withBasename(url), { ...options, replace: false });
37
45
  },
38
46
 
39
47
  replace(url: string, options?: RouterNavigateOptions): Promise<void> {
40
- return ctx.navigate(url, { ...options, replace: true });
48
+ return ctx.navigate(withBasename(url), { ...options, replace: true });
41
49
  },
42
50
 
43
51
  refresh(): Promise<void> {
@@ -47,7 +55,12 @@ export function useRouter(): RouterInstance {
47
55
  prefetch(url: string): void {
48
56
  const segmentState = ctx.store?.getSegmentState();
49
57
  if (segmentState) {
50
- prefetchDirect(url, segmentState.currentSegmentIds, getAppVersion());
58
+ prefetchDirect(
59
+ withBasename(url),
60
+ segmentState.currentSegmentIds,
61
+ getAppVersion(),
62
+ ctx.store?.getRouterId?.(),
63
+ );
51
64
  }
52
65
  },
53
66
 
@@ -58,7 +71,6 @@ export function useRouter(): RouterInstance {
58
71
  forward(): void {
59
72
  window.history.forward();
60
73
  },
61
- }),
62
- [],
63
- );
74
+ };
75
+ }, []);
64
76
  }
@@ -164,6 +164,12 @@ export async function initBrowserApp(
164
164
  ...(storeOptions?.cacheSize && { cacheSize: storeOptions.cacheSize }),
165
165
  });
166
166
 
167
+ // Seed router identity from the initial SSR payload so the first
168
+ // cross-app SPA navigation can detect the app switch.
169
+ if (initialPayload.metadata?.routerId) {
170
+ store.setRouterId?.(initialPayload.metadata.routerId);
171
+ }
172
+
167
173
  // Create event controller for reactive state management
168
174
  const eventController = createEventController({
169
175
  initialLocation: new URL(window.location.href),
@@ -316,6 +322,7 @@ export async function initBrowserApp(
316
322
  segmentIds: [],
317
323
  previousUrl: store.getSegmentState().currentUrl,
318
324
  interceptSourceUrl: interceptSourceUrl || undefined,
325
+ routerId: store.getRouterId?.(),
319
326
  hmr: true,
320
327
  signal: abort.signal,
321
328
  });
@@ -493,6 +500,7 @@ export function RSCRouter(_props: RSCRouterProps): React.ReactElement {
493
500
  initialTheme={initialTheme}
494
501
  warmupEnabled={warmupEnabled}
495
502
  version={version}
503
+ basename={initialPayload.metadata?.basename}
496
504
  />
497
505
  );
498
506
  }
@@ -167,6 +167,11 @@ export function createServerActionBridge(
167
167
  if (version) {
168
168
  url.searchParams.set("_rsc_v", version);
169
169
  }
170
+ // Add router ID for app switch detection
171
+ const rid = store.getRouterId?.();
172
+ if (rid) {
173
+ url.searchParams.set("_rsc_rid", rid);
174
+ }
170
175
 
171
176
  // Encode arguments
172
177
  const encodedBody = await deps.encodeReply(args, { temporaryReferences });
@@ -32,6 +32,9 @@ export type HandleData = Record<string, Record<string, unknown[]>>;
32
32
  export interface RscMetadata {
33
33
  pathname: string;
34
34
  segments: ResolvedSegment[];
35
+ /** Router instance ID. When this changes between navigations, the client
36
+ * forces a full tree replacement (app switch via host router). */
37
+ routerId?: string;
35
38
  isPartial?: boolean;
36
39
  isError?: boolean;
37
40
  matched?: string[];
@@ -70,6 +73,8 @@ export interface RscMetadata {
70
73
  * Included when theme is enabled in router config.
71
74
  */
72
75
  initialTheme?: Theme;
76
+ /** URL prefix for all routes (from createRouter({ basename })). */
77
+ basename?: string;
73
78
  /** Whether connection warmup is enabled */
74
79
  warmupEnabled?: boolean;
75
80
  /** Server-side redirect with optional state (for partial requests) */
@@ -409,10 +414,13 @@ export interface NavigationStore {
409
414
  segments: ResolvedSegment[],
410
415
  handleData?: HandleData,
411
416
  ): void;
412
- getCachedSegments(
413
- historyKey: string,
414
- ):
415
- | { segments: ResolvedSegment[]; stale: boolean; handleData?: HandleData }
417
+ getCachedSegments(historyKey: string):
418
+ | {
419
+ segments: ResolvedSegment[];
420
+ stale: boolean;
421
+ handleData?: HandleData;
422
+ routerId?: string;
423
+ }
416
424
  | undefined;
417
425
  hasHistoryCache(historyKey: string): boolean;
418
426
  updateCacheHandleData(historyKey: string, handleData: HandleData): void;
@@ -428,6 +436,10 @@ export interface NavigationStore {
428
436
  getInterceptSourceUrl(): string | null;
429
437
  setInterceptSourceUrl(url: string | null): void;
430
438
 
439
+ // Router identity tracking (for cross-app navigation detection)
440
+ getRouterId?(): string | undefined;
441
+ setRouterId?(id: string): void;
442
+
431
443
  // UI update notifications
432
444
  onUpdate(callback: UpdateSubscriber): () => void;
433
445
  emitUpdate(update: NavigationUpdate): void;
@@ -458,6 +470,8 @@ export interface FetchPartialOptions {
458
470
  interceptSourceUrl?: string;
459
471
  /** RSC version for cache invalidation detection */
460
472
  version?: string;
473
+ /** Current router ID — server detects app switch and returns full response */
474
+ routerId?: string;
461
475
  /** If true, this is an HMR refetch - server should invalidate manifest cache */
462
476
  hmr?: boolean;
463
477
  }
@@ -285,6 +285,7 @@ export function generateManifest<TEnv>(
285
285
  export function generateManifestFull<TEnv>(
286
286
  urlpatterns: UrlPatterns<TEnv, any>,
287
287
  mountIndex: number = 0,
288
+ options?: { urlPrefix?: string },
288
289
  ): FullManifest {
289
290
  const routeManifest: Record<string, string> = {};
290
291
  const routeAncestry: Record<string, string[]> = {};
@@ -310,6 +311,8 @@ export function generateManifestFull<TEnv>(
310
311
  counters: {},
311
312
  mountIndex,
312
313
  trackedIncludes, // Enable include tracking
314
+ // basename sets the initial URL prefix for all path() registrations
315
+ ...(options?.urlPrefix ? { urlPrefix: options.urlPrefix } : {}),
313
316
  },
314
317
  () => {
315
318
  const helpers = createRouteHelpers();
@@ -25,6 +25,9 @@ export {
25
25
  } from "./route-types/include-resolution.js";
26
26
  export {
27
27
  extractUrlsVariableFromRouter,
28
+ extractUrlsFromRouter,
29
+ extractBasenameFromRouter,
30
+ type UrlsExtractionResult,
28
31
  buildCombinedRouteMapForRouterFile,
29
32
  detectUnresolvableIncludes,
30
33
  detectUnresolvableIncludesForUrlsFile,
@@ -357,12 +357,17 @@ function buildRouteMapFromBlock(
357
357
  /**
358
358
  * Build route map and search schemas together.
359
359
  * Internal helper used by the include resolution path.
360
+ *
361
+ * @param inlineBlock - Optional pre-extracted code block (e.g. from an inline
362
+ * builder function). When provided, variableName is ignored and the block
363
+ * is parsed directly for path()/include() calls.
360
364
  */
361
365
  export function buildCombinedRouteMapWithSearch(
362
366
  filePath: string,
363
367
  variableName?: string,
364
368
  visited?: Set<string>,
365
369
  diagnosticsOut?: UnresolvableInclude[],
370
+ inlineBlock?: string,
366
371
  ): {
367
372
  routes: Record<string, string>;
368
373
  searchSchemas: Record<string, Record<string, string>>;
@@ -384,7 +389,9 @@ export function buildCombinedRouteMapWithSearch(
384
389
  }
385
390
 
386
391
  let block: string;
387
- if (variableName) {
392
+ if (inlineBlock) {
393
+ block = inlineBlock;
394
+ } else if (variableName) {
388
395
  const extracted = extractUrlsBlockForVariable(source, variableName);
389
396
  if (!extracted) return { routes: {}, searchSchemas: {} };
390
397
  block = extracted;