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

View File

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

View File

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

View File

@@ -1,12 +1,12 @@
import { httpBatchLink } from '@trpc/client' import { httpBatchLink } from '@trpc/client'
import { createTRPCNuxtProxyClient } from 'trpc-nuxt/client' import { createTRPCNuxtProxyClient } from 'trpc-nuxt/client'
import type { AppRouter } from '~~/server/trpc' import type { AppRouter } from '~~/server/trpc/routers'
export default defineNuxtPlugin(() => { export default defineNuxtPlugin(() => {
const client = createTRPCNuxtProxyClient<AppRouter>({ const client = createTRPCNuxtProxyClient<AppRouter>({
links: [ links: [
httpBatchLink({ 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 { 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(), * Create a router
id: z.number(), * @see https://trpc.io/docs/v10/router
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
export const router = t.router export const router = t.router
/**
* Create an unprotected procedure
* @see https://trpc.io/docs/v10/procedures
**/
export const publicProcedure = t.procedure export const publicProcedure = t.procedure
const anotherRouter = router({ /**
getTodo: publicProcedure * @see https://trpc.io/docs/v10/middlewares
.input(z.number()) */
.query((req) => { export const middleware = t.middleware
return $fetch<Todo>(`${baseURL}/todos/${req.input}`)
}),
addTodo: publicProcedure
.input(TodoShape)
.mutation((req) => {
return $fetch<Todo>(`${baseURL}/todos`, {
method: 'POST',
body: req.input,
})
}),
})
export const appRouter = router({ /**
todo: anotherRouter, * @see https://trpc.io/docs/v10/merging-routers
getTodos: publicProcedure.query(() => { */
return $fetch<Todo[]>(`${baseURL}/todos`) export const mergeRouters = t.mergeRouters
}),
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>

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 { createFlatProxy, createRecursiveProxy } from '@trpc/server/shared'
import { hash } from 'ohash' import { hash } from 'ohash'
import type { DecoratedProcedureRecord } from './types' import type { DecoratedProcedureRecord } from './types'
// @ts-expect-error: Nuxt internal
import { useAsyncData } from '#app'
/** /**
* Calculates the key used for `useAsyncData` call * Calculates the key used for `useAsyncData` call
@@ -40,12 +38,14 @@ export function createNuxtProxyDecoration<TRouter extends AnyRouter>(name: strin
const queryKey = getQueryKey(path, input) const queryKey = getQueryKey(path, input)
if (lastArg === 'mutate') { if (lastArg === 'mutate') {
// @ts-ignore: nuxt internal
return useAsyncData(() => (client as any)[path][lastArg](input), { return useAsyncData(() => (client as any)[path][lastArg](input), {
...asyncDataOptions as Record<string, any>, ...asyncDataOptions as Record<string, any>,
immediate: false, immediate: false,
}) })
} }
// @ts-ignore: nuxt internal
return useAsyncData(queryKey, () => (client as any)[path][lastArg](input), asyncDataOptions as Record<string, any>) 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 type OnErrorFn<TRouter extends AnyRouter> = (opts: OnErrorPayload<TRouter>) => void
export function createTRPCHandler<Router extends AnyRouter>({ export function createNuxtApiHandler<TRouter extends AnyRouter>({
router, router,
createContext, createContext,
responseMeta, responseMeta,
onError, onError,
endpoint, url = '/api/trpc',
}: { }: {
router: Router router: TRouter
createContext?: CreateContextFn<Router> createContext?: CreateContextFn<TRouter>
responseMeta?: ResponseMetaFn<Router> responseMeta?: ResponseMetaFn<TRouter>
onError?: OnErrorFn<Router> onError?: OnErrorFn<TRouter>
endpoint: string url?: string
}) { }) {
return defineEventHandler(async (event) => { return defineEventHandler(async (event) => {
const { const {
@@ -66,7 +66,7 @@ export function createTRPCHandler<Router extends AnyRouter>({
body: isMethod(event, 'GET') ? null : await readBody(event), body: isMethod(event, 'GET') ? null : await readBody(event),
query: $url.searchParams, query: $url.searchParams,
}, },
path: $url.pathname.substring(endpoint.length + 1), path: $url.pathname.substring(url.length + 1),
createContext: async () => createContext?.(event), createContext: async () => createContext?.(event),
responseMeta, responseMeta,
onError: (o) => { 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,
})