Skip to content

Commit

Permalink
feat(contract, server)!: rewrite builders (#110)
Browse files Browse the repository at this point in the history
* wip

* types tests

* wip

* 100% test coverage

* fix types

* fix types

* remove ~type and decorated method

* procedure builders

* rename

* router builders

* builder

* remove redundant method

* wip

* router

* wip

* fix isLazy

* procedure tests

* router tests

* builder tests

* 100% test coverage

* wip

* fixed

* fix types tests

* wip

* fix types

* ORPCErrorConstructorMap

* improve

* wip

* improve

* improve

* wip

* improve

* improve

* fix

* prefix lazy

* fix

* procedure implementer

* type tests

* tests

* index

* variants tests

* setRouterContract

* fix

* docs

* docs

* tests

* clear mock before each tests

* lint fixed

* fix docs

* fix restrict context

* fix types

* improve middlewares
  • Loading branch information
unnoq authored Jan 31, 2025
1 parent ed15210 commit 32cb70c
Show file tree
Hide file tree
Showing 212 changed files with 6,439 additions and 9,509 deletions.
7 changes: 3 additions & 4 deletions apps/content/content/docs/contract-first.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,15 @@ export type Outputs = InferContractRouterOutputs<typeof contract>
With your contract defined, implement the server logic:
```ts twoslash
import { os, ORPCError } from '@orpc/server'
import { implement, ORPCError } from '@orpc/server'
import { contract } from 'examples/contract'

export const pub = os.contract(contract) // Ensure every implement must be match contract
export const authed = os
export const pub = implement(contract) // Ensure every implement must be match contract
export const authed = pub
.use(({ context, path, next }, input) => {
/** put auth logic here */
return next({})
})
.contract(contract)

export const router = pub.router({
getUser: pub.getUser.handler(({ input, context }) => {
Expand Down
6 changes: 2 additions & 4 deletions apps/content/content/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ import { z } from 'zod'
export type Context = { user?: { id: string } }

// global pub, authed completely optional
export const pub = os.context<Context>()
export const pub = os.$context<Context>()
export const authed = pub.use(({ context, path, next }, input) => {
/** put auth logic here */
return next({})
Expand Down Expand Up @@ -98,9 +98,7 @@ export const router = pub.router({
)
.use(async ({ context, path, next }, input) => {
if (!context.user) {
throw new ORPCError({
code: 'UNAUTHORIZED',
})
throw new ORPCError('UNAUTHORIZED')
}

const result = await next({
Expand Down
2 changes: 1 addition & 1 deletion apps/content/content/docs/openapi/generator.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { z } from 'zod'
export type Context = { user?: { id: string } }

// global pub, authed completely optional
export const pub = os.context<Context>()
export const pub = os.$context<Context>()
export const authed = pub.use(({ context, path, next }, input) => {
/** put auth logic here */
return next({})
Expand Down
12 changes: 6 additions & 6 deletions apps/content/content/docs/server/context.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ import { z } from 'zod'
import { headers } from 'next/headers'

type ORPCContext = { db: 'fake-db', user?: { id: string } }
const base = os.context<ORPCContext>() // define `initial context`
const base = os.$context<ORPCContext>() // define `initial context`

const pub = os.context<ORPCContext>().use(async ({ context, path, next }, input) => {
const pub = os.$context<ORPCContext>().use(async ({ context, path, next }, input) => {
const headersList = await headers()

// the `user` is `middleware context`, because it is created by middleware
Expand Down Expand Up @@ -58,13 +58,13 @@ const base = os.use(async ({ context, path, next }, input) => {
})

const authMid = os
.context<{ db: string }>() // this middleware depends on some context
.$context<{ db: string }>() // this middleware depends on some context
.middleware(async ({ context, next, path }, input) => {
const headersList = await headers()
const user = headersList.get('Authorization') ? { id: 'example' } : undefined

if (!user) {
throw new ORPCError({ code: 'UNAUTHORIZED' })
throw new ORPCError('UNAUTHORIZED')
}

return next({
Expand Down Expand Up @@ -108,11 +108,11 @@ import { OpenAPIServerlessHandler, OpenAPIServerHandler } from '@orpc/openapi/fe

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

const base = os.context<ORPCContext>()
const base = os.$context<ORPCContext>()

const authMid = base.middleware(({ context, next, path }, input) => {
if(!context.user) {
throw new ORPCError({ code: 'UNAUTHORIZED' })
throw new ORPCError('UNAUTHORIZED')
}

return next({
Expand Down
7 changes: 3 additions & 4 deletions apps/content/content/docs/server/contract.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -78,18 +78,17 @@ export const contract = oc.router({
All `server` features are available, except the input, output, and route parts, which are defined in the contract.

```ts twoslash
import { os } from '@orpc/server'
import { implement } from '@orpc/server'
import { contract } from 'examples/contract'

export type Context = { user?: { id: string } }
export const base = os.context<Context>()
export const pub = base.contract(contract) // Ensure every implement must be match contract
export const base = implement(contract).$context<Context>()
export const pub = base
export const authed = base
.use(({ context, path, next }, input) => {
/** put auth logic here */
return next({})
})
.contract(contract)

export const router = pub.router({
getUser: pub.getUser.handler(({ input, context }) => {
Expand Down
5 changes: 2 additions & 3 deletions apps/content/content/docs/server/error-handling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const createPost = os
throw errors.CONFLICT({ data: { why: 'some reason' } })

// is the same as, but this is not typed
throw new ORPCError({ code: 'CONFLICT', data: { why: 'some reason' } })
throw new ORPCError('CONFLICT', { data: { why: 'some reason' } })

// throw errors.ANY_CODE()
})
Expand Down Expand Up @@ -82,8 +82,7 @@ const ping = os
}
})
.handler(({ input, context }) => {
throw new ORPCError({
code: 'NOT_FOUND',
throw new ORPCError('NOT_FOUND', {
message: 'Not found',
status: 404, // Optional: custom default behavior
data: { something: 'include in the body and send to the client' } // pass data to the client
Expand Down
4 changes: 2 additions & 2 deletions apps/content/content/docs/server/lazy.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Here's how you can set up and use them:
```typescript twoslash
import { os, call } from '@orpc/server'

const pub = os.context<{ user?: { id: string } }>()
const pub = os.$context<{ user?: { id: string } }>()

// Define a router with lazy loading
const router = pub.router({
Expand Down Expand Up @@ -48,7 +48,7 @@ const output = await call(router.lazy.getUser, { id: '123' })

```ts twoslash
import { os } from '@orpc/server'
const pub = os.context<{ user?: { id: string } }>()
const pub = os.$context<{ user?: { id: string } }>()

const lazy = pub.prefix('/some').lazy(() => import('examples/server'))
```
Expand Down
9 changes: 4 additions & 5 deletions apps/content/content/docs/server/middleware.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,11 @@ import { os, ORPCError } from '@orpc/server'

export type Context = { user?: { id: string } }

export const pub = os.context<Context>()
export const pub = os.$context<Context>()

const authMiddleware = pub.middleware(async ({ context, next, path }, input) => {
if (!context.user) {
throw new ORPCError({
code: 'UNAUTHORIZED',
throw new ORPCError('UNAUTHORIZED', {
message: 'You need to log in first',
})
}
Expand Down Expand Up @@ -86,13 +85,13 @@ type Context = {
}
}

export const pub = os.context<Context>()
export const pub = os.$context<Context>()

// Any procedure using this middleware will infer context.user as NonNullable<typeof context['user']>
const authMiddleware = pub
.middleware(async ({ context, next, path }, input) => {
if (!context.user) {
throw new ORPCError({ code: 'UNAUTHORIZED' })
throw new ORPCError('UNAUTHORIZED')
}

return next({
Expand Down
15 changes: 6 additions & 9 deletions apps/content/content/docs/server/procedure.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,11 @@ import { os, ORPCError } from '@orpc/server'

// Define context type for full type inference
const pub = os
.context<{user?: {id: string}}>()
.config({ // optional, change some default configuration
initialRoute: {
method: 'DELETE', // change default method to DELETE
inputStructure: 'detailed', // change default input structure to detailed
outputStructure: 'detailed' // change default output structure to detailed
}
.$context<{user?: {id: string}}>() // optional, set config
.$route({ // optional, change initial route
method: 'DELETE', // change default method to DELETE
inputStructure: 'detailed', // change default input structure to detailed
outputStructure: 'detailed' // change default output structure to detailed
})

const getUser = pub
Expand All @@ -40,8 +38,7 @@ const getUser = pub

// Example: Authentication check
if (!context.user) {
throw new ORPCError({
code: 'UNAUTHORIZED',
throw new ORPCError('UNAUTHORIZED', {
message: 'Authentication required'
})
}
Expand Down
4 changes: 2 additions & 2 deletions apps/content/content/docs/server/server-action.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ When calling `.actionable()`, you can pass a context function that provides addi
import { os } from '@orpc/server'
import { z } from 'zod'

const pub = os.context<{ db: string }>()
const pub = os.$context<{ db: string }>()

export const getting = pub
.input(z.object({
Expand Down Expand Up @@ -77,7 +77,7 @@ const authed = os.use(async ({ next }) => {
const user = headersList.get('Authorization') ? { id: 'example' } : undefined

if (!user) {
throw new ORPCError({ code: 'UNAUTHORIZED' })
throw new ORPCError('UNAUTHORIZED')
}

return next({
Expand Down
7 changes: 4 additions & 3 deletions apps/content/content/home/landing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,12 @@ convert `1992` into a `bigint` and seamlessly parse objects like `user`.
```tsx
type ORPCContext = { db: DB, user?: { id: string } }

const pub = os.context<ORPCContext>()
const pub = os.$context<ORPCContext>()

const createPost = pub
.use(({ context, path, next }, input) => {
if(!context.user){
throw new ORPCError({ code: 'UNAUTHORIZED' })
throw new ORPCError('UNAUTHORIZED')
}

return next({
Expand All @@ -144,8 +144,9 @@ const getUserContract = oc
.input({/*something*/})
.output({/*something*/})

const os = implement(getUserContract)

const getUser = os
.contract(getUserContract)
.handler(({ input }) => /* handle user */)
```

Expand Down
11 changes: 4 additions & 7 deletions apps/content/examples/contract.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { InferContractRouterInputs, InferContractRouterOutputs } from '@orpc/contract'
import { oc } from '@orpc/contract'
import { ORPCError, os } from '@orpc/server'
import { implement, ORPCError } from '@orpc/server'
import { oz, ZodCoercer } from '@orpc/zod'
import { z } from 'zod'

Expand Down Expand Up @@ -68,14 +68,13 @@ export type Outputs = InferContractRouterOutputs<typeof contract>
// Implement the contract

export type Context = { user?: { id: string } }
export const base = os.context<Context>()
export const pub = base.contract(contract) // Ensure every implement must be match contract
export const base = implement(contract).$context<Context>()
export const pub = base
export const authed = base
.use(({ context, path, next }, input) => {
/** put auth logic here */
return next({})
})
.contract(contract)

export const router = pub.router({
getUser: pub.getUser.handler(({ input, context }) => {
Expand All @@ -89,9 +88,7 @@ export const router = pub.router({
getPost: pub.posts.getPost
.use(async ({ context, path, next }, input) => {
if (!context.user) {
throw new ORPCError({
code: 'UNAUTHORIZED',
})
throw new ORPCError('UNAUTHORIZED')
}

const result = await next({
Expand Down
6 changes: 3 additions & 3 deletions apps/content/examples/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import { z } from 'zod'

export type Context = { user?: { id: string } }

export const pub = os.context<Context>()
export const pub = os.$context<Context>()

export const authMiddleware = pub.middleware(async ({ context, next, path, procedure }, input) => {
if (!context.user) {
throw new ORPCError({ code: 'UNAUTHORIZED' })
throw new ORPCError('UNAUTHORIZED')
}

const result = await next({ context: { user: context.user } })
Expand All @@ -23,7 +23,7 @@ export const canEditPost = authMiddleware.concat(
// Now you expect to have id in input
async ({ context, next }, input: { id: string }) => {
if (context.user.id !== input.id) {
throw new ORPCError({ code: 'UNAUTHORIZED' })
throw new ORPCError('UNAUTHORIZED')
}

return next({})
Expand Down
6 changes: 2 additions & 4 deletions apps/content/examples/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { z } from 'zod'
export type Context = { user?: { id: string } }

// global pub, authed completely optional
export const pub = os.context<Context>()
export const pub = os.$context<Context>()
export const authed = pub.use(({ context, path, next }, input) => {
/** put auth logic here */
return next()
Expand Down Expand Up @@ -46,9 +46,7 @@ export const router = pub.router({
)
.use(async ({ context, path, next }, input) => {
if (!context?.user) {
throw new ORPCError({
code: 'UNAUTHORIZED',
})
throw new ORPCError('UNAUTHORIZED')
}

const result = await next({
Expand Down
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export default antfu({
'react-refresh/only-export-components': 'off',
'react/prefer-destructuring-assignment': 'off',
'react/no-context-provider': 'off',
'ts/method-signature-style': ['error', 'method'],
},
}, {
files: ['**/*.test.ts', '**/*.test.tsx', '**/*.test-d.ts', '**/*.test-d.tsx', 'apps/content/examples/**', 'playgrounds/**'],
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"@testing-library/jest-dom": "^6.6.2",
"@testing-library/react": "^16.0.1",
"@types/node": "^22.9.0",
"@vitest/coverage-v8": "^2.1.1",
"@vitest/coverage-v8": "^3.0.4",
"eslint": "^9.15.0",
"eslint-plugin-format": "^0.1.2",
"eslint-plugin-react-hooks": "^5.0.0",
Expand All @@ -41,7 +41,7 @@
"simple-git-hooks": "^2.11.1",
"tsup": "^8.3.0",
"typescript": "5.7.2",
"vitest": "^2.1.8"
"vitest": "^3.0.4"
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"
Expand Down
Loading

0 comments on commit 32cb70c

Please sign in to comment.