make trpc-nuxt flexible by exporting server and client functions instead of nuxt modules

This commit is contained in:
Robert Soriano
2022-10-29 21:50:27 -07:00
parent bad2de134b
commit ae920a37fd
20 changed files with 713 additions and 780 deletions

2
client.d.ts vendored
View File

@@ -1 +1 @@
export * from './dist/runtime/client'
export * from './dist/client'

View File

@@ -1,24 +1,22 @@
{
"name": "trpc-nuxt",
"type": "module",
"version": "0.3.1",
"packageManager": "pnpm@7.5.0",
"license": "MIT",
"main": "./dist/module.cjs",
"types": "./dist/types.d.ts",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": "./dist/module.mjs",
"require": "./dist/module.cjs"
},
"./server": {
"import": "./dist/runtime/server.mjs",
"types": "./dist/runtime/server.d.ts"
"require": "./dist/index.js",
"import": "./dist/index.mjs",
"types": "./dist/index.d.ts"
},
"./client": {
"import": "./dist/runtime/client.mjs",
"types": "./dist/runtime/client.d.ts"
"require": "./dist/client.js",
"import": "./dist/client.mjs",
"types": "./dist/client.d.ts"
}
},
"files": [
@@ -27,40 +25,33 @@
],
"scripts": {
"prepublishOnly": "nr build",
"build": "nuxt-module-build",
"play": "nr build && nuxi dev playground",
"build:playground": "nuxi build playground",
"build": "tsup",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"release": "bumpp --commit --push --tag && npm publish",
"prepare": "nuxi prepare playground"
"release": "bumpp --commit --push --tag && npm publish"
},
"peerDependencies": {
"@trpc/client": "^10.0.0-proxy-beta.21",
"@trpc/server": "^10.0.0-proxy-beta.21"
"@trpc/server": "^10.0.0-proxy-beta.21",
"nuxt": "^3.0.0-rc.12"
},
"dependencies": {
"@nuxt/kit": "3.0.0-rc.12",
"dedent": "^0.7.0",
"defu": "^6.1.0",
"h3": "^0.8.5",
"ohash": "^0.1.5",
"pathe": "^0.3.9",
"ohmyfetch": "^0.4.20",
"ufo": "^0.8.6"
},
"devDependencies": {
"@antfu/eslint-config": "^0.23.1",
"@antfu/ni": "^0.16.2",
"@nuxt/module-builder": "^0.2.0",
"@trpc/client": "10.0.0-rc.1",
"@trpc/server": "10.0.0-rc.1",
"@types/dedent": "^0.7.0",
"bumpp": "^7.2.0",
"eslint": "^8.25.0",
"nuxt": "3.0.0-rc.12",
"pnpm": "^7.5.0",
"trpc-nuxt": "workspace:*",
"zod": "^3.19.1"
"tsup": "^6.3.0",
"typescript": "4.5.4"
},
"eslintConfig": {
"extends": "@antfu",

View File

@@ -1,8 +1,5 @@
import Module from '../src/module'
// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
modules: [Module],
runtimeConfig: {
baseURL: '',
},

View File

@@ -1,4 +1,18 @@
{
"name": "playground",
"private": true
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"dependencies": {
"trpc-nuxt": "workspace:*",
"zod": "^3.19.1"
},
"devDependencies": {
"nuxt": "3.0.0-rc.12"
}
}

View File

@@ -1,7 +1,6 @@
<script setup lang="ts">
const { $client } = useNuxtApp()
// const headers = useClientHeaders()
// const { data: todos, pending, error, refresh } = await useAsyncQuery(['getTodos'])
// const addHeader = () => {
// headers.value.authorization = 'Bearer abcdefghijklmnop'
@@ -13,7 +12,7 @@ const addTodo = async () => {
try {
const result = await $client.todo.addTodo.mutate({
id: 69,
id: Date.now(),
userId: 69,
title,
completed: false,
@@ -26,7 +25,7 @@ const addTodo = async () => {
}
}
const { data: todos, pending, error, refresh } = await $client.getTodos.query()
const { data: todos, pending, error, refresh } = await $client.todo.getTodos.query()
</script>
<template>

View File

@@ -1,12 +1,12 @@
import { httpBatchLink } from '@trpc/client'
import { createTRPCNuxtProxyClient } from 'trpc-nuxt/client'
import type { AppRouter } from '~~/server/trpc'
import type { AppRouter } from '~~/server/trpc/routers'
export default defineNuxtPlugin(() => {
const client = createTRPCNuxtProxyClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000/trpc',
url: 'http://localhost:3000/api/trpc',
}),
],
})

View File

@@ -0,0 +1,12 @@
import { createNuxtApiHandler } from 'trpc-nuxt'
import { appRouter } from '../../trpc/routers'
export default createNuxtApiHandler({
router: appRouter,
createContext: async () => {
return {}
},
onError({ error }) {
console.log('Error', error)
},
})

View File

@@ -1,69 +1,25 @@
import { initTRPC } from '@trpc/server'
import { z } from 'zod'
import type { H3Event } from 'h3'
import type { inferAsyncReturnType } from '@trpc/server'
const baseURL = 'https://jsonplaceholder.typicode.com'
const t = initTRPC.context<any>().create()
const TodoShape = z.object({
userId: z.number(),
id: z.number(),
title: z.string(),
completed: z.boolean(),
})
export type Todo = z.infer<typeof TodoShape>
const t = initTRPC.context<Context>().create()
// We explicitly export the methods we use here
// This allows us to create reusable & protected base procedures
export const middleware = t.middleware
/**
* Create a router
* @see https://trpc.io/docs/v10/router
*/
export const router = t.router
/**
* Create an unprotected procedure
* @see https://trpc.io/docs/v10/procedures
**/
export const publicProcedure = t.procedure
const anotherRouter = router({
getTodo: publicProcedure
.input(z.number())
.query((req) => {
return $fetch<Todo>(`${baseURL}/todos/${req.input}`)
}),
addTodo: publicProcedure
.input(TodoShape)
.mutation((req) => {
return $fetch<Todo>(`${baseURL}/todos`, {
method: 'POST',
body: req.input,
})
}),
})
/**
* @see https://trpc.io/docs/v10/middlewares
*/
export const middleware = t.middleware
export const appRouter = router({
todo: anotherRouter,
getTodos: publicProcedure.query(() => {
return $fetch<Todo[]>(`${baseURL}/todos`)
}),
getTodo: publicProcedure
.input(z.number())
.query((req) => {
console.log('REQ', req)
return $fetch<Todo>(`${baseURL}/todos/${req.input}`)
}),
})
export type AppRouter = typeof appRouter
export async function createContext(event: H3Event) {
// Create your context based on the request object
// Will be available as `ctx` in all your resolvers
// This is just an example of something you'd might want to do in your ctx fn
// const x = useCookies(event)
console.log(event.req.headers)
return {
}
}
type Context = inferAsyncReturnType<typeof createContext>
/**
* @see https://trpc.io/docs/v10/merging-routers
*/
export const mergeRouters = t.mergeRouters

View File

@@ -0,0 +1,10 @@
import { router } from '..'
import { todoRouter } from './todo'
import { userRouter } from './user'
export const appRouter = router({
todo: todoRouter,
user: userRouter,
})
export type AppRouter = typeof appRouter

View File

@@ -0,0 +1,33 @@
import { z } from 'zod'
import { publicProcedure, router } from '..'
const baseURL = 'https://jsonplaceholder.typicode.com'
const TodoShape = z.object({
userId: z.number(),
id: z.number(),
title: z.string(),
completed: z.boolean(),
})
export type Todo = z.infer<typeof TodoShape>
export const todoRouter = router({
getTodos: publicProcedure
.query(() => {
return $fetch<Todo[]>(`${baseURL}/todos`)
}),
getTodo: publicProcedure
.input(z.number())
.query((req) => {
return $fetch<Todo>(`${baseURL}/todos/${req.input}`)
}),
addTodo: publicProcedure
.input(TodoShape)
.mutation((req) => {
return $fetch<Todo>(`${baseURL}/todos`, {
method: 'POST',
body: req.input,
})
}),
})

View File

@@ -0,0 +1,33 @@
import { z } from 'zod'
import { publicProcedure, router } from '..'
const baseURL = 'https://jsonplaceholder.typicode.com'
const UserShape = z.object({
id: z.number(),
name: z.string(),
username: z.string(),
email: z.string(),
})
export type User = z.infer<typeof UserShape>
export const userRouter = router({
getUsers: publicProcedure
.query(() => {
return $fetch<User[]>(`${baseURL}/users`)
}),
getUser: publicProcedure
.input(z.number())
.query((req) => {
return $fetch<User>(`${baseURL}/users/${req.input}`)
}),
addUser: publicProcedure
.input(UserShape)
.mutation((req) => {
return $fetch<User>(`${baseURL}/users`, {
method: 'POST',
body: req.input,
})
}),
})

4
playground/tsconfig.json Normal file
View File

@@ -0,0 +1,4 @@
{
// https://v3.nuxtjs.org/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
}

1160
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

1
server.d.ts vendored
View File

@@ -1 +0,0 @@
export * from './dist/runtime/server'

View File

@@ -7,8 +7,6 @@ import type {
import { createFlatProxy, createRecursiveProxy } from '@trpc/server/shared'
import { hash } from 'ohash'
import type { DecoratedProcedureRecord } from './types'
// @ts-expect-error: Nuxt internal
import { useAsyncData } from '#app'
/**
* Calculates the key used for `useAsyncData` call
@@ -40,12 +38,14 @@ export function createNuxtProxyDecoration<TRouter extends AnyRouter>(name: strin
const queryKey = getQueryKey(path, input)
if (lastArg === 'mutate') {
// @ts-ignore: nuxt internal
return useAsyncData(() => (client as any)[path][lastArg](input), {
...asyncDataOptions as Record<string, any>,
immediate: false,
})
}
// @ts-ignore: nuxt internal
return useAsyncData(queryKey, () => (client as any)[path][lastArg](input), asyncDataOptions as Record<string, any>)
})
}

1
src/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from './server'

View File

@@ -1,58 +0,0 @@
import { fileURLToPath } from 'url'
import { join } from 'pathe'
import { defu } from 'defu'
import dedent from 'dedent'
import { addServerHandler, addTemplate, defineNuxtModule } from '@nuxt/kit'
export interface ModuleOptions {
baseURL: string
endpoint: string
}
export default defineNuxtModule<ModuleOptions>({
meta: {
name: 'trpc-nuxt',
configKey: 'trpc',
},
defaults: {
baseURL: '',
endpoint: '/trpc',
},
async setup(options, nuxt) {
const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url))
nuxt.options.build.transpile.push(runtimeDir, '#build/trpc-handler')
const handlerPath = join(nuxt.options.buildDir, 'trpc-handler.ts')
const trpcOptionsPath = join(nuxt.options.srcDir, 'server/trpc')
// Final resolved configuration
const finalConfig = nuxt.options.runtimeConfig.public.trpc = defu(nuxt.options.runtimeConfig.public.trpc, {
baseURL: options.baseURL,
endpoint: options.endpoint,
})
addServerHandler({
route: `${finalConfig.endpoint}/*`,
handler: handlerPath,
})
addTemplate({
filename: 'trpc-handler.ts',
write: true,
getContents() {
return dedent`
import { createTRPCHandler } from 'trpc-nuxt/server'
import * as functions from '${trpcOptionsPath}'
export default createTRPCHandler({
...functions,
router: functions.appRouter,
endpoint: '${finalConfig.endpoint}'
})
`
},
})
},
})

View File

@@ -37,18 +37,18 @@ export interface OnErrorPayload<TRouter extends AnyRouter> {
export type OnErrorFn<TRouter extends AnyRouter> = (opts: OnErrorPayload<TRouter>) => void
export function createTRPCHandler<Router extends AnyRouter>({
export function createNuxtApiHandler<TRouter extends AnyRouter>({
router,
createContext,
responseMeta,
onError,
endpoint,
url = '/api/trpc',
}: {
router: Router
createContext?: CreateContextFn<Router>
responseMeta?: ResponseMetaFn<Router>
onError?: OnErrorFn<Router>
endpoint: string
router: TRouter
createContext?: CreateContextFn<TRouter>
responseMeta?: ResponseMetaFn<TRouter>
onError?: OnErrorFn<TRouter>
url?: string
}) {
return defineEventHandler(async (event) => {
const {
@@ -66,7 +66,7 @@ export function createTRPCHandler<Router extends AnyRouter>({
body: isMethod(event, 'GET') ? null : await readBody(event),
query: $url.searchParams,
},
path: $url.pathname.substring(endpoint.length + 1),
path: $url.pathname.substring(url.length + 1),
createContext: async () => createContext?.(event),
responseMeta,
onError: (o) => {

10
tsup.config.ts Normal file
View File

@@ -0,0 +1,10 @@
import { defineConfig } from 'tsup'
export default defineConfig({
entry: ['src/index.ts', 'src/client.ts'],
format: ['cjs', 'esm'],
splitting: false,
clean: true,
external: ['#app'],
dts: true,
})