@rsc-kit/core 0.18.0 → 0.19.0

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 (73) hide show
  1. package/dist/action.d.ts +16 -2
  2. package/dist/action.js +21 -12
  3. package/dist/action.js.map +1 -1
  4. package/dist/apiPrerender.js +24 -6
  5. package/dist/apiPrerender.js.map +1 -1
  6. package/dist/clientPackages.d.ts +30 -0
  7. package/dist/clientPackages.js +234 -0
  8. package/dist/clientPackages.js.map +1 -0
  9. package/dist/compress.d.ts +12 -0
  10. package/dist/compress.js +134 -0
  11. package/dist/compress.js.map +1 -0
  12. package/dist/compressRuntime.d.ts +8 -0
  13. package/dist/compressRuntime.js +62 -0
  14. package/dist/compressRuntime.js.map +1 -0
  15. package/dist/files.d.ts +20 -0
  16. package/dist/files.js +38 -0
  17. package/dist/files.js.map +1 -1
  18. package/dist/formSubmit.d.ts +1 -0
  19. package/dist/formSubmit.js +14 -0
  20. package/dist/formSubmit.js.map +1 -0
  21. package/dist/host.d.ts +23 -0
  22. package/dist/host.js +175 -31
  23. package/dist/host.js.map +1 -1
  24. package/dist/hostCalls.d.ts +15 -0
  25. package/dist/hostCalls.js +75 -8
  26. package/dist/hostCalls.js.map +1 -1
  27. package/dist/js/Form.d.ts +19 -2
  28. package/dist/js/Form.js +110 -83
  29. package/dist/js/Form.js.map +1 -1
  30. package/dist/js/LoadingBoundary.d.ts +5 -0
  31. package/dist/js/LoadingBoundary.js +19 -0
  32. package/dist/js/LoadingBoundary.js.map +1 -0
  33. package/dist/js/createViteRscApp.d.ts +1 -0
  34. package/dist/js/createViteRscApp.js +34 -5
  35. package/dist/js/createViteRscApp.js.map +1 -1
  36. package/dist/js/errors.d.ts +3 -1
  37. package/dist/js/errors.js +26 -3
  38. package/dist/js/errors.js.map +1 -1
  39. package/dist/js/fallbackReport.js +10 -8
  40. package/dist/js/fallbackReport.js.map +1 -1
  41. package/dist/js/formEncoding.d.ts +72 -3
  42. package/dist/js/formEncoding.js +284 -20
  43. package/dist/js/formEncoding.js.map +1 -1
  44. package/dist/js/navigate.d.ts +3 -0
  45. package/dist/js/navigate.js +56 -6
  46. package/dist/js/navigate.js.map +1 -1
  47. package/dist/js/updateStore.js +8 -2
  48. package/dist/js/updateStore.js.map +1 -1
  49. package/dist/js/useEvents.js +9 -7
  50. package/dist/js/useEvents.js.map +1 -1
  51. package/dist/js/usePolling.js +38 -37
  52. package/dist/js/usePolling.js.map +1 -1
  53. package/dist/openapi.d.ts +74 -0
  54. package/dist/openapi.js +172 -0
  55. package/dist/openapi.js.map +1 -0
  56. package/dist/prerender.js +1 -0
  57. package/dist/prerender.js.map +1 -1
  58. package/dist/redirect.d.ts +2 -2
  59. package/dist/redirect.js.map +1 -1
  60. package/dist/request.d.ts +56 -2
  61. package/dist/request.js +68 -4
  62. package/dist/request.js.map +1 -1
  63. package/dist/routeSchema.d.ts +49 -0
  64. package/dist/routeSchema.js.map +1 -1
  65. package/dist/routes.d.ts +15 -1
  66. package/dist/routes.js.map +1 -1
  67. package/dist/testing.d.ts +22 -0
  68. package/dist/testing.js +83 -5
  69. package/dist/testing.js.map +1 -1
  70. package/dist/vite.d.ts +80 -1
  71. package/dist/vite.js +625 -82
  72. package/dist/vite.js.map +1 -1
  73. package/package.json +7 -3
@@ -0,0 +1,74 @@
1
+ /** What the generated entry hands over for each route.ts. */
2
+ export interface OpenApiRoute {
3
+ /** The route's pattern in this package's spelling: `/api/orders/[id]`. */
4
+ pattern: string;
5
+ methods: string[];
6
+ /** The middleware.ts files above it: a guarded route gets a security requirement. */
7
+ guarded: boolean;
8
+ /** The route module: its params, searchParams, body and openapi exports, if any. */
9
+ module: Record<string, unknown>;
10
+ }
11
+ export interface OpenApiInfo {
12
+ title?: string;
13
+ version?: string;
14
+ description?: string;
15
+ }
16
+ /**
17
+ * What the document says that no route can: where the API is served, and
18
+ * how a caller authenticates. Spread onto the document as written, so
19
+ * anything OpenAPI allows at the top level is allowed here.
20
+ */
21
+ export interface OpenApiDocumentOptions {
22
+ /**
23
+ * Which routes the document describes. `'all'` (the default) is every
24
+ * route.ts that did not opt out; `'declared'` is only the ones that
25
+ * export `openapi`, for an app whose routes are mostly webhooks and
26
+ * callbacks with a handful of endpoints meant for a caller to read about.
27
+ */
28
+ include?: "all" | "declared";
29
+ info?: OpenApiInfo;
30
+ servers?: {
31
+ url: string;
32
+ description?: string;
33
+ }[];
34
+ security?: Record<string, string[]>[];
35
+ components?: Record<string, unknown>;
36
+ tags?: {
37
+ name: string;
38
+ description?: string;
39
+ }[];
40
+ }
41
+ /**
42
+ * What a route.ts may say about itself, beside its handler:
43
+ *
44
+ * export const openapi = {
45
+ * summary: 'Chat completions',
46
+ * tags: ['Chat'],
47
+ * responses: { 200: { description: 'The completion', content: { … } } },
48
+ * }
49
+ *
50
+ * Merged onto every operation the file exports, or per method when keyed
51
+ * by one: `{ POST: { summary: … } }`. Anything OpenAPI allows on an operation.
52
+ * `false` leaves the route out of the document altogether; `{ DELETE: false }`
53
+ * leaves one method out. HEAD and OPTIONS are never documented: the engine
54
+ * answers them for every route.
55
+ */
56
+ export type OpenApiOperationExtras = Record<string, unknown>;
57
+ /** `/api/orders/[id]/route` → `/api/orders/{id}`, and the names it binds. */
58
+ export declare function openApiPath(pattern: string): {
59
+ path: string;
60
+ params: string[];
61
+ };
62
+ /**
63
+ * The document.
64
+ *
65
+ * Path parameters come from the pattern, typed by the route's `params` schema
66
+ * where it has one and as strings otherwise; query parameters from the
67
+ * `searchParams` schema's properties, each optional unless the schema
68
+ * requires it; a request body from the `body` schema, as JSON. A guarded
69
+ * route names the session as its security requirement, which is what a
70
+ * middleware.ts above it checks.
71
+ */
72
+ export declare function buildOpenApi(routes: OpenApiRoute[], options?: OpenApiDocumentOptions): Record<string, unknown>;
73
+ /** The document, as a route answers it: JSON, cacheable, built once. */
74
+ export declare function openApiResponse(routes: OpenApiRoute[], options?: OpenApiDocumentOptions): Response;
@@ -0,0 +1,172 @@
1
+ // An OpenAPI document from the route tree, and a page that reads it.
2
+ //
3
+ // Every route.ts already says what an operation needs: the methods it
4
+ // exports, and the params, searchParams and body schemas beside them. A
5
+ // Standard Schema describes itself as JSON Schema (Zod 4 and ArkType do;
6
+ // Valibot needs its converter and contributes nothing here), so the document
7
+ // is derived rather than written - the way Elysia derives its from TypeBox.
8
+ // Nothing is annotated twice.
9
+ //
10
+ // The document is built from the modules the generated entry imported, and
11
+ // stored by the build like any other api route that reads nothing per
12
+ // request. The page that reads it is Scalar's own package, mounted as a
13
+ // route: `export const GET = ApiReference({ url: '/openapi.json' })`.
14
+ const METHODS = new Set(["GET", "POST", "PUT", "PATCH", "DELETE"]);
15
+ /** The extras a route declared for one method: the shared ones, then that method's. */
16
+ function extrasFor(module, method) {
17
+ const declared = module.openapi;
18
+ if (declared === null || typeof declared !== "object")
19
+ return {};
20
+ const record = declared;
21
+ const shared = {};
22
+ const own = (record[method] ?? {});
23
+ for (const [key, value] of Object.entries(record)) {
24
+ if (!METHODS.has(key))
25
+ shared[key] = value;
26
+ }
27
+ return { ...shared, ...own };
28
+ }
29
+ /** A Standard Schema's JSON Schema, or null when it cannot describe itself. */
30
+ function jsonSchemaOf(schema) {
31
+ if (schema === null || typeof schema !== "object")
32
+ return null;
33
+ try {
34
+ const produce = schema["~standard"]?.jsonSchema?.input;
35
+ if (typeof produce !== "function")
36
+ return null;
37
+ // A leaf JSON Schema cannot say - a Date, a custom check - documents as
38
+ // `{}` rather than costing the route its whole body schema.
39
+ const json = produce({ target: "draft-2020-12", libraryOptions: { unrepresentable: "any" } });
40
+ // The dialect marker belongs on the document, not on every schema in it.
41
+ delete json.$schema;
42
+ return json;
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ }
48
+ /** `/api/orders/[id]/route` → `/api/orders/{id}`, and the names it binds. */
49
+ export function openApiPath(pattern) {
50
+ const params = [];
51
+ const path = pattern.replace(/\[(?:\.\.\.)?(\w+)\]/g, (_, name) => {
52
+ params.push(name);
53
+ return `{${name}}`;
54
+ });
55
+ return { path, params };
56
+ }
57
+ const HAS_BODY = new Set(["POST", "PUT", "PATCH", "DELETE"]);
58
+ /**
59
+ * The document.
60
+ *
61
+ * Path parameters come from the pattern, typed by the route's `params` schema
62
+ * where it has one and as strings otherwise; query parameters from the
63
+ * `searchParams` schema's properties, each optional unless the schema
64
+ * requires it; a request body from the `body` schema, as JSON. A guarded
65
+ * route names the session as its security requirement, which is what a
66
+ * middleware.ts above it checks.
67
+ */
68
+ export function buildOpenApi(routes, options = {}) {
69
+ const { info = {}, include = "all", ...rest } = options;
70
+ const paths = {};
71
+ let anyGuarded = false;
72
+ for (const route of routes) {
73
+ // `export const openapi = false`: a route that is not part of the API -
74
+ // the page that renders this document, a webhook for one caller. With
75
+ // include: 'declared', a route with no `openapi` export is the same.
76
+ if (route.module.openapi === false)
77
+ continue;
78
+ if (include === "declared" && route.module.openapi === undefined)
79
+ continue;
80
+ const { path, params } = openApiPath(route.pattern);
81
+ const paramsSchema = jsonSchemaOf(route.module.params);
82
+ const searchSchema = jsonSchemaOf(route.module.searchParams);
83
+ const bodySchema = jsonSchemaOf(route.module.body);
84
+ const pathParameters = params.map((name) => ({
85
+ name,
86
+ in: "path",
87
+ required: true,
88
+ schema: paramsSchema?.properties?.[name] ?? { type: "string" },
89
+ }));
90
+ const queryParameters = Object.entries(searchSchema?.properties ?? {}).map(([name, schema]) => ({
91
+ name,
92
+ in: "query",
93
+ required: searchSchema?.required?.includes(name) ?? false,
94
+ schema,
95
+ }));
96
+ const operations = {};
97
+ for (const method of route.methods) {
98
+ // HEAD and OPTIONS are answered for every route by the engine, and a
99
+ // file that exports one - a CORS preflight - is not documenting an
100
+ // operation. `openapi: { OPTIONS: false }` drops any other method.
101
+ if (method === "HEAD" || method === "OPTIONS")
102
+ continue;
103
+ if ((route.module.openapi?.[method]) === false)
104
+ continue;
105
+ const operation = {
106
+ operationId: `${method.toLowerCase()}${path
107
+ .replace(/\{(\w+)\}/g, "By$1")
108
+ .split("/")
109
+ .filter(Boolean)
110
+ .map((part) => part[0].toUpperCase() + part.slice(1))
111
+ .join("")}`,
112
+ parameters: [...pathParameters, ...queryParameters],
113
+ responses: { "200": { description: "OK" } },
114
+ };
115
+ if (bodySchema && HAS_BODY.has(method)) {
116
+ operation.requestBody = {
117
+ required: true,
118
+ content: { "application/json": { schema: bodySchema } },
119
+ };
120
+ operation.responses["422"] = {
121
+ description: "The input was refused; validationErrors names each field.",
122
+ };
123
+ }
124
+ if (route.guarded) {
125
+ anyGuarded = true;
126
+ operation.security = [{ session: [] }];
127
+ operation.responses["401"] = { description: "Not signed in." };
128
+ operation.responses["403"] = { description: "Signed in, and still not allowed." };
129
+ }
130
+ // What the route said about itself wins over what was derived, field
131
+ // by field; its responses merge onto the derived ones.
132
+ const extras = extrasFor(route.module, method);
133
+ const responses = { ...operation.responses, ...(extras.responses ?? {}) };
134
+ operations[method.toLowerCase()] = { ...operation, ...extras, responses };
135
+ }
136
+ paths[path] = { ...(paths[path] ?? {}), ...operations };
137
+ }
138
+ const document = {
139
+ openapi: "3.1.0",
140
+ info: {
141
+ title: info.title ?? "API",
142
+ version: info.version ?? "0.0.0",
143
+ ...(info.description ? { description: info.description } : {}),
144
+ },
145
+ ...rest,
146
+ paths,
147
+ };
148
+ if (anyGuarded) {
149
+ const components = (rest.components ?? {});
150
+ const schemes = (components.securitySchemes ?? {});
151
+ document.components = {
152
+ ...components,
153
+ securitySchemes: {
154
+ session: {
155
+ type: "apiKey",
156
+ in: "cookie",
157
+ name: "session",
158
+ description: "The visitor's session, checked by the middleware.ts above the route.",
159
+ },
160
+ ...schemes,
161
+ },
162
+ };
163
+ }
164
+ return document;
165
+ }
166
+ /** The document, as a route answers it: JSON, cacheable, built once. */
167
+ export function openApiResponse(routes, options = {}) {
168
+ return Response.json(buildOpenApi(routes, options), {
169
+ headers: { "Cache-Control": "public, max-age=300" },
170
+ });
171
+ }
172
+ //# sourceMappingURL=openapi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi.js","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"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","sourcesContent":["// An OpenAPI document from the route tree, and a page that reads it.\n//\n// Every route.ts already says what an operation needs: the methods it\n// exports, and the params, searchParams and body schemas beside them. A\n// Standard Schema describes itself as JSON Schema (Zod 4 and ArkType do;\n// Valibot needs its converter and contributes nothing here), so the document\n// is derived rather than written - the way Elysia derives its from TypeBox.\n// Nothing is annotated twice.\n//\n// The document is built from the modules the generated entry imported, and\n// stored by the build like any other api route that reads nothing per\n// request. The page that reads it is Scalar's own package, mounted as a\n// route: `export const GET = ApiReference({ url: '/openapi.json' })`.\n\ntype JsonSchema = Record<string, unknown> & {\n type?: string | string[];\n properties?: Record<string, JsonSchema>;\n required?: string[];\n};\n\ntype WithJsonSchema = {\n \"~standard\"?: {\n jsonSchema?: {\n input?: (options: { target: string; libraryOptions?: Record<string, unknown> }) => unknown;\n };\n };\n};\n\n/** What the generated entry hands over for each route.ts. */\nexport interface OpenApiRoute {\n /** The route's pattern in this package's spelling: `/api/orders/[id]`. */\n pattern: string;\n methods: string[];\n /** The middleware.ts files above it: a guarded route gets a security requirement. */\n guarded: boolean;\n /** The route module: its params, searchParams, body and openapi exports, if any. */\n module: Record<string, unknown>;\n}\n\nconst METHODS = new Set([\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"]);\n\n/** The extras a route declared for one method: the shared ones, then that method's. */\nfunction extrasFor(module: Record<string, unknown>, method: string): OpenApiOperationExtras {\n const declared = module.openapi;\n\n if (declared === null || typeof declared !== \"object\") return {};\n\n const record = declared as Record<string, unknown>;\n const shared: Record<string, unknown> = {};\n const own = (record[method] ?? {}) as Record<string, unknown>;\n\n for (const [key, value] of Object.entries(record)) {\n if (!METHODS.has(key)) shared[key] = value;\n }\n\n return { ...shared, ...own };\n}\n\nexport interface OpenApiInfo {\n title?: string;\n version?: string;\n description?: string;\n}\n\n/**\n * What the document says that no route can: where the API is served, and\n * how a caller authenticates. Spread onto the document as written, so\n * anything OpenAPI allows at the top level is allowed here.\n */\nexport interface OpenApiDocumentOptions {\n /**\n * Which routes the document describes. `'all'` (the default) is every\n * route.ts that did not opt out; `'declared'` is only the ones that\n * export `openapi`, for an app whose routes are mostly webhooks and\n * callbacks with a handful of endpoints meant for a caller to read about.\n */\n include?: \"all\" | \"declared\";\n info?: OpenApiInfo;\n servers?: { url: string; description?: string }[];\n security?: Record<string, string[]>[];\n components?: Record<string, unknown>;\n tags?: { name: string; description?: string }[];\n}\n\n/**\n * What a route.ts may say about itself, beside its handler:\n *\n * export const openapi = {\n * summary: 'Chat completions',\n * tags: ['Chat'],\n * responses: { 200: { description: 'The completion', content: { … } } },\n * }\n *\n * Merged onto every operation the file exports, or per method when keyed\n * by one: `{ POST: { summary: … } }`. Anything OpenAPI allows on an operation.\n * `false` leaves the route out of the document altogether; `{ DELETE: false }`\n * leaves one method out. HEAD and OPTIONS are never documented: the engine\n * answers them for every route.\n */\nexport type OpenApiOperationExtras = Record<string, unknown>;\n\n/** A Standard Schema's JSON Schema, or null when it cannot describe itself. */\nfunction jsonSchemaOf(schema: unknown): JsonSchema | null {\n if (schema === null || typeof schema !== \"object\") return null;\n\n try {\n const produce = (schema as WithJsonSchema)[\"~standard\"]?.jsonSchema?.input;\n\n if (typeof produce !== \"function\") return null;\n\n // A leaf JSON Schema cannot say - a Date, a custom check - documents as\n // `{}` rather than costing the route its whole body schema.\n const json = produce({ target: \"draft-2020-12\", libraryOptions: { unrepresentable: \"any\" } }) as JsonSchema;\n\n // The dialect marker belongs on the document, not on every schema in it.\n delete json.$schema;\n\n return json;\n } catch {\n return null;\n }\n}\n\n/** `/api/orders/[id]/route` → `/api/orders/{id}`, and the names it binds. */\nexport function openApiPath(pattern: string): { path: string; params: string[] } {\n const params: string[] = [];\n const path = pattern.replace(/\\[(?:\\.\\.\\.)?(\\w+)\\]/g, (_, name: string) => {\n params.push(name);\n\n return `{${name}}`;\n });\n\n return { path, params };\n}\n\nconst HAS_BODY = new Set([\"POST\", \"PUT\", \"PATCH\", \"DELETE\"]);\n\n/**\n * The document.\n *\n * Path parameters come from the pattern, typed by the route's `params` schema\n * where it has one and as strings otherwise; query parameters from the\n * `searchParams` schema's properties, each optional unless the schema\n * requires it; a request body from the `body` schema, as JSON. A guarded\n * route names the session as its security requirement, which is what a\n * middleware.ts above it checks.\n */\nexport function buildOpenApi(routes: OpenApiRoute[], options: OpenApiDocumentOptions = {}): Record<string, unknown> {\n const { info = {}, include = \"all\", ...rest } = options;\n const paths: Record<string, Record<string, unknown>> = {};\n let anyGuarded = false;\n\n for (const route of routes) {\n // `export const openapi = false`: a route that is not part of the API -\n // the page that renders this document, a webhook for one caller. With\n // include: 'declared', a route with no `openapi` export is the same.\n if (route.module.openapi === false) continue;\n if (include === \"declared\" && route.module.openapi === undefined) continue;\n\n const { path, params } = openApiPath(route.pattern);\n const paramsSchema = jsonSchemaOf(route.module.params);\n const searchSchema = jsonSchemaOf(route.module.searchParams);\n const bodySchema = jsonSchemaOf(route.module.body);\n\n const pathParameters = params.map((name) => ({\n name,\n in: \"path\",\n required: true,\n schema: paramsSchema?.properties?.[name] ?? { type: \"string\" },\n }));\n\n const queryParameters = Object.entries(searchSchema?.properties ?? {}).map(([name, schema]) => ({\n name,\n in: \"query\",\n required: searchSchema?.required?.includes(name) ?? false,\n schema,\n }));\n\n const operations: Record<string, unknown> = {};\n\n for (const method of route.methods) {\n // HEAD and OPTIONS are answered for every route by the engine, and a\n // file that exports one - a CORS preflight - is not documenting an\n // operation. `openapi: { OPTIONS: false }` drops any other method.\n if (method === \"HEAD\" || method === \"OPTIONS\") continue;\n if (((route.module.openapi as Record<string, unknown> | undefined)?.[method]) === false) continue;\n\n const operation: Record<string, unknown> = {\n operationId: `${method.toLowerCase()}${path\n .replace(/\\{(\\w+)\\}/g, \"By$1\")\n .split(\"/\")\n .filter(Boolean)\n .map((part) => part[0].toUpperCase() + part.slice(1))\n .join(\"\")}`,\n parameters: [...pathParameters, ...queryParameters],\n responses: { \"200\": { description: \"OK\" } },\n };\n\n if (bodySchema && HAS_BODY.has(method)) {\n operation.requestBody = {\n required: true,\n content: { \"application/json\": { schema: bodySchema } },\n };\n (operation.responses as Record<string, unknown>)[\"422\"] = {\n description: \"The input was refused; validationErrors names each field.\",\n };\n }\n\n if (route.guarded) {\n anyGuarded = true;\n operation.security = [{ session: [] }];\n (operation.responses as Record<string, unknown>)[\"401\"] = { description: \"Not signed in.\" };\n (operation.responses as Record<string, unknown>)[\"403\"] = { description: \"Signed in, and still not allowed.\" };\n }\n\n // What the route said about itself wins over what was derived, field\n // by field; its responses merge onto the derived ones.\n const extras = extrasFor(route.module, method);\n const responses = { ...(operation.responses as object), ...((extras.responses as object) ?? {}) };\n\n operations[method.toLowerCase()] = { ...operation, ...extras, responses };\n }\n\n paths[path] = { ...(paths[path] ?? {}), ...operations };\n }\n\n const document: Record<string, unknown> = {\n openapi: \"3.1.0\",\n info: {\n title: info.title ?? \"API\",\n version: info.version ?? \"0.0.0\",\n ...(info.description ? { description: info.description } : {}),\n },\n ...rest,\n paths,\n };\n\n if (anyGuarded) {\n const components = (rest.components ?? {}) as Record<string, unknown>;\n const schemes = (components.securitySchemes ?? {}) as Record<string, unknown>;\n\n document.components = {\n ...components,\n securitySchemes: {\n session: {\n type: \"apiKey\",\n in: \"cookie\",\n name: \"session\",\n description: \"The visitor's session, checked by the middleware.ts above the route.\",\n },\n ...schemes,\n },\n };\n }\n\n return document;\n}\n\n/** The document, as a route answers it: JSON, cacheable, built once. */\nexport function openApiResponse(routes: OpenApiRoute[], options: OpenApiDocumentOptions = {}): Response {\n return Response.json(buildOpenApi(routes, options), {\n headers: { \"Cache-Control\": \"public, max-age=300\" },\n });\n}\n"]}
package/dist/prerender.js CHANGED
@@ -51,6 +51,7 @@ const DEFAULT_PRERENDER_CONCURRENCY = 4;
51
51
  const RUNTIME_OWN = new Set([
52
52
  "DefaultRouteError",
53
53
  "DocumentTitle",
54
+ "LoadingBoundary",
54
55
  "PathnameProvider",
55
56
  "RouteErrorBoundary",
56
57
  "SegmentBoundary",