Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(server)!: fetch handler rewrite and tests with multiple coercers support #57

Merged
merged 24 commits into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .npmrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use-node-version = 22.11.0
auto-install-peers = true
shamefully-hoist = true
shamefully-hoist = true
link-workspace-packages = true
prefer-workspace-packages = true
21 changes: 12 additions & 9 deletions apps/content/content/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -144,26 +144,29 @@ In oRPC middleware is very useful and fully typed you can find more info [here](
This example uses [@whatwg-node/server](https://www.npmjs.com/package/@whatwg-node/server) to create a Node server with [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).

```ts twoslash
import { handleFetchRequest, createORPCHandler } from '@orpc/server/fetch'
import { createOpenAPIServerlessHandler } from '@orpc/openapi/fetch'
import { ORPCHandler, CompositeHandler } from '@orpc/server/fetch'
import { OpenAPIServerlessHandler } from '@orpc/openapi/fetch'
import { createServer } from 'node:http'
import { createServerAdapter } from '@whatwg-node/server'
import { router } from 'examples/server'
import { ZodCoercer } from '@orpc/zod'

const openapiHandler = new OpenAPIServerlessHandler(router, {
schemaCoercers: [
new ZodCoercer(),
],
})
const orpcHandler = new ORPCHandler(router)
const compositeHandler = new CompositeHandler([openapiHandler, orpcHandler])

const server = createServer(
createServerAdapter((request: Request) => {
const url = new URL(request.url)

if (url.pathname.startsWith('/api')) {
return handleFetchRequest({
router,
request,
return compositeHandler.fetch(request, {
prefix: '/api',
context: {},
handlers: [
createORPCHandler(),
createOpenAPIServerlessHandler(),
],
})
}

Expand Down
28 changes: 10 additions & 18 deletions apps/content/content/docs/server/context.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -82,19 +82,14 @@ export const router = base.router({
// You can call this procedure directly without manually providing context
const output = await router.getting()

import { handleFetchRequest, createORPCHandler } from '@orpc/server/fetch'
import { createOpenAPIServerlessHandler, createOpenAPIServerHandler } from '@orpc/openapi/fetch'
import { ORPCHandler } from '@orpc/server/fetch'
import { OpenAPIServerlessHandler, OpenAPIServerHandler } from '@orpc/openapi/fetch'

const orpcHandler = new ORPCHandler(router)

export function fetch(request: Request) {
// No need to pass context; middleware handles it
return handleFetchRequest({
router,
request,
handlers: [
createORPCHandler(),
createOpenAPIServerlessHandler()
],
})
return orpcHandler.fetch(request)
}
```

Expand All @@ -106,8 +101,8 @@ rather than relying on global mechanisms like `headers` or `cookies` in Next.js.

```ts twoslash
import { os, ORPCError, createProcedureClient } from '@orpc/server'
import { handleFetchRequest, createORPCHandler } from '@orpc/server/fetch'
import { createOpenAPIServerlessHandler, createOpenAPIServerHandler } from '@orpc/openapi/fetch'
import { ORPCHandler } from '@orpc/server/fetch'
import { OpenAPIServerlessHandler, OpenAPIServerHandler } from '@orpc/openapi/fetch'

type ORPCContext = { user?: { id: string }, db: 'fake-db' }

Expand All @@ -133,17 +128,14 @@ export const router = base.router({
}),
})

const orpcHandler = new ORPCHandler(router)

export function fetch(request: Request) {
// Initialize context explicitly for each request
const db = 'fake-db' as const
const user = request.headers.get('Authorization') ? { id: 'example' } : undefined

return handleFetchRequest({
router,
request,
context: { db, user },
handlers: [createORPCHandler(), createOpenAPIServerlessHandler()],
})
return orpcHandler.fetch(request, { context: { db, user } })
}

// If you want to call this procedure or use as server action
Expand Down
132 changes: 74 additions & 58 deletions apps/content/content/docs/server/integrations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,22 @@ Whether you're targeting serverless, edge environments, or traditional backends,
## Quick Example

```ts twoslash
import { handleFetchRequest, createORPCHandler } from '@orpc/server/fetch'
import { createOpenAPIServerlessHandler, createOpenAPIServerHandler } from '@orpc/openapi/fetch'
import { ORPCHandler, CompositeHandler } from '@orpc/server/fetch'
import { OpenAPIServerlessHandler } from '@orpc/openapi/fetch'
import { router } from 'examples/server'
import { ZodCoercer } from '@orpc/zod'

const openapiHandler = new OpenAPIServerlessHandler(router, {
schemaCoercers: [
new ZodCoercer(),
],
})
const orpcHandler = new ORPCHandler(router)
const compositeHandler = new CompositeHandler([openapiHandler, orpcHandler])

export function fetch(request: Request) {
return handleFetchRequest({
router,
request,
return compositeHandler.fetch(request, {
context: {},
// prefix: '/api', // Optionally define a route prefix
handlers: [
createORPCHandler(),
createOpenAPIServerlessHandler(),
],
})
}
```
Expand All @@ -37,23 +39,25 @@ Node.js doesn't provide native support for creating server with [Fetch API](http
but you can easily use [@whatwg-node/server](https://npmjs.com/package/@whatwg-node/server) as an adapter.

```ts twoslash
import { handleFetchRequest, createORPCHandler } from '@orpc/server/fetch'
import { createOpenAPIServerlessHandler, createOpenAPIServerHandler } from '@orpc/openapi/fetch'
import { createServer } from 'node:http'
import { createServerAdapter } from '@whatwg-node/server'
import { ORPCHandler, CompositeHandler } from '@orpc/server/fetch'
import { OpenAPIServerlessHandler } from '@orpc/openapi/fetch'
import { router } from 'examples/server'
import { ZodCoercer } from '@orpc/zod'

const openapiHandler = new OpenAPIServerlessHandler(router, {
schemaCoercers: [
new ZodCoercer(),
],
})
const orpcHandler = new ORPCHandler(router)
const compositeHandler = new CompositeHandler([openapiHandler, orpcHandler])

const server = createServer(
createServerAdapter((request: Request) => {
return handleFetchRequest({
router,
request,
return compositeHandler.fetch(request, {
context: {},
// prefix: '/api',
handlers: [
createORPCHandler(),
createOpenAPIServerlessHandler(),
],
})
})
)
Expand All @@ -66,24 +70,27 @@ server.listen(3000, () => {
## Express.js

```ts twoslash
import { handleFetchRequest, createORPCHandler } from '@orpc/server/fetch'
import { createOpenAPIServerlessHandler, createOpenAPIServerHandler } from '@orpc/openapi/fetch'
import { createServerAdapter } from '@whatwg-node/server'
import express from 'express'
import { createServerAdapter } from '@whatwg-node/server'
import { ORPCHandler, CompositeHandler } from '@orpc/server/fetch'
import { OpenAPIServerlessHandler } from '@orpc/openapi/fetch'
import { router } from 'examples/server'
import { ZodCoercer } from '@orpc/zod'

const openapiHandler = new OpenAPIServerlessHandler(router, {
schemaCoercers: [
new ZodCoercer(),
],
})
const orpcHandler = new ORPCHandler(router)
const compositeHandler = new CompositeHandler([openapiHandler, orpcHandler])

const app = express()

app.all('/api/*', createServerAdapter((request: Request) => {
return handleFetchRequest({
router,
request,
return compositeHandler.fetch(request, {
context: {},
prefix: '/api',
handlers: [
createORPCHandler(),
createOpenAPIServerHandler(),
],
})
}))

Expand All @@ -96,23 +103,26 @@ app.listen(3000, () => {

```ts twoslash
import { Hono } from 'hono'
import { handleFetchRequest, createORPCHandler } from '@orpc/server/fetch'
import { createOpenAPIServerlessHandler, createOpenAPIServerHandler } from '@orpc/openapi/fetch'
import { ORPCHandler, CompositeHandler } from '@orpc/server/fetch'
import { OpenAPIServerlessHandler } from '@orpc/openapi/fetch'
import { router } from 'examples/server'
import { ZodCoercer } from '@orpc/zod'

const openapiHandler = new OpenAPIServerlessHandler(router, {
schemaCoercers: [
new ZodCoercer(),
],
})
const orpcHandler = new ORPCHandler(router)
const compositeHandler = new CompositeHandler([openapiHandler, orpcHandler])

const app = new Hono()

app.get('/api/*', (c) => {
return handleFetchRequest({
router,
request: c.req.raw,
return compositeHandler.fetch(c.req.raw, {
prefix: '/api',
context: {},
handlers: [
createORPCHandler(),
createOpenAPIServerlessHandler(),
],
})
})
})

export default app
Expand All @@ -121,20 +131,23 @@ export default app
## Next.js

```ts title="app/api/[...orpc]/route.ts" twoslash
import { handleFetchRequest, createORPCHandler } from '@orpc/server/fetch'
import { createOpenAPIServerlessHandler, createOpenAPIServerHandler } from '@orpc/openapi/fetch'
import { ORPCHandler, CompositeHandler } from '@orpc/server/fetch'
import { OpenAPIServerlessHandler } from '@orpc/openapi/fetch'
import { router } from 'examples/server'
import { ZodCoercer } from '@orpc/zod'

const openapiHandler = new OpenAPIServerlessHandler(router, {
schemaCoercers: [
new ZodCoercer(),
],
})
const orpcHandler = new ORPCHandler(router)
const compositeHandler = new CompositeHandler([openapiHandler, orpcHandler])

export function GET(request: Request) {
return handleFetchRequest({
router,
request,
return compositeHandler.fetch(request, {
prefix: '/api',
context: {},
handlers: [
createORPCHandler(),
createOpenAPIServerlessHandler(),
],
})
}

Expand All @@ -147,21 +160,24 @@ export const PATCH = GET
## Cloudflare Workers

```ts twoslash
import { handleFetchRequest, createORPCHandler } from '@orpc/server/fetch'
import { createOpenAPIServerlessHandler, createOpenAPIServerHandler } from '@orpc/openapi/fetch'
import { ORPCHandler, CompositeHandler } from '@orpc/server/fetch'
import { OpenAPIServerlessHandler } from '@orpc/openapi/fetch'
import { router } from 'examples/server'
import { ZodCoercer } from '@orpc/zod'

const openapiHandler = new OpenAPIServerlessHandler(router, {
schemaCoercers: [
new ZodCoercer(),
],
})
const orpcHandler = new ORPCHandler(router)
const compositeHandler = new CompositeHandler([openapiHandler, orpcHandler])

export default {
async fetch(request: Request) {
return handleFetchRequest({
router,
request,
return compositeHandler.fetch(request, {
prefix: '/',
context: {},
handlers: [
createORPCHandler(),
createOpenAPIServerlessHandler(),
],
})
},
}
Expand Down
3 changes: 3 additions & 0 deletions apps/content/content/docs/server/server-action.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,11 @@ React forms integrate seamlessly with server actions using `createFormAction`. T
// on server
import { createFormAction } from '@orpc/next'
import { updateUser } from 'examples/server-action'
import { ZodCoercer } from '@orpc/zod'

const updateUserFA = createFormAction({
procedure: updateUser,
schemaCoercers: [new ZodCoercer()],
onSuccess: () => {
// redirect('/some-where')
}
Expand Down
1 change: 1 addition & 0 deletions apps/content/content/home/landing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ const { execute, isPending, isError, error, output, input, status } = useAction(
// use as a form action
const gettingFA = createFormAction({
procedure: getting,
schemaCoercers: [new ZodCoercer()],
onSuccess: () => redirect('/some-where')
})

Expand Down
33 changes: 17 additions & 16 deletions apps/content/examples/contract.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import type { InferContractRouterInputs, InferContractRouterOutputs } from '@orpc/contract'
import { oc } from '@orpc/contract'
import { oz } from '@orpc/zod'
import { z } from 'zod'

// Implement the contract

import { ORPCError, os } from '@orpc/server'
import { oz, ZodCoercer } from '@orpc/zod'
import { z } from 'zod'

// Define your contract first
// This contract can replace server router in most-case
Expand Down Expand Up @@ -63,6 +60,8 @@ export const contract = oc.router({
export type Inputs = InferContractRouterInputs<typeof contract>
export type Outputs = InferContractRouterOutputs<typeof contract>

// Implement the contract

export type Context = { user?: { id: string } }
export const base = os.context<Context>()
export const pub /** os with ... */ = base.contract(contract) // Ensure every implement must be match contract
Expand Down Expand Up @@ -114,27 +113,29 @@ export const router = pub.router({
},
})

// Expose apis to the internet with fetch handler
import { createOpenAPIServerlessHandler } from '@orpc/openapi/fetch'
import { createORPCHandler, handleFetchRequest } from '@orpc/server/fetch'
// Modern runtime that support fetch api like deno, bun, cloudflare workers, even node can used
import { createServer } from 'node:http'
// Expose apis to the internet with fetch handler
import { OpenAPIServerlessHandler } from '@orpc/openapi/fetch'
import { CompositeHandler, ORPCHandler } from '@orpc/server/fetch'
import { createServerAdapter } from '@whatwg-node/server'

const openapiHandler = new OpenAPIServerlessHandler(router, {
schemaCoercers: [
new ZodCoercer(),
],
})
const orpcHandler = new ORPCHandler(router)
const compositeHandler = new CompositeHandler([openapiHandler, orpcHandler])

const server = createServer(
createServerAdapter((request: Request) => {
const url = new URL(request.url)

if (url.pathname.startsWith('/api')) {
return handleFetchRequest({
router,
request,
prefix: '/api',
return compositeHandler.fetch(request, {
context: {},
handlers: [
createORPCHandler(),
createOpenAPIServerlessHandler(),
],
prefix: '/api',
})
}

Expand Down
Loading
Loading