flamefront 0.1.0-alpha.0 → 0.1.1
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/LICENSE.md +2 -91
- package/README.md +35 -620
- package/package.json +49 -26
- package/src/cli.ts +16 -1
- package/src/entry.ts +49 -0
- package/src/fetch.ts +280 -0
- package/src/fragment-client.ts +127 -87
- package/src/fragment-hydration-client.tsx +81 -0
- package/src/fragment-protocol.ts +3 -3
- package/src/fragment.tsx +656 -0
- package/src/glob.ts +294 -0
- package/src/identifier-prefix.ts +5 -0
- package/src/index.ts +569 -49
- package/src/lifecycle.ts +7 -6
- package/src/octane-client-core.ts +10 -3
- package/src/octane-client.ts +12 -6
- package/src/octane-compiler.d.ts +19 -0
- package/src/octane-default-renderer.tsx +124 -0
- package/src/octane-router-document.ts +19 -2
- package/src/{octane.ts → octane.tsx} +152 -145
- package/src/output.ts +48 -0
- package/src/remix-route-data.ts +28 -20
- package/src/remix-router.ts +10 -4
- package/src/route-data-client.ts +22 -12
- package/src/route-prefetch.ts +19 -12
- package/src/server.ts +122 -24
- package/src/srvx.ts +43 -183
- package/src/typegen.ts +207 -0
- package/src/virtual-remix-routes.d.ts +16 -1
- package/src/vite.ts +364 -27
- package/src/fragment.ts +0 -260
package/package.json
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flamefront",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Typed centralized route manifests for Octane.",
|
|
6
|
-
"license": "
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": "https://github.com/aleclarson/flamefront",
|
|
7
8
|
"files": [
|
|
8
|
-
"LICENSE.md",
|
|
9
|
-
"README.md",
|
|
10
9
|
"bin",
|
|
11
10
|
"src"
|
|
12
11
|
],
|
|
13
12
|
"engines": {
|
|
14
|
-
"node": ">=
|
|
13
|
+
"node": ">=26.0.0"
|
|
15
14
|
},
|
|
16
15
|
"publishConfig": {
|
|
17
16
|
"access": "public"
|
|
@@ -22,45 +21,69 @@
|
|
|
22
21
|
"type": "module",
|
|
23
22
|
"exports": {
|
|
24
23
|
".": "./src/index.ts",
|
|
25
|
-
"./
|
|
26
|
-
"./
|
|
24
|
+
"./entry": "./src/entry.ts",
|
|
25
|
+
"./fetch": "./src/fetch.ts",
|
|
26
|
+
"./fragment": "./src/fragment.tsx",
|
|
27
|
+
"./octane": "./src/octane.tsx",
|
|
27
28
|
"./octane/client": "./src/octane-client.ts",
|
|
28
29
|
"./octane/router-document": "./src/octane-router-document.ts",
|
|
29
30
|
"./remix-router": "./src/remix-router.ts",
|
|
30
31
|
"./remix-router/data": "./src/remix-route-data.ts",
|
|
31
32
|
"./server": "./src/server.ts",
|
|
33
|
+
"./output": "./src/output.ts",
|
|
32
34
|
"./srvx": "./src/srvx.ts",
|
|
33
35
|
"./vite": "./src/vite.ts"
|
|
34
36
|
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"dev": "pnpm --filter flamefront-playground dev",
|
|
39
|
+
"build": "pnpm --filter flamefront-playground build",
|
|
40
|
+
"preview": "pnpm --filter flamefront-playground preview",
|
|
41
|
+
"typegen": "pnpm --filter flamefront-playground typegen",
|
|
42
|
+
"routes": "pnpm --filter flamefront-playground routes",
|
|
43
|
+
"typecheck": "tsc -p tsconfig.json && pnpm --filter flamefront-playground typecheck",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"test:e2e": "pnpm --filter flamefront-playground build && vitest run --config vitest.e2e.config.ts",
|
|
46
|
+
"check": "oxlint && prettier --check . && pnpm typecheck && pnpm test && pnpm test:e2e",
|
|
47
|
+
"format": "oxlint --fix && prettier --write ."
|
|
48
|
+
},
|
|
35
49
|
"dependencies": {
|
|
36
|
-
"@alloc/cmd-ts": "0.17.1",
|
|
37
|
-
"@babel/generator": "^
|
|
38
|
-
"@babel/parser": "^
|
|
39
|
-
"@babel/traverse": "^
|
|
40
|
-
"@babel/types": "^
|
|
41
|
-
"@remix-run/route-pattern": "0.24.0",
|
|
42
|
-
"babel-dead-code-elimination": "^
|
|
43
|
-
"
|
|
50
|
+
"@alloc/cmd-ts": "^0.17.1",
|
|
51
|
+
"@babel/generator": "^8.0.5",
|
|
52
|
+
"@babel/parser": "^8.0.5",
|
|
53
|
+
"@babel/traverse": "^8.0.5",
|
|
54
|
+
"@babel/types": "^8.0.5",
|
|
55
|
+
"@remix-run/route-pattern": "^0.24.0",
|
|
56
|
+
"babel-dead-code-elimination": "^2.0.0",
|
|
57
|
+
"satteri": "^0.10.5",
|
|
58
|
+
"srvx": "^1.0.4",
|
|
59
|
+
"vite-plugin-satteri": "^0.3.5"
|
|
44
60
|
},
|
|
45
61
|
"devDependencies": {
|
|
62
|
+
"@octanejs/remix-router": "^0.1.48",
|
|
63
|
+
"@octanejs/vite-plugin": "^0.1.54",
|
|
64
|
+
"@stylistic/eslint-plugin": "^5.10.0",
|
|
65
|
+
"@tsrx/prettier-plugin": "^0.3.137",
|
|
46
66
|
"@types/babel__generator": "^7.27.0",
|
|
47
67
|
"@types/babel__traverse": "^7.28.0",
|
|
48
|
-
"@types/node": "26.
|
|
49
|
-
"
|
|
50
|
-
"
|
|
68
|
+
"@types/node": "^26.5.1",
|
|
69
|
+
"octane": "^0.2.7",
|
|
70
|
+
"oxc-tsrx": "^0.6.1",
|
|
71
|
+
"oxlint": "^1.82.0",
|
|
72
|
+
"oxlint-tsgolint": "^7.0.2001",
|
|
73
|
+
"playwright": "^1.63.0",
|
|
74
|
+
"prettier": "^3.9.6",
|
|
75
|
+
"typescript": "^5.9.3",
|
|
76
|
+
"vite": "^8.3.0",
|
|
77
|
+
"vitest": "^5.0.0"
|
|
51
78
|
},
|
|
52
79
|
"peerDependencies": {
|
|
53
|
-
"@octanejs/remix-router": "0.1.
|
|
54
|
-
"octane": "0.
|
|
55
|
-
"vite": "
|
|
80
|
+
"@octanejs/remix-router": ">=0.1.48",
|
|
81
|
+
"octane": ">=0.2.7",
|
|
82
|
+
"vite": ">=8.3.0"
|
|
56
83
|
},
|
|
57
84
|
"peerDependenciesMeta": {
|
|
58
85
|
"@octanejs/remix-router": {
|
|
59
86
|
"optional": true
|
|
60
87
|
}
|
|
61
|
-
},
|
|
62
|
-
"scripts": {
|
|
63
|
-
"test": "node --test test/*.test.ts",
|
|
64
|
-
"typecheck": "tsc -p tsconfig.json"
|
|
65
88
|
}
|
|
66
|
-
}
|
|
89
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { relative } from "node:path"
|
|
3
4
|
import {
|
|
4
5
|
binary,
|
|
5
6
|
command,
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
16
17
|
loadProject,
|
|
17
18
|
previewProject,
|
|
18
19
|
} from "./lifecycle.ts"
|
|
20
|
+
import { generateProjectTypes } from "./typegen.ts"
|
|
19
21
|
|
|
20
22
|
const routes = command({
|
|
21
23
|
name: "routes",
|
|
@@ -73,11 +75,24 @@ const preview = command({
|
|
|
73
75
|
handler: () => previewProject(),
|
|
74
76
|
})
|
|
75
77
|
|
|
78
|
+
const typegen = command({
|
|
79
|
+
name: "typegen",
|
|
80
|
+
description: "Generate route import declarations for the current app.",
|
|
81
|
+
args: {},
|
|
82
|
+
async handler() {
|
|
83
|
+
const result = await generateProjectTypes()
|
|
84
|
+
const status = result.written ? "Generated" : "Unchanged"
|
|
85
|
+
const file = relative(process.cwd(), result.file) || "."
|
|
86
|
+
|
|
87
|
+
console.log(`${status} ${file}.`)
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
|
|
76
91
|
const cli = subcommands({
|
|
77
92
|
name: "ff",
|
|
78
93
|
version: "0.1.0-alpha.0",
|
|
79
94
|
description: "Flamefront, a small compiler-oriented framework for Octane.",
|
|
80
|
-
cmds: { build, dev, preview, routes },
|
|
95
|
+
cmds: { build, dev, preview, routes, typegen },
|
|
81
96
|
})
|
|
82
97
|
|
|
83
98
|
await run(binary(cli), process.argv)
|
package/src/entry.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { RouteDefinition } from "./index.ts"
|
|
2
|
+
|
|
3
|
+
export type {
|
|
4
|
+
FetchMiddleware,
|
|
5
|
+
FetchServerAssets,
|
|
6
|
+
FetchServerEntryOptions,
|
|
7
|
+
FlamefrontFetchServerEntry,
|
|
8
|
+
ResponseHeaders,
|
|
9
|
+
ResponseHeadersContext,
|
|
10
|
+
ResponseHeadersHook,
|
|
11
|
+
ServerDocuments,
|
|
12
|
+
ServerEntryLifecycle,
|
|
13
|
+
StaticFragmentContext,
|
|
14
|
+
StaticFragmentLoader,
|
|
15
|
+
TemplateContext,
|
|
16
|
+
TemplateLoader,
|
|
17
|
+
} from "./fetch.ts"
|
|
18
|
+
export type {
|
|
19
|
+
FlamefrontServerEntry,
|
|
20
|
+
ServerAssets,
|
|
21
|
+
SrvxMiddleware,
|
|
22
|
+
SrvxServerEntryOptions,
|
|
23
|
+
} from "./srvx.ts"
|
|
24
|
+
|
|
25
|
+
import type {
|
|
26
|
+
FetchServerEntryOptions,
|
|
27
|
+
FlamefrontFetchServerEntry,
|
|
28
|
+
} from "./fetch.ts"
|
|
29
|
+
import type { FlamefrontServerEntry, SrvxServerEntryOptions } from "./srvx.ts"
|
|
30
|
+
|
|
31
|
+
export type ServerEntryOptions<
|
|
32
|
+
Route extends RouteDefinition = RouteDefinition,
|
|
33
|
+
> = FetchServerEntryOptions<Route> | SrvxServerEntryOptions<Route>
|
|
34
|
+
|
|
35
|
+
export type ServerEntry = FlamefrontFetchServerEntry | FlamefrontServerEntry
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create the server entry selected by the `flamefront` Vite plugin options.
|
|
39
|
+
* Vite replaces this placeholder with the generated adapter module at build
|
|
40
|
+
* time; the direct-import error prevents an accidental unconfigured entry.
|
|
41
|
+
*/
|
|
42
|
+
export function createServerEntry<
|
|
43
|
+
Route extends RouteDefinition = RouteDefinition,
|
|
44
|
+
>(options: ServerEntryOptions<Route>): ServerEntry {
|
|
45
|
+
void options
|
|
46
|
+
throw new Error(
|
|
47
|
+
"flamefront/entry must be loaded through the flamefront Vite plugin.",
|
|
48
|
+
)
|
|
49
|
+
}
|
package/src/fetch.ts
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import {
|
|
2
|
+
joinBasename,
|
|
3
|
+
type AppDefinition,
|
|
4
|
+
type RouteDefinition,
|
|
5
|
+
} from "./index.ts"
|
|
6
|
+
import type { OctaneDocuments } from "./octane.tsx"
|
|
7
|
+
import type { DocumentMode, RenderedDocument } from "./server.ts"
|
|
8
|
+
import {
|
|
9
|
+
isRouteFragmentRequest,
|
|
10
|
+
stripFlamefrontProtocolRequest,
|
|
11
|
+
} from "./fragment-protocol.ts"
|
|
12
|
+
import type { RouteFragmentArtifact } from "./fragment-client.ts"
|
|
13
|
+
|
|
14
|
+
export interface TemplateContext<
|
|
15
|
+
Route extends RouteDefinition = RouteDefinition,
|
|
16
|
+
> {
|
|
17
|
+
readonly request: Request
|
|
18
|
+
readonly route: Route | null
|
|
19
|
+
readonly mode: DocumentMode
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type TemplateLoader<Route extends RouteDefinition = RouteDefinition> = (
|
|
23
|
+
context: TemplateContext<Route>,
|
|
24
|
+
) => string | Promise<string>
|
|
25
|
+
|
|
26
|
+
export interface StaticFragmentContext<
|
|
27
|
+
Route extends RouteDefinition = RouteDefinition,
|
|
28
|
+
> {
|
|
29
|
+
readonly request: Request
|
|
30
|
+
readonly route: Route
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Load a pre-rendered static fragment from the host's asset system. */
|
|
34
|
+
export type StaticFragmentLoader<
|
|
35
|
+
Route extends RouteDefinition = RouteDefinition,
|
|
36
|
+
> = (
|
|
37
|
+
context: StaticFragmentContext<Route>,
|
|
38
|
+
) =>
|
|
39
|
+
RouteFragmentArtifact | undefined | Promise<RouteFragmentArtifact | undefined>
|
|
40
|
+
|
|
41
|
+
export interface ResponseHeadersContext<
|
|
42
|
+
Route extends RouteDefinition = RouteDefinition,
|
|
43
|
+
> {
|
|
44
|
+
readonly request: Request
|
|
45
|
+
readonly route: Route | null
|
|
46
|
+
readonly mode: DocumentMode
|
|
47
|
+
readonly document: RenderedDocument
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type ResponseHeaders = HeadersInit
|
|
51
|
+
|
|
52
|
+
/** Add or override response headers after document rendering. */
|
|
53
|
+
export type ResponseHeadersHook<
|
|
54
|
+
Route extends RouteDefinition = RouteDefinition,
|
|
55
|
+
> = (
|
|
56
|
+
context: ResponseHeadersContext<Route>,
|
|
57
|
+
) => ResponseHeaders | Promise<ResponseHeaders>
|
|
58
|
+
|
|
59
|
+
export type FetchMiddleware = (
|
|
60
|
+
request: Request,
|
|
61
|
+
next: () => Response | Promise<Response>,
|
|
62
|
+
) => Response | Promise<Response>
|
|
63
|
+
|
|
64
|
+
export type ServerDocuments = Pick<
|
|
65
|
+
OctaneDocuments,
|
|
66
|
+
"renderDocument" | "loadRouteData"
|
|
67
|
+
> &
|
|
68
|
+
Partial<Pick<OctaneDocuments, "renderFragment">>
|
|
69
|
+
|
|
70
|
+
/** Lifecycle operations shared by all Flamefront server adapters. */
|
|
71
|
+
export interface ServerEntryLifecycle {
|
|
72
|
+
readonly renderDocument: OctaneDocuments["renderDocument"]
|
|
73
|
+
readonly loadRouteData: OctaneDocuments["loadRouteData"]
|
|
74
|
+
readonly renderFragment?: OctaneDocuments["renderFragment"]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** A Web Fetch-compatible server entry. */
|
|
78
|
+
export interface FlamefrontFetchServerEntry extends ServerEntryLifecycle {
|
|
79
|
+
readonly fetch: (request: Request) => Response | Promise<Response>
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface FetchServerAssets<
|
|
83
|
+
Route extends RouteDefinition = RouteDefinition,
|
|
84
|
+
> {
|
|
85
|
+
readonly loadTemplate: TemplateLoader<Route>
|
|
86
|
+
readonly loadStaticFragment?: StaticFragmentLoader<Route>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface FetchServerEntryOptions<
|
|
90
|
+
Route extends RouteDefinition = RouteDefinition,
|
|
91
|
+
> {
|
|
92
|
+
readonly app: AppDefinition<Route>
|
|
93
|
+
readonly documents: ServerDocuments
|
|
94
|
+
readonly assets: FetchServerAssets<Route>
|
|
95
|
+
/** Applied outermost first, in declaration order. */
|
|
96
|
+
readonly middleware?: readonly FetchMiddleware[]
|
|
97
|
+
readonly headers?: ResponseHeadersHook<Route>
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function mergeHeaders(target: Headers, source: HeadersInit | undefined): void {
|
|
101
|
+
if (source === undefined) {
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const [name, value] of new Headers(source)) {
|
|
106
|
+
target.set(name, value)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function composeMiddleware(
|
|
111
|
+
middleware: readonly FetchMiddleware[],
|
|
112
|
+
handler: (request: Request) => Promise<Response>,
|
|
113
|
+
): (request: Request) => Promise<Response> {
|
|
114
|
+
return middleware.reduceRight<(request: Request) => Promise<Response>>(
|
|
115
|
+
(next, current) => async (request) => current(request, () => next(request)),
|
|
116
|
+
handler,
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Create the transport-neutral Flamefront request handler. Hosts can expose
|
|
122
|
+
* the returned `fetch` function directly or wrap it in their own adapter.
|
|
123
|
+
*/
|
|
124
|
+
export function createFetchServerEntry<
|
|
125
|
+
Route extends RouteDefinition = RouteDefinition,
|
|
126
|
+
>(options: FetchServerEntryOptions<Route>): FlamefrontFetchServerEntry {
|
|
127
|
+
const defaultClientRoute = options.app.routes.find(
|
|
128
|
+
(route) => route.render === "client",
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
const handleRequest = async (request: Request): Promise<Response> => {
|
|
132
|
+
const url = new URL(request.url)
|
|
133
|
+
const match = options.app.match(url)
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
if (
|
|
137
|
+
url.pathname === options.app.routing.basename &&
|
|
138
|
+
!match &&
|
|
139
|
+
defaultClientRoute
|
|
140
|
+
) {
|
|
141
|
+
return new Response(null, {
|
|
142
|
+
status: 302,
|
|
143
|
+
headers: {
|
|
144
|
+
Location: joinBasename(
|
|
145
|
+
options.app.routing.basename,
|
|
146
|
+
defaultClientRoute.path,
|
|
147
|
+
),
|
|
148
|
+
},
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (url.pathname === options.app.routing.dataPath) {
|
|
153
|
+
return options.documents.loadRouteData(request)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (isRouteFragmentRequest(url)) {
|
|
157
|
+
const sanitizedRequest = stripFlamefrontProtocolRequest(request)
|
|
158
|
+
const fragmentMatch = options.app.match(sanitizedRequest.url)
|
|
159
|
+
|
|
160
|
+
if (!fragmentMatch || fragmentMatch.data.render === "client") {
|
|
161
|
+
return new Response("Not found", { status: 404 })
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let artifact: RouteFragmentArtifact | undefined
|
|
165
|
+
|
|
166
|
+
if (
|
|
167
|
+
fragmentMatch.data.render === "static" &&
|
|
168
|
+
options.assets.loadStaticFragment
|
|
169
|
+
) {
|
|
170
|
+
artifact = await options.assets.loadStaticFragment({
|
|
171
|
+
request: sanitizedRequest,
|
|
172
|
+
route: fragmentMatch.data,
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!artifact && options.documents.renderFragment) {
|
|
177
|
+
artifact = await options.documents.renderFragment(sanitizedRequest)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (!artifact) {
|
|
181
|
+
return new Response("Not found", { status: 404 })
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const responseHeaders = new Headers({
|
|
185
|
+
"Content-Type":
|
|
186
|
+
"application/vnd.flamefront.fragment+json; charset=utf-8",
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
if (options.headers) {
|
|
190
|
+
mergeHeaders(
|
|
191
|
+
responseHeaders,
|
|
192
|
+
await options.headers({
|
|
193
|
+
request: sanitizedRequest,
|
|
194
|
+
route: fragmentMatch.data,
|
|
195
|
+
mode: fragmentMatch.data.render,
|
|
196
|
+
document: {
|
|
197
|
+
html: artifact.html,
|
|
198
|
+
routeData: artifact.routeData,
|
|
199
|
+
status: artifact.status,
|
|
200
|
+
},
|
|
201
|
+
}),
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return new Response(JSON.stringify(artifact), {
|
|
206
|
+
status: artifact.status ?? 200,
|
|
207
|
+
headers: responseHeaders,
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (!match) {
|
|
212
|
+
return new Response("Not found", { status: 404 })
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const mode: DocumentMode = url.searchParams.has("__flamefront_shell")
|
|
216
|
+
? "shell"
|
|
217
|
+
: match.data.render
|
|
218
|
+
const template = await options.assets.loadTemplate({
|
|
219
|
+
request,
|
|
220
|
+
route: match.data,
|
|
221
|
+
mode,
|
|
222
|
+
})
|
|
223
|
+
const document = await options.documents.renderDocument(
|
|
224
|
+
template,
|
|
225
|
+
request,
|
|
226
|
+
{ mode },
|
|
227
|
+
)
|
|
228
|
+
const responseHeaders = new Headers({
|
|
229
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
mergeHeaders(responseHeaders, document.headers)
|
|
233
|
+
if (options.headers) {
|
|
234
|
+
mergeHeaders(
|
|
235
|
+
responseHeaders,
|
|
236
|
+
await options.headers({
|
|
237
|
+
request,
|
|
238
|
+
route: match.data,
|
|
239
|
+
mode,
|
|
240
|
+
document,
|
|
241
|
+
}),
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return new Response(document.html, {
|
|
246
|
+
status: document.status ?? 200,
|
|
247
|
+
headers: responseHeaders,
|
|
248
|
+
})
|
|
249
|
+
} catch (error) {
|
|
250
|
+
if (error instanceof Response) {
|
|
251
|
+
return error
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
throw error
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const composedFetch = composeMiddleware(
|
|
259
|
+
options.middleware ?? [],
|
|
260
|
+
handleRequest,
|
|
261
|
+
)
|
|
262
|
+
const fetch = async (request: Request): Promise<Response> => {
|
|
263
|
+
try {
|
|
264
|
+
return await composedFetch(request)
|
|
265
|
+
} catch (error) {
|
|
266
|
+
if (error instanceof Response) {
|
|
267
|
+
return error
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
throw error
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
fetch,
|
|
276
|
+
renderDocument: options.documents.renderDocument,
|
|
277
|
+
loadRouteData: options.documents.loadRouteData,
|
|
278
|
+
renderFragment: options.documents.renderFragment,
|
|
279
|
+
}
|
|
280
|
+
}
|