feat: add file routing option

This commit is contained in:
wobsoriano
2022-11-05 19:22:19 -07:00
parent d9c8f877d3
commit 750783e860
33 changed files with 589 additions and 348 deletions

2
client.d.ts vendored
View File

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

7
examples/custom/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
node_modules
*.log*
.nuxt
.nitro
.cache
.output
.env

31
examples/custom/README.md Normal file
View File

@@ -0,0 +1,31 @@
# dev playground
## Setup
Make sure to install the dependencies:
```bash
pnpm install
```
## Development Server
Start the development server on http://localhost:3000
```bash
pnpm dev
```
## Production
Build the application for production:
```bash
pnpm build
```
Locally preview production build:
```bash
pnpm preview
```

5
examples/custom/app.vue Normal file
View File

@@ -0,0 +1,5 @@
<template>
<div>
<NuxtPage />
</div>
</template>

View File

@@ -0,0 +1,7 @@
// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
modules: ['trpc-nuxt'],
trpc: {
enableFileRouting: false
}
})

View File

@@ -0,0 +1,20 @@
{
"name": "custom",
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview"
},
"dependencies": {
"@trpc/client": "10.0.0-rc.4",
"@trpc/server": "10.0.0-rc.4",
"superjson": "^1.11.0",
"trpc-nuxt": "workspace:*",
"zod": "^3.19.1"
},
"devDependencies": {
"nuxt": "3.0.0-rc.13"
}
}

View File

@@ -0,0 +1,67 @@
<script setup lang="ts">
const { $client } = useNuxtApp()
// const headers = useClientHeaders()
// const addHeader = () => {
// headers.value.authorization = 'Bearer abcdefghijklmnop'
// console.log(headers.value)
// }
const addTodo = async () => {
const title = Math.random().toString(36).slice(2, 7)
try {
const x = await $client.todo.addTodo.mutate({
id: Date.now(),
userId: 69,
title,
completed: false
})
console.log(x.data.value)
} catch (e) {
console.log(e)
}
}
const { data: todos, pending, error, refresh } = await $client.todo.getTodos.query(undefined, {
trpc: {
abortOnUnmount: true
}
})
</script>
<template>
<div>
<div v-if="pending">
Loading...
</div>
<div v-else-if="error?.data?.code">
Error: {{ error.data.code }}
</div>
<div v-else>
<ul>
<li v-for="t in todos?.slice(0, 10)" :key="t.id">
<NuxtLink :class="{ completed: t.completed }" :to="`/todo/${t.id}`">
Title: {{ t.title }}
</NuxtLink>
</li>
</ul>
<button @click="addTodo">
Add Todo
</button>
<button @click="() => refresh()">
Refresh
</button>
</div>
</div>
</template>
<style>
a {
text-decoration: none;
}
.completed {
text-decoration: line-through;
}
</style>

View File

@@ -0,0 +1,19 @@
<script setup lang="ts">
const route = useRoute()
const { $client } = useNuxtApp()
const { data: todo, pending, error } = await $client.todo.getTodo.query(Number(route.params.id))
</script>
<template>
<div v-if="pending">
Loading...
</div>
<div v-else-if="error?.data?.code">
{{ error.data.code }}
</div>
<div v-else>
ID: {{ todo?.id }} <br>
Title: {{ todo?.title }} <br>
Completed: {{ todo?.completed }}
</div>
</template>

View File

@@ -0,0 +1,27 @@
import { httpBatchLink, loggerLink } from '@trpc/client'
import { createTRPCNuxtProxyClient } from 'trpc-nuxt/client'
import superjson from 'superjson'
import { AppRouter } from '~~/server/trpc/routers'
export default defineNuxtPlugin(() => {
const client = createTRPCNuxtProxyClient<AppRouter>({
transformer: superjson,
links: [
// adds pretty logs to your console in development and logs errors in production
loggerLink({
enabled: opts =>
process.env.NODE_ENV === 'development' ||
(opts.direction === 'down' && opts.result instanceof Error)
}),
httpBatchLink({
url: 'http://localhost:3000/api/trpc'
})
]
})
return {
provide: {
client
}
}
})

View File

@@ -1,4 +1,4 @@
import { createNuxtApiHandler } from 'trpc-nuxt' import { createNuxtApiHandler } from 'trpc-nuxt/handler'
import { appRouter } from '@/server/trpc/routers' import { appRouter } from '@/server/trpc/routers'
import { createContext } from '@/server/trpc/context' import { createContext } from '@/server/trpc/context'

View File

@@ -0,0 +1,17 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import type { inferAsyncReturnType } from '@trpc/server'
import type { H3Event } from 'h3'
export type Context = inferAsyncReturnType<typeof createContext>
/**
* Creates context for an incoming request
* @link https://trpc.io/docs/context
*/
export function createContext (
opts: H3Event
) {
// for API-response caching see https://trpc.io/docs/caching
return {}
}

View File

@@ -0,0 +1,33 @@
import { z } from 'zod'
import { publicProcedure, router } from '../trpc'
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 '../trpc'
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
})
})
})

View File

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

1
handler.d.ts vendored Normal file
View File

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

View File

@@ -1,17 +0,0 @@
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
meta: {
name: 'trpc-nuxt',
configKey: 'trpc',
compatibility: {
nuxt: '^3.0.0-rc.13',
},
},
async setup(_moduleOptions, nuxt) {
nuxt.options.build.transpile.push('trpc-nuxt')
nuxt.options.vite.optimizeDeps = nuxt.options.vite.optimizeDeps || {}
nuxt.options.vite.optimizeDeps.exclude = nuxt.options.vite.optimizeDeps.exclude || []
nuxt.options.vite.optimizeDeps.exclude.push('trpc-nuxt/client')
},
})

View File

@@ -5,53 +5,59 @@
"license": "MIT", "license": "MIT",
"sideEffects": false, "sideEffects": false,
"exports": { "exports": {
"./package.json": "./package.json",
".": { ".": {
"require": "./dist/index.cjs", "require": "./dist/module.cjs",
"import": "./dist/index.mjs" "import": "./dist/module.mjs"
}, },
"./client": { "./client": {
"require": "./dist/client/index.cjs", "types": "./dist/runtime/client.d.ts",
"import": "./dist/client/index.mjs" "import": "./dist/runtime/client.mjs"
}, },
"./module": { "./handler": {
"import": "./module.mjs" "types": "./dist/runtime/handler.d.ts",
"import": "./dist/runtime/handler.mjs"
} }
}, },
"main": "./dist/index.mjs", "main": "./dist/module.cjs",
"module": "./dist/index.mjs", "types": "./dist/types.d.ts",
"types": "./dist/index.d.ts",
"files": [ "files": [
"dist", "dist",
"client.d.ts", "client.d.ts",
"module.mjs" "handler.d.ts"
], ],
"scripts": { "scripts": {
"dev": "concurrently \"pnpm build --watch\" \"pnpm --filter playground dev\"", "dev": "pnpm prepack && pnpm --filter playground dev",
"dev:prepare": "pnpm build && nuxt prepare playground", "dev:prepare": "nuxt-module-build --stub && nuxi prepare playground",
"prepublishOnly": "pnpm build", "prepublishOnly": "pnpm prepack",
"build": "tsup", "prepack": "nuxt-module-build",
"lint": "eslint .", "lint": "eslint .",
"lint:fix": "eslint . --fix", "lint:fix": "eslint . --fix",
"release": "bumpp && npm publish" "release": "bumpp && npm publish"
}, },
"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.13"
}, },
"dependencies": { "dependencies": {
"@nuxt/kit": "^3.0.0-rc.13",
"dedent": "^0.7.0",
"defu": "^6.1.0",
"fast-glob": "^3.2.12",
"h3": "^0.8.6", "h3": "^0.8.6",
"knitwork": "^0.1.2",
"nanoid": "^4.0.0", "nanoid": "^4.0.0",
"ohash": "^0.1.5", "ohash": "^0.1.5",
"pathe": "^0.3.9",
"ufo": "^0.8.6" "ufo": "^0.8.6"
}, },
"devDependencies": { "devDependencies": {
"@nuxt/module-builder": "^0.2.0",
"@nuxt/schema": "^3.0.0-rc.13",
"@nuxtjs/eslint-config-typescript": "^11.0.0", "@nuxtjs/eslint-config-typescript": "^11.0.0",
"@trpc/client": "10.0.0-rc.4", "@trpc/client": "10.0.0-rc.4",
"@trpc/server": "10.0.0-rc.4", "@trpc/server": "10.0.0-rc.4",
"@types/dedent": "^0.7.0",
"bumpp": "^8.2.1", "bumpp": "^8.2.1",
"concurrently": "^7.5.0",
"eslint": "^8.25.0", "eslint": "^8.25.0",
"nuxt": "3.0.0-rc.13", "nuxt": "3.0.0-rc.13",
"tsup": "6.4.0", "tsup": "6.4.0",

View File

@@ -1,4 +1,4 @@
// https://v3.nuxtjs.org/api/configuration/nuxt.config // https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({ export default defineNuxtConfig({
modules: ['trpc-nuxt/module'] modules: ['trpc-nuxt']
}) })

View File

@@ -5,8 +5,7 @@
"build": "nuxt build", "build": "nuxt build",
"dev": "nuxt dev", "dev": "nuxt dev",
"generate": "nuxt generate", "generate": "nuxt generate",
"preview": "nuxt preview", "preview": "nuxt preview"
"postinstall": "nuxt prepare"
}, },
"dependencies": { "dependencies": {
"@trpc/client": "10.0.0-rc.4", "@trpc/client": "10.0.0-rc.4",

View File

@@ -1,7 +1,7 @@
import { httpBatchLink, loggerLink } from '@trpc/client' import { httpBatchLink, loggerLink } from '@trpc/client'
import { createTRPCNuxtProxyClient } from 'trpc-nuxt/client' import { createTRPCNuxtProxyClient } from 'trpc-nuxt/client'
import superjson from 'superjson' import superjson from 'superjson'
import type { AppRouter } from '~~/server/trpc/routers' import type { AppRouter } from '#build/trpc/handler'
export default defineNuxtPlugin(() => { export default defineNuxtPlugin(() => {
const client = createTRPCNuxtProxyClient<AppRouter>({ const client = createTRPCNuxtProxyClient<AppRouter>({

View File

@@ -1,5 +1,5 @@
import { z } from 'zod' import { z } from 'zod'
import { publicProcedure, router } from '../trpc' import { publicProcedure, router } from '#trpc/init'
const baseURL = 'https://jsonplaceholder.typicode.com' const baseURL = 'https://jsonplaceholder.typicode.com'
@@ -12,7 +12,7 @@ const TodoShape = z.object({
export type Todo = z.infer<typeof TodoShape> export type Todo = z.infer<typeof TodoShape>
export const todoRouter = router({ export default router({
getTodos: publicProcedure getTodos: publicProcedure
.query(() => { .query(() => {
return $fetch<Todo[]>(`${baseURL}/todos`) return $fetch<Todo[]>(`${baseURL}/todos`)

View File

@@ -1,5 +1,5 @@
import { z } from 'zod' import { z } from 'zod'
import { publicProcedure, router } from '../trpc' import { publicProcedure, router } from '#trpc/init'
const baseURL = 'https://jsonplaceholder.typicode.com' const baseURL = 'https://jsonplaceholder.typicode.com'
@@ -12,7 +12,7 @@ const UserShape = z.object({
export type User = z.infer<typeof UserShape> export type User = z.infer<typeof UserShape>
export const userRouter = router({ export default router({
getUsers: publicProcedure getUsers: publicProcedure
.query(() => { .query(() => {
return $fetch<User[]>(`${baseURL}/users`) return $fetch<User[]>(`${baseURL}/users`)

405
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,4 @@
packages: packages:
- playground - playground
- docs - docs
- examples/*

View File

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

141
src/module.ts Normal file
View File

@@ -0,0 +1,141 @@
import { defineNuxtModule, addTemplate, addServerHandler } from '@nuxt/kit'
import fg from 'fast-glob'
import { resolve } from 'pathe'
import dedent from 'dedent'
export interface ModuleOptions {
enableFileRouting: boolean
}
export default defineNuxtModule<ModuleOptions>({
meta: {
name: 'trpc-nuxt',
configKey: 'trpc',
compatibility: {
nuxt: '^3.0.0-rc.13'
}
},
defaults: {
enableFileRouting: true
},
async setup (moduleOptions, nuxt) {
nuxt.options.build.transpile.push('trpc-nuxt')
nuxt.options.vite.optimizeDeps = nuxt.options.vite.optimizeDeps || {}
nuxt.options.vite.optimizeDeps.exclude = nuxt.options.vite.optimizeDeps.exclude || []
nuxt.options.vite.optimizeDeps.exclude.push('trpc-nuxt/client', 'trpc-nuxt/handler')
if (!moduleOptions.enableFileRouting) {
return
}
const files: string[] = []
const dirs = [
resolve(nuxt.options.rootDir, 'server/trpc/routers')
]
const extGlob = '*.{ts,js}'
async function scanServerFunctions () {
files.length = 0
files.push(...new Set(
(await Promise.all(
dirs.map(dir => fg(extGlob, { cwd: dir, absolute: true, onlyFiles: true })))
).flat()
))
return files
}
nuxt.hook('builder:watch', async (e, path) => {
if (e === 'change') {
return
}
const abs = resolve(nuxt.options.rootDir, path)
if (files.includes(abs) || dirs.some(dir => abs.startsWith(dir))) {
await scanServerFunctions()
await nuxt.callHook('builder:generateApp')
}
})
function getRouteName (routePath: string) {
const pathSplit = routePath.split('/')
return pathSplit[pathSplit.length - 1]
}
addTemplate({
filename: 'trpc/init.mjs',
write: true,
getContents () {
return dedent`
import { initTRPC } from '@trpc/server'
import superjson from 'superjson'
const t = initTRPC.context().create({
transformer: superjson,
})
export const router = t.router
export const defineRouter = router
export const publicProcedure = t.procedure
export const middleware = t.middleware
export const mergeRouters = t.mergeRouters
`
}
})
addTemplate({
filename: 'trpc/types.d.ts',
write: true,
getContents () {
const initFile = resolve(nuxt.options.buildDir, 'trpc/init.mjs')
return dedent`
declare module '#trpc/init' {
const router: typeof import('${initFile}').router
const defineRouter: typeof import('${initFile}').defineRouter
const publicProcedure: typeof import('${initFile}').publicProcedure
const middleware: typeof import('${initFile}').middleware
const mergeRouters: typeof import('${initFile}').mergeRouters
}
`
}
})
addTemplate({
filename: 'trpc/handler.ts',
write: true,
getContents () {
const routeFiles = files.map(i => i.replace(/\.ts$/, ''))
return dedent`
import { createNuxtApiHandler } from 'trpc-nuxt/handler'
import { router } from '${resolve(nuxt.options.buildDir, 'trpc/init.mjs')}'
${routeFiles.map(i => `import { default as ${getRouteName(i)}Route } from '${i}'`).join('\n')}
export const appRouter = router({
${routeFiles.map(i => `${getRouteName(i)}: ${getRouteName(i)}Route`).join(',\n')}
})
export type AppRouter = typeof appRouter
export default createNuxtApiHandler({
router: appRouter
})
`
}
})
addServerHandler({
route: '/api/trpc/:trpc',
handler: resolve(nuxt.options.buildDir, 'trpc/handler.ts'),
lazy: true
})
nuxt.hook('nitro:config', (nitroConfig) => {
nitroConfig.alias = nitroConfig.alias || {}
nitroConfig.alias['#trpc/init'] = resolve(nuxt.options.buildDir, 'trpc/init.mjs')
})
nuxt.hook('prepare:types', (options) => {
options.references.push({ path: resolve(nuxt.options.buildDir, 'trpc/types.d.ts') })
})
await scanServerFunctions()
}
})

View File

@@ -1,17 +1,17 @@
import type { ResponseMeta } from '@trpc/server/http'
import { resolveHTTPResponse } from '@trpc/server/http' import { resolveHTTPResponse } from '@trpc/server/http'
import {
TRPCError
} from '@trpc/server'
import type { import type {
AnyRouter, AnyRouter,
ProcedureType, ProcedureType,
inferRouterContext, inferRouterContext,
inferRouterError inferRouterError
} from '@trpc/server' } from '@trpc/server'
import {
TRPCError
} from '@trpc/server'
import { createURL } from 'ufo'
import type { H3Event } from 'h3' import type { H3Event } from 'h3'
import { createURL } from 'ufo'
import { createError, defineEventHandler, isMethod, readBody } from 'h3' import { createError, defineEventHandler, isMethod, readBody } from 'h3'
import type { ResponseMeta } from '@trpc/server/http'
import type { TRPCResponse } from '@trpc/server/rpc' import type { TRPCResponse } from '@trpc/server/rpc'
type MaybePromise<T> = T | Promise<T> type MaybePromise<T> = T | Promise<T>

View File

@@ -1,16 +1,3 @@
{ {
"compilerOptions": { "extends": "./playground/.nuxt/tsconfig.json"
"target": "es2020",
"module": "esnext",
"strict": true,
"esModuleInterop": true,
"moduleResolution": "node",
"skipLibCheck": true,
"noUnusedLocals": true,
"noImplicitAny": true,
"allowJs": true,
"noEmit": true,
"resolveJsonModule": true,
"skipDefaultLibCheck": true
}
} }

View File

@@ -1,15 +0,0 @@
import { defineConfig } from 'tsup'
export default defineConfig({
entry: ['src/index.ts', 'src/client/index.ts'],
format: ['cjs', 'esm'],
splitting: false,
clean: true,
external: ['#app', '#imports'],
dts: true,
outExtension({ format }) {
return {
js: format === 'esm' ? '.mjs' : `.${format}`,
}
},
})