Skip to content

Commit

Permalink
feat: add module service adapter, unify route handling to `handleRequ…
Browse files Browse the repository at this point in the history
…estWithEdgeSpec`

* F

* unneeded import

* EdgeSpec -> EdgeSpecRouteBundle

* rename create server from route map

* refactor hadnleRequestWithEdgeSpec

* fixes

* better EdgeSpecOptions

* rename file
  • Loading branch information
mxsdev authored Jan 19, 2024
1 parent 660af81 commit 9df17b8
Show file tree
Hide file tree
Showing 15 changed files with 428 additions and 98 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Create an `entrypoint.js` file:
import { startServer } from "edgespec/adapters/node"
import bundle from "./dist"

startServer(bundle, 3000)
startServer(bundle, { port: 3000 })
```

### WinterCG (Cloudflare Workers/Vercel Edge Functions)
Expand Down
24 changes: 20 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"ts-node": "^10.9.1",
"tsup": "^7.2.0",
"tsx": "^4.5.0",
"type-fest": "^4.9.0",
"typescript": "^5.3.2"
},
"dependencies": {
Expand Down
88 changes: 88 additions & 0 deletions src/adapters/module-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {
type EdgeSpecAdapter,
type EdgeSpecRouteBundle,
handleRequestWithEdgeSpec,
} from "src/types/edge-spec"
import { EdgeSpecRequest, EdgeSpecRouteFn } from "src/types/web-handler"

export interface ModuleServiceOptions {
routeParam?: string
handleRouteParamNotFound?: EdgeSpecRouteFn
allowMatchingOnAnyCatchAllRouteParam?: boolean
}

export type ModuleService = (options?: ModuleServiceOptions) => EdgeSpecRouteFn

export const createModuleService: EdgeSpecAdapter<[], ModuleService> = (
moduleServiceEdgeSpec
) => {
return (options) => async (request) => {
// cascade options down the edge spec chain
const edgeSpec: EdgeSpecRouteBundle = {
...request.edgeSpec,
...moduleServiceEdgeSpec,
...(options?.handleRouteParamNotFound && {
handleModuleServiceRouteNotFound: options?.handleRouteParamNotFound,
}),
}

const pathnameOverrideResult = getPathnameOverride(request, options ?? {})

if ("failed" in pathnameOverrideResult) {
return await pathnameOverrideResult.failed(request)
}

const response = await handleRequestWithEdgeSpec(
edgeSpec,
pathnameOverrideResult.pathnameOverride
)(request)

return response
}
}

function getPathnameOverride(
request: EdgeSpecRequest,
options: ModuleServiceOptions
):
| {
pathnameOverride: string | undefined
}
| {
failed: EdgeSpecRouteFn
} {
const {
routeParam,
handleRouteParamNotFound = request.edgeSpec
.handleModuleServiceRouteNotFound ??
(() => {
throw new Error("Module service route not found!")
}),
allowMatchingOnAnyCatchAllRouteParam = true,
} = options

let paths: string[] | undefined

if (routeParam) {
const candidate = request.pathParams?.[routeParam]

if (candidate && Array.isArray(candidate)) {
paths = candidate
}
}

if (!paths && allowMatchingOnAnyCatchAllRouteParam) {
for (const routes of Object.values(request.pathParams ?? {})) {
if (Array.isArray(routes)) {
paths = routes
break
}
}
}

if (!paths) {
return { failed: handleRouteParamNotFound }
}

return { pathnameOverride: "/" + paths.join("/") }
}
24 changes: 10 additions & 14 deletions src/adapters/node.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
import http from "node:http"
import { transformToNodeBuilder } from "src/edge-runtime/transform-to-node"
import { EdgeSpecAdapter } from "src/types/edge-spec"
import { EdgeSpecRequest } from "src/types/web-handler"
import { EdgeSpecAdapter, handleRequestWithEdgeSpec } from "src/types/edge-spec"

export const startServer: EdgeSpecAdapter = (edgeSpec, port) => {
export interface EdgeSpecNodeAdapterOptions {
port?: number
}

export const startServer: EdgeSpecAdapter<[EdgeSpecNodeAdapterOptions]> = (
edgeSpec,
{ port }
) => {
const transformToNode = transformToNodeBuilder({
defaultOrigin: `http://localhost${port ? `:${port}` : ""}`,
})

const server = http.createServer(
transformToNode(async (fetchRequest: EdgeSpecRequest) => {
const { matchedRoute, routeParams } = edgeSpec.routeMatcher(
new URL(fetchRequest.url).pathname
)
const handler = edgeSpec.routeMapWithHandlers[matchedRoute]
fetchRequest.pathParams = routeParams

const fetchResponse: Response = await handler(fetchRequest)

return fetchResponse
})
transformToNode(handleRequestWithEdgeSpec(edgeSpec))
)
server.listen(port)

Expand Down
10 changes: 3 additions & 7 deletions src/adapters/wintercg-minimal.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import { EdgeSpecAdapter } from "src/types/edge-spec"
import { EdgeSpecAdapter, handleRequestWithEdgeSpec } from "src/types/edge-spec"
import { EdgeSpecFetchEvent } from "src/types/web-handler"

export const addFetchListener: EdgeSpecAdapter = (edgeSpec) => {
addEventListener("fetch", async (event) => {
// TODO: find a better way to cast this
const fetchEvent = event as unknown as EdgeSpecFetchEvent

const { matchedRoute, routeParams } = edgeSpec.routeMatcher(
new URL(fetchEvent.request.url).pathname
fetchEvent.respondWith(
await handleRequestWithEdgeSpec(edgeSpec)(fetchEvent.request)
)
const handler = edgeSpec.routeMapWithHandlers[matchedRoute]
fetchEvent.request.pathParams = routeParams

fetchEvent.respondWith(await handler(fetchEvent.request))
})
}
2 changes: 1 addition & 1 deletion src/create-with-edge-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { EdgeSpecRouteFn } from "./types/web-handler.js"
export const createWithEdgeSpec = (globalSpec: SetupParams) => {
return (routeSpec: any) =>
(routeFn: EdgeSpecRouteFn): EdgeSpecRouteFn =>
async (req: Request) => {
async (req) => {
// Identify environment this is being executed in and convert to WinterCG-
// compatible request

Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./types/edge-spec.js"
export * from "./codegen/generate-module-code.js"
export * from "./create-with-edge-spec.js"
50 changes: 50 additions & 0 deletions src/serve/create-node-server-from-route-map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { createServer } from "node:http"
import { getRouteMatcher } from "next-route-matcher"
import { normalizeRouteMap } from "../lib/normalize-route-map.js"
import {
type TransformToNodeOptions,
transformToNodeBuilder,
} from "src/edge-runtime/transform-to-node.js"
import { EdgeSpecRouteFn } from "src/types/web-handler.js"
import {
EdgeSpecRouteBundle,
EdgeSpecOptions,
handleRequestWithEdgeSpec,
} from "src/types/edge-spec.js"

export const createEdgeSpecFromRouteMap = (
routeMap: Record<string, EdgeSpecRouteFn>,
edgeSpecOptions?: Partial<EdgeSpecOptions>
): EdgeSpecRouteBundle => {
const formattedRoutes = normalizeRouteMap(routeMap)
const routeMatcher = getRouteMatcher(Object.keys(formattedRoutes))

const routeMapWithHandlers = Object.fromEntries(
Object.entries(formattedRoutes).map(([routeFormatted, route]) => [
routeFormatted,
routeMap[route],
])
)

return {
routeMatcher,
routeMapWithHandlers,
...edgeSpecOptions,
}
}

export const createNodeServerFromRouteMap = async (
routeMap: Record<string, EdgeSpecRouteFn>,
transformToNodeOptions: TransformToNodeOptions,
edgeSpecOptions?: Partial<EdgeSpecOptions>
) => {
const edgeSpec = createEdgeSpecFromRouteMap(routeMap, edgeSpecOptions)

const transformToNode = transformToNodeBuilder(transformToNodeOptions)

const server = createServer(
transformToNode(handleRequestWithEdgeSpec(edgeSpec))
)

return server
}
54 changes: 0 additions & 54 deletions src/serve/create-server-from-route-map.ts

This file was deleted.

Loading

0 comments on commit 9df17b8

Please sign in to comment.