@uniflowed/router 0.0.0-alpha.32 → 0.0.0-alpha.34

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/index.js CHANGED
@@ -22,6 +22,12 @@
22
22
  // URL the page is, so one URL renders two subtrees at once, and
23
23
  // `$default.js` is what a slot renders when the URL matched none of its
24
24
  // routes. See ubugeeei-prod/uf#267.
25
+ //
26
+ // A directory named `(.)photo` inside a slot is an intercepting route: a client
27
+ // navigation that starts on a page the slot is on, and reaches the URL the
28
+ // directory stands in for, renders it in the slot and leaves the page
29
+ // underneath where it was. A document request for that URL — a reload, a
30
+ // shared link, a prerender — renders the ordinary page.
25
31
 
26
32
  import * as React from "react";
27
33
 
@@ -31,6 +37,7 @@ export type {
31
37
  AppProps,
32
38
  ErrorBoundary,
33
39
  ErrorModule,
40
+ Interception,
34
41
  JsonLd,
35
42
  LayoutModule,
36
43
  LinkPrefetch,
@@ -86,7 +86,18 @@
86
86
  import * as React from "react";
87
87
  import { useEffect, useState } from "react";
88
88
 
89
+ import { SYNTHESISED_SOURCE } from "./boundary-data.js";
89
90
  import { reportDiagnostic } from "./diagnostics.js";
91
+ import type { RouteBoundary } from "./boundary-data.js";
92
+
93
+ export type { BoundaryKind, RouteBoundary } from "./boundary-data.js";
94
+ export {
95
+ ROOT_ERROR_ID,
96
+ ROUTE_ERROR_ID,
97
+ SYNTHESISED_SOURCE,
98
+ routeBoundaries,
99
+ suspenseId,
100
+ } from "./boundary-data.js";
90
101
 
91
102
  /** The attribute a mark carries its boundary's id in. */
92
103
  export const BOUNDARY_ATTRIBUTE: string = "data-uf-boundary";
@@ -100,116 +111,6 @@ export const SOURCE_ATTRIBUTE: string = "data-uf-boundary-source";
100
111
  /** The name `uf dev` installs the on-demand report under. */
101
112
  export const BOUNDARY_GLOBAL: string = "__ufBoundaries";
102
113
 
103
- /**
104
- * What `file` says for the error boundary the build synthesises.
105
- *
106
- * `routesModuleSource` writes this string where a declared boundary has a path,
107
- * because the record has no module to name — the framework's own error page
108
- * renders in its place. Written out again rather than imported for the reason
109
- * `./devtools.js` gives about `DEVTOOLS_HOOK`: `@uniflowed/vite` is plain
110
- * JavaScript loaded by Vite before any Flow transform exists, so the import
111
- * cannot go either way. `boundaries.test.js` holds the two spellings together.
112
- */
113
- export const SYNTHESISED_SOURCE: string = "@uniflowed/router";
114
-
115
- /** Which kind of boundary a mark belongs to. */
116
- export type BoundaryKind = "suspense" | "error";
117
-
118
- /**
119
- * One boundary of a resolved route, as the marks and the report name it.
120
- *
121
- * `id` pairs the two marks and is stable for a boundary across renders, so a
122
- * navigation that keeps a boundary keeps its marks mounted. `above` is how many
123
- * of the route's layouts are outside it — the same number, spelled the same
124
- * way, that `ResolvedRoute["errorBoundary"].above` and every `loading` entry
125
- * carry, because there is no second vocabulary for where a thing sits in the
126
- * stack.
127
- *
128
- * `source` is the file it was declared in, and is `null` for every `<Suspense>`
129
- * boundary. That asymmetry is the route table's rather than this module's: an
130
- * error boundary is matched by path, so the table carries its `file`, while a
131
- * `$loading.js` is carried by depth alone. Adding a path to the loading
132
- * records would put one in every visitor's bundle to serve a report only
133
- * `uf dev` reads.
134
- */
135
- export type RouteBoundary = {|
136
- readonly id: string,
137
- readonly kind: BoundaryKind,
138
- readonly above: number,
139
- readonly source: ?string,
140
- |};
141
-
142
- /** The id of the `<Suspense>` boundary at `index` of a route's `loading`. */
143
- export function suspenseId(index: number): string {
144
- return `suspense:${index}`;
145
- }
146
-
147
- /** The id of the boundary a route's own `$error.js` renders. */
148
- export const ROUTE_ERROR_ID: string = "error:route";
149
-
150
- /**
151
- * The id of the boundary that stands outside every layout.
152
- *
153
- * It has no module and renders the framework's page; it is what is between a
154
- * throw in a root layout, or in the error component itself, and an unmounted
155
- * document. Marked like any other, because "which subtree does the last resort
156
- * own" is exactly as unanswerable from the page as the rest.
157
- */
158
- export const ROOT_ERROR_ID: string = "error:root";
159
-
160
- /** The part of a resolved route this module reads. */
161
- type BoundedRoute = {
162
- readonly errorBoundary: { readonly above: number, ... },
163
- readonly loading: $ReadOnlyArray<{ readonly above: number, ... }>,
164
- readonly error: mixed,
165
- ...
166
- };
167
-
168
- /**
169
- * Every boundary a resolved route renders, in the order they nest.
170
- *
171
- * A `Map` rather than a list because it is read both ways: `RouteView` asks for
172
- * one by id as it builds the stack, and the report walks the values. One
173
- * function answering both is the point — an id `RouteView` marks and the report
174
- * cannot find is a boundary that silently disappears from the map, and
175
- * ubugeeei-prod/uf#636 made the same argument about an explanation that can
176
- * disagree with the thing it explains.
177
- *
178
- * The route's own error boundary is absent when the route *is* its error page,
179
- * which is exactly when `RouteView` does not render one: wrapping that page in
180
- * the boundary whose component it is would answer a throw inside it with
181
- * itself.
182
- *
183
- * @param resolved the route being rendered
184
- * @param errorSource the `file` of the nearest `$error.js`, when the table
185
- * has one; `null` leaves the boundary named by its depth alone
186
- */
187
- export function routeBoundaries(
188
- resolved: BoundedRoute,
189
- errorSource: ?string,
190
- ): Map<string, RouteBoundary> {
191
- const found: Map<string, RouteBoundary> = new Map();
192
- found.set(ROOT_ERROR_ID, {
193
- id: ROOT_ERROR_ID,
194
- kind: "error",
195
- above: 0,
196
- source: SYNTHESISED_SOURCE,
197
- });
198
- if (resolved.error == null) {
199
- found.set(ROUTE_ERROR_ID, {
200
- id: ROUTE_ERROR_ID,
201
- kind: "error",
202
- above: resolved.errorBoundary.above,
203
- source: errorSource,
204
- });
205
- }
206
- resolved.loading.forEach((boundary, index) => {
207
- const id = suspenseId(index);
208
- found.set(id, { id, kind: "suspense", above: boundary.above, source: null });
209
- });
210
- return found;
211
- }
212
-
213
114
  /**
214
115
  * Whether an edge that mounts now should be in the DOM immediately.
215
116
  *
@@ -0,0 +1,88 @@
1
+ // @flow
2
+ //
3
+ // React-free names for the boundaries a resolved route renders.
4
+
5
+ /**
6
+ * What `file` says for the error boundary the build synthesises.
7
+ *
8
+ * `routesModuleSource` writes this string where a declared boundary has a path,
9
+ * because the record has no module to name: the framework's own error page
10
+ * renders in its place. This module is plain data so the routing surface and
11
+ * the dev-only DOM marker code can share one spelling without importing React.
12
+ */
13
+ export const SYNTHESISED_SOURCE: string = "@uniflowed/router";
14
+
15
+ /** Which kind of boundary a mark or payload row belongs to. */
16
+ export type BoundaryKind = "suspense" | "error";
17
+
18
+ /** The id of the `<Suspense>` boundary at `index` of a route's `loading`. */
19
+ export function suspenseId(index: number): string {
20
+ return `suspense:${index}`;
21
+ }
22
+
23
+ /** The id of the boundary a route's own `$error.js` renders. */
24
+ export const ROUTE_ERROR_ID: string = "error:route";
25
+
26
+ /**
27
+ * The id of the boundary that stands outside every layout.
28
+ *
29
+ * It has no module and renders the framework's page; it is what is between a
30
+ * throw in a root layout, or in the error component itself, and an unmounted
31
+ * document.
32
+ */
33
+ export const ROOT_ERROR_ID: string = "error:root";
34
+
35
+ /**
36
+ * One boundary of a resolved route, in uf's vocabulary rather than React's.
37
+ *
38
+ * `id` is the stable name `RouteView` marks in the DOM today and the name a
39
+ * future element payload can put beside the chunk that completed it. `above`
40
+ * is how many layouts are outside the boundary.
41
+ */
42
+ export type RouteBoundary = {|
43
+ readonly id: string,
44
+ readonly kind: BoundaryKind,
45
+ readonly above: number,
46
+ readonly source: ?string,
47
+ |};
48
+
49
+ /** The part of a resolved route the boundary map reads. */
50
+ type BoundedRoute = {
51
+ readonly errorBoundary: { readonly above: number, ... },
52
+ readonly loading: $ReadOnlyArray<{ readonly above: number, ... }>,
53
+ readonly error: mixed,
54
+ ...
55
+ };
56
+
57
+ /**
58
+ * Every boundary a resolved route renders, in the order they nest.
59
+ *
60
+ * A `Map` rather than a list because callers read it both ways: `RouteView`
61
+ * asks for one by id as it builds the stack, while diagnostics and payload
62
+ * summaries walk the values.
63
+ */
64
+ export function routeBoundaries(
65
+ resolved: BoundedRoute,
66
+ errorSource: ?string,
67
+ ): Map<string, RouteBoundary> {
68
+ const found: Map<string, RouteBoundary> = new Map();
69
+ found.set(ROOT_ERROR_ID, {
70
+ id: ROOT_ERROR_ID,
71
+ kind: "error",
72
+ above: 0,
73
+ source: SYNTHESISED_SOURCE,
74
+ });
75
+ if (resolved.error == null) {
76
+ found.set(ROUTE_ERROR_ID, {
77
+ id: ROUTE_ERROR_ID,
78
+ kind: "error",
79
+ above: resolved.errorBoundary.above,
80
+ source: errorSource,
81
+ });
82
+ }
83
+ resolved.loading.forEach((boundary, index) => {
84
+ const id = suspenseId(index);
85
+ found.set(id, { id, kind: "suspense", above: boundary.above, source: null });
86
+ });
87
+ return found;
88
+ }
@@ -27,7 +27,7 @@
27
27
  // lands this is the recorder it is fed to: `inspectStream` takes chunks and
28
28
  // knows nothing about who produced them.
29
29
  //
30
- // # It reads React's own markers, and no others
30
+ // # It reads React's markers, plus uf's row marker
31
31
  //
32
32
  // Fizz writes a suspended boundary into the shell as an empty
33
33
  // `<template id="B:0">` followed by the fallback and closed by a `<!--/$-->`
@@ -46,6 +46,11 @@
46
46
  // content of these chunks is the application's own — with a step limit on every
47
47
  // loop for the same reason `./boundaries.js` has a `WALK_LIMIT`.
48
48
  //
49
+ // The one uf-owned marker read here is `data-uf-row` on deferred payload row
50
+ // scripts. The inspector does not parse the row JSON or attach semantics to
51
+ // the value; it only keeps the terminal report from saying a row boundary built
52
+ // an anonymous `script`.
53
+ //
49
54
  // A marker that a chunk boundary happened to split is not attributed rather
50
55
  // than guessed at: the chunk is still counted, still timed, and says so. The
51
56
  // alternative is a report that is confidently wrong about which part of the
@@ -81,6 +86,8 @@
81
86
  // honest report is silence. That is also what keeps this from being a request
82
87
  // log: it fires for the pages the feature is about and for no others.
83
88
 
89
+ import { PAYLOAD_ROW_ATTRIBUTE } from "./payload.js";
90
+
84
91
  /** How many chunks of one document are recorded before the rest are counted. */
85
92
  const CHUNK_LIMIT = 64;
86
93
 
@@ -412,6 +419,10 @@ function topLevelElements(markup: string, from: number): $ReadOnlyArray<string>
412
419
  export function describeTag(tag: string): string {
413
420
  const space = tag.search(/[\s/>]/);
414
421
  const name = tag.slice(1, space === -1 ? tag.length : space).toLowerCase();
422
+ const row = name === "script" ? attribute(tag, PAYLOAD_ROW_ATTRIBUTE) : null;
423
+ if (row != null && row !== "") {
424
+ return `deferred payload row ${row}`;
425
+ }
415
426
  const id = attribute(tag, "id");
416
427
  if (id != null && id !== "") {
417
428
  return `${name}#${id}`;
@@ -0,0 +1,199 @@
1
+ // @flow
2
+ //
3
+ // Module-free summaries of resolved routes.
4
+
5
+ import { routeBoundaries, suspenseId } from "./boundary-data.js";
6
+ import type { RouteBoundary } from "./boundary-data.js";
7
+ import type { RouteParams, SearchParams } from "./routing.js";
8
+
9
+ type RouteStatus = 200 | 401 | 403 | 404 | 500;
10
+
11
+ type RouteMetadata = { readonly [string]: mixed, ... };
12
+
13
+ type RouteErrorLike = {
14
+ readonly kind: "thrown" | "unauthorized" | "forbidden",
15
+ ...
16
+ };
17
+
18
+ type BoundaryLike = {
19
+ readonly above: number,
20
+ ...
21
+ };
22
+
23
+ type ModuleBoundaryLike = {
24
+ readonly above: number,
25
+ readonly module?: mixed,
26
+ ...
27
+ };
28
+
29
+ type SlotLike = {
30
+ readonly name: string,
31
+ readonly above: number,
32
+ readonly page: mixed,
33
+ readonly params: RouteParams,
34
+ readonly layouts: $ReadOnlyArray<mixed>,
35
+ readonly loading: $ReadOnlyArray<BoundaryLike>,
36
+ readonly templates: $ReadOnlyArray<BoundaryLike>,
37
+ readonly errorBoundary: ?ModuleBoundaryLike,
38
+ readonly slots: $ReadOnlyArray<SlotLike>,
39
+ ...
40
+ };
41
+
42
+ type RouteLike = {
43
+ readonly pathname: string,
44
+ readonly search: string,
45
+ readonly path: string,
46
+ readonly params: RouteParams,
47
+ readonly searchParams: SearchParams,
48
+ readonly layouts: $ReadOnlyArray<mixed>,
49
+ readonly data: mixed,
50
+ readonly deferred: mixed,
51
+ readonly metadata: RouteMetadata,
52
+ readonly viewTransition: ?string,
53
+ readonly status: RouteStatus,
54
+ readonly error: ?RouteErrorLike,
55
+ readonly errorBoundary: ModuleBoundaryLike,
56
+ readonly loading: $ReadOnlyArray<BoundaryLike>,
57
+ readonly templates: $ReadOnlyArray<BoundaryLike>,
58
+ readonly slots: $ReadOnlyArray<SlotLike>,
59
+ ...
60
+ };
61
+
62
+ export type ResolvedBoundarySummary = {|
63
+ readonly id: string,
64
+ readonly above: number,
65
+ |};
66
+
67
+ export type ResolvedTemplateSummary = {|
68
+ readonly above: number,
69
+ |};
70
+
71
+ export type ResolvedRouteErrorSummary =
72
+ | {| readonly kind: "thrown" |}
73
+ | {| readonly kind: "unauthorized" |}
74
+ | {| readonly kind: "forbidden" |};
75
+
76
+ export type ResolvedErrorBoundarySummary = {|
77
+ readonly above: number,
78
+ readonly custom: boolean,
79
+ readonly rendered: boolean,
80
+ |};
81
+
82
+ export type ResolvedSlotSummary = {|
83
+ readonly name: string,
84
+ readonly above: number,
85
+ readonly active: boolean,
86
+ readonly params: RouteParams,
87
+ readonly layoutCount: number,
88
+ readonly loading: $ReadOnlyArray<ResolvedBoundarySummary>,
89
+ readonly templates: $ReadOnlyArray<ResolvedTemplateSummary>,
90
+ readonly errorBoundary: ?ResolvedErrorBoundarySummary,
91
+ readonly slots: $ReadOnlyArray<ResolvedSlotSummary>,
92
+ |};
93
+
94
+ export type ResolvedRouteSummary = {|
95
+ readonly pathname: string,
96
+ readonly search: string,
97
+ readonly path: string,
98
+ readonly params: RouteParams,
99
+ readonly searchParams: SearchParams,
100
+ readonly layoutCount: number,
101
+ readonly data: mixed,
102
+ readonly deferred: boolean,
103
+ readonly metadata: RouteMetadata,
104
+ readonly viewTransition: ?string,
105
+ readonly status: RouteStatus,
106
+ readonly error: ?ResolvedRouteErrorSummary,
107
+ readonly errorBoundary: ResolvedErrorBoundarySummary,
108
+ readonly loading: $ReadOnlyArray<ResolvedBoundarySummary>,
109
+ readonly templates: $ReadOnlyArray<ResolvedTemplateSummary>,
110
+ readonly slots: $ReadOnlyArray<ResolvedSlotSummary>,
111
+ readonly boundaries: $ReadOnlyArray<RouteBoundary>,
112
+ |};
113
+
114
+ /**
115
+ * The part of a `ResolvedRoute` that can cross to a payload or diagnostic.
116
+ *
117
+ * A resolved route carries loaded modules so `RouteView` can render today.
118
+ * The element payload in ubugeeei-prod/uf#519 needs the other half: route
119
+ * identity, data, metadata, status, and boundary names, with page/layout/
120
+ * fallback modules left behind in the renderer graph.
121
+ */
122
+ export function summarizeResolvedRoute(
123
+ route: RouteLike,
124
+ errorSource?: ?string,
125
+ ): ResolvedRouteSummary {
126
+ return {
127
+ pathname: route.pathname,
128
+ search: route.search,
129
+ path: route.path,
130
+ params: route.params,
131
+ searchParams: route.searchParams,
132
+ layoutCount: route.layouts.length,
133
+ data: route.data,
134
+ deferred: route.deferred != null,
135
+ metadata: route.metadata,
136
+ viewTransition: route.viewTransition,
137
+ status: route.status,
138
+ error: summarizeError(route.error),
139
+ errorBoundary: summarizeErrorBoundary(route.errorBoundary, route.error == null),
140
+ loading: summarizeLoading(route.loading),
141
+ templates: summarizeTemplates(route.templates),
142
+ slots: route.slots.map(summarizeSlot),
143
+ boundaries: [...routeBoundaries(route, errorSource).values()],
144
+ };
145
+ }
146
+
147
+ function summarizeSlot(slot: SlotLike): ResolvedSlotSummary {
148
+ return {
149
+ name: slot.name,
150
+ above: slot.above,
151
+ active: slot.page != null,
152
+ params: slot.params,
153
+ layoutCount: slot.layouts.length,
154
+ loading: summarizeLoading(slot.loading),
155
+ templates: summarizeTemplates(slot.templates),
156
+ errorBoundary:
157
+ slot.errorBoundary == null ? null : summarizeErrorBoundary(slot.errorBoundary, true),
158
+ slots: slot.slots.map(summarizeSlot),
159
+ };
160
+ }
161
+
162
+ function summarizeLoading(
163
+ boundaries: $ReadOnlyArray<BoundaryLike>,
164
+ ): $ReadOnlyArray<ResolvedBoundarySummary> {
165
+ return boundaries.map((boundary, index) => ({
166
+ id: suspenseId(index),
167
+ above: boundary.above,
168
+ }));
169
+ }
170
+
171
+ function summarizeTemplates(
172
+ templates: $ReadOnlyArray<BoundaryLike>,
173
+ ): $ReadOnlyArray<ResolvedTemplateSummary> {
174
+ return templates.map((template) => ({ above: template.above }));
175
+ }
176
+
177
+ function summarizeErrorBoundary(
178
+ boundary: ModuleBoundaryLike,
179
+ rendered: boolean,
180
+ ): ResolvedErrorBoundarySummary {
181
+ return {
182
+ above: boundary.above,
183
+ custom: boundary.module != null,
184
+ rendered,
185
+ };
186
+ }
187
+
188
+ function summarizeError(error: ?RouteErrorLike): ?ResolvedRouteErrorSummary {
189
+ if (error == null) {
190
+ return null;
191
+ }
192
+ if (error.kind === "unauthorized") {
193
+ return { kind: "unauthorized" };
194
+ }
195
+ if (error.kind === "forbidden") {
196
+ return { kind: "forbidden" };
197
+ }
198
+ return { kind: "thrown" };
199
+ }
@@ -31,29 +31,77 @@ export type LoadingRecord<TLoading = mixed> = {|
31
31
  readonly module: RouteModule<TLoading>,
32
32
  |};
33
33
 
34
+ /** One `$error.js` inside a slot, as the route table carries it. */
35
+ export type SlotErrorBoundaryRecord<TError = mixed> = {|
36
+ readonly above: number,
37
+ readonly module: RouteModule<TError>,
38
+ |};
39
+
34
40
  /** One parallel-route slot, as the route table carries it. */
35
- export type SlotRecord<TPage = mixed, TLayout = mixed> = {|
41
+ export type SlotRecord<
42
+ TPage = mixed,
43
+ TLayout = mixed,
44
+ TTemplate = mixed,
45
+ TLoading = mixed,
46
+ TError = mixed,
47
+ > = {|
36
48
  readonly name: string,
37
49
  readonly above: number,
38
50
  readonly defaultPage: ?RouteModule<TPage>,
39
51
  readonly defaultFile?: string,
40
52
  readonly defaultMdx?: boolean,
41
- readonly routes: $ReadOnlyArray<SlotRouteRecord<TPage, TLayout>>,
53
+ readonly defaultErrorBoundary?: ?SlotErrorBoundaryRecord<TError>,
54
+ readonly routes: $ReadOnlyArray<SlotRouteRecord<TPage, TLayout, TTemplate, TLoading, TError>>,
55
+ /**
56
+ * The routes this slot renders when a client navigation *intercepts* the URL
57
+ * each one names — `app/feed/@modal/(.)photo/[id]/$page.js` is the one for
58
+ * `/feed/photo/:id` — keyed by that URL.
59
+ *
60
+ * A second list rather than a flag on `routes`, because the two are read by
61
+ * different callers at different times and neither may see the other's.
62
+ * `routes` is matched against every URL the segment renders, by the server and
63
+ * by the browser alike. These are matched only by a navigation that starts on
64
+ * a page this slot is already rendered on, and nothing on a server reads them
65
+ * — which is the whole of why a document request for an intercepted URL
66
+ * renders the ordinary page. `resolveInterception`, beside the rest of the
67
+ * rendering, is the one reader.
68
+ *
69
+ * Absent on a slot that intercepts nothing, which is every slot a table
70
+ * written before interception existed.
71
+ */
72
+ readonly intercepts?: $ReadOnlyArray<
73
+ SlotRouteRecord<TPage, TLayout, TTemplate, TLoading, TError>,
74
+ >,
42
75
  |};
43
76
 
44
77
  /** One page inside a slot. */
45
- export type SlotRouteRecord<TPage = mixed, TLayout = mixed> = {|
78
+ export type SlotRouteRecord<
79
+ TPage = mixed,
80
+ TLayout = mixed,
81
+ TTemplate = mixed,
82
+ TLoading = mixed,
83
+ TError = mixed,
84
+ > = {|
46
85
  readonly path: string,
47
86
  readonly params: $ReadOnlyArray<RouteParamSpec>,
48
87
  readonly mdx: boolean,
49
88
  readonly file: string,
50
89
  readonly page: RouteModule<TPage>,
51
90
  readonly layouts: $ReadOnlyArray<RouteModule<TLayout>>,
52
- readonly slots: $ReadOnlyArray<SlotRecord<TPage, TLayout>>,
91
+ readonly loading?: $ReadOnlyArray<LoadingRecord<TLoading>>,
92
+ readonly templates?: $ReadOnlyArray<TemplateRecord<TTemplate>>,
93
+ readonly errorBoundary?: ?SlotErrorBoundaryRecord<TError>,
94
+ readonly slots: $ReadOnlyArray<SlotRecord<TPage, TLayout, TTemplate, TLoading, TError>>,
53
95
  |};
54
96
 
55
97
  /** One entry of the generated route table. */
56
- export type RouteRecord<TPage = mixed, TLayout = mixed, TTemplate = mixed, TLoading = mixed> = {|
98
+ export type RouteRecord<
99
+ TPage = mixed,
100
+ TLayout = mixed,
101
+ TTemplate = mixed,
102
+ TLoading = mixed,
103
+ TError = mixed,
104
+ > = {|
57
105
  readonly path: string,
58
106
  readonly params: $ReadOnlyArray<RouteParamSpec>,
59
107
  readonly mdx: boolean,
@@ -62,7 +110,7 @@ export type RouteRecord<TPage = mixed, TLayout = mixed, TTemplate = mixed, TLoad
62
110
  readonly layouts: $ReadOnlyArray<RouteModule<TLayout>>,
63
111
  readonly loading?: $ReadOnlyArray<LoadingRecord<TLoading>>,
64
112
  readonly templates?: $ReadOnlyArray<TemplateRecord<TTemplate>>,
65
- readonly slots?: $ReadOnlyArray<SlotRecord<TPage, TLayout>>,
113
+ readonly slots?: $ReadOnlyArray<SlotRecord<TPage, TLayout, TTemplate, TLoading, TError>>,
66
114
  |};
67
115
 
68
116
  /** One not-found boundary: the page for a path under `path` that matched nothing. */
@@ -90,12 +138,12 @@ export type RouteTable<
90
138
  TLoading = mixed,
91
139
  TError = mixed,
92
140
  > = {|
93
- readonly routes: $ReadOnlyArray<RouteRecord<TPage, TLayout, TTemplate, TLoading>>,
141
+ readonly routes: $ReadOnlyArray<RouteRecord<TPage, TLayout, TTemplate, TLoading, TError>>,
94
142
  readonly notFound: $ReadOnlyArray<NotFoundBoundary<TPage, TLayout>>,
95
143
  readonly errors: $ReadOnlyArray<ErrorBoundary<TError, TLayout>>,
96
144
  |};
97
145
 
98
- type UnknownRouteRecord = RouteRecord<mixed, mixed, mixed, mixed>;
146
+ type UnknownRouteRecord = RouteRecord<mixed, mixed, mixed, mixed, mixed>;
99
147
 
100
148
  /** A URL matched against a table. */
101
149
  export type RouteMatch<TRoute: { +path: string, ... } = UnknownRouteRecord> = {|