From c7ac9ee09dace2ee3d2b1577a62bebb1411f0ab3 Mon Sep 17 00:00:00 2001 From: Robert Soriano Date: Thu, 19 May 2022 01:20:47 -0700 Subject: [PATCH] fix publish issue --- package/.gitignore | 51 +++++++++ package/.npmrc | 2 + package/LICENSE | 21 ++++ package/README.md | 141 ++++++++++++++++++++++++ package/api.d.ts | 1 + package/package.json | 39 +++++++ package/playground/.gitignore | 7 ++ package/playground/README.md | 31 ++++++ package/playground/app.vue | 18 +++ package/playground/nuxt.config.ts | 13 +++ package/playground/package.json | 4 + package/playground/server/trpc/index.ts | 82 ++++++++++++++ package/pnpm-workspace.yaml | 2 + package/recipes/authorization.md | 102 +++++++++++++++++ package/recipes/error-formatting.md | 41 +++++++ package/recipes/error-handling.md | 15 +++ package/recipes/validation.md | 49 ++++++++ package/src/module.ts | 61 ++++++++++ package/src/runtime/api.ts | 92 ++++++++++++++++ package/src/runtime/client.ts | 64 +++++++++++ package/tsconfig.json | 3 + 21 files changed, 839 insertions(+) create mode 100644 package/.gitignore create mode 100644 package/.npmrc create mode 100644 package/LICENSE create mode 100644 package/README.md create mode 100644 package/api.d.ts create mode 100644 package/package.json create mode 100644 package/playground/.gitignore create mode 100644 package/playground/README.md create mode 100644 package/playground/app.vue create mode 100644 package/playground/nuxt.config.ts create mode 100644 package/playground/package.json create mode 100644 package/playground/server/trpc/index.ts create mode 100644 package/pnpm-workspace.yaml create mode 100644 package/recipes/authorization.md create mode 100644 package/recipes/error-formatting.md create mode 100644 package/recipes/error-handling.md create mode 100644 package/recipes/validation.md create mode 100644 package/src/module.ts create mode 100644 package/src/runtime/api.ts create mode 100644 package/src/runtime/client.ts create mode 100644 package/tsconfig.json diff --git a/package/.gitignore b/package/.gitignore new file mode 100644 index 0000000..4994002 --- /dev/null +++ b/package/.gitignore @@ -0,0 +1,51 @@ +# Dependencies +node_modules + +# Logs +*.log* + +# Temp directories +.temp +.tmp +.cache + +# Yarn +**/.yarn/cache +**/.yarn/*state* + +# Generated dirs +dist + +# Nuxt +.nuxt +.output +.vercel_build_output +.build-* +.env +.netlify + +# Env +.env + +# Testing +reports +coverage +*.lcov +.nyc_output + +# VSCode +.vscode + +# Intellij idea +*.iml +.idea + +# OSX +.DS_Store +.AppleDouble +.LSOverride +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk diff --git a/package/.npmrc b/package/.npmrc new file mode 100644 index 0000000..116d16a --- /dev/null +++ b/package/.npmrc @@ -0,0 +1,2 @@ +ignore-workspace-root-check=true +shamefully-hoist=true diff --git a/package/LICENSE b/package/LICENSE new file mode 100644 index 0000000..53c76fc --- /dev/null +++ b/package/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Robert Soriano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/package/README.md b/package/README.md new file mode 100644 index 0000000..de945ba --- /dev/null +++ b/package/README.md @@ -0,0 +1,141 @@ +# tRPC-Nuxt + +[![Version](https://img.shields.io/npm/v/trpc-nuxt?style=flat&colorA=000000&colorB=000000)](https://www.npmjs.com/package/trpc-nuxt) + +End-to-end typesafe APIs with [tRPC.io](https://trpc.io/) in Nuxt applications. + +## Install + +```bash +npm i trpc-nuxt +``` + +```ts +// nuxt.config.ts +import { defineNuxtConfig } from 'nuxt' + +export default defineNuxtConfig({ + modules: ['trpc-nuxt'], + trpc: { + baseURL: 'http://localhost:3000', // defaults to http://localhost:3000 + trpcURL: '/api/trpc', // defaults to /api/trpc + }, + typescript: { + strict: true // set this to true to make input/output types work + } +}) +``` + +## Usage + +Expose your tRPC [routes](https://trpc.io/docs/router) under `~/server/trpc/index.ts`: + +```ts +// ~/server/trpc/index.ts +import type { inferAsyncReturnType } from '@trpc/server' +import * as trpc from '@trpc/server' +import { z } from 'zod' // yup/superstruct/zod/myzod/custom + +export const router = trpc.router() + // queries and mutations... + .query('getUsers', { + async resolve(req) { + // use your ORM of choice + return await UserModel.all() + }, + }) + .mutation('createUser', { + // validate input with Zod + input: z.object({ name: z.string().min(5) }), + async resolve(req) { + // use your ORM of choice + return await UserModel.create({ + data: req.input, + }) + }, + }) +``` + +Use the client like so: + +```ts +const client = useClient() // auto-imported + +const users = await client.query('getUsers') + +const addUser = async () => { + const mutate = await client.mutation('createUser', { + name: 'wagmi' + }) +} +``` + +## useAsyncQuery + +A thin wrapper around [`useAsyncData`](https://v3.nuxtjs.org/api/composables/use-async-data/) and `client.query()`. + +The first argument is a `[path, input]`-tuple - if the `input` is optional, you can omit the, `input`-part. + +You'll notice that you get autocompletion on the `path` and automatic typesafety on the `input`. + +```ts +const { + data, + pending, + error, + refresh +} = await useAsyncQuery(['getUser', { id: 69 }], { + // pass useAsyncData options here + server: true +}) +``` + +## Options + +trpc-nuxt accepts the following options exposed under `~/server/trpc.index.ts`: + +```ts +import * as trpc from '@trpc/server' +import type { inferAsyncReturnType } from '@trpc/server' +import type { CompatibilityEvent } from 'h3' +import type { OnErrorPayload } from 'trpc-nuxt/api' + +export const router = trpc.router>() + +// Optional +// https://trpc.io/docs/context +export const createContext = (event: CompatibilityEvent) => { + // ... + return { + /** context data */ + } +} + +// Optional +// https://trpc.io/docs/caching#using-responsemeta--to-cache-responses +export const responseMeta = () => { + // ... + return { + // { headers: ... } + } +} + +// Optional +// https://trpc.io/docs/error-handling#handling-errors +export const onError = (payload: OnErrorPayload) => { + // Do whatever here like send to bug reporting and stuff +} +``` + +## Recipes + +- [Validation](/recipes/validation.md) +- [Authorization](/recipes/authorization.md) +- [Error Handling](/recipes/error-handling.md) +- [Error Formatting](/recipes/error-formatting.md) + +Learn more about tRPC.io [here](https://trpc.io/docs). + +## License + +MIT diff --git a/package/api.d.ts b/package/api.d.ts new file mode 100644 index 0000000..283419d --- /dev/null +++ b/package/api.d.ts @@ -0,0 +1 @@ +export * from './dist/runtime/api' diff --git a/package/package.json b/package/package.json new file mode 100644 index 0000000..6781f27 --- /dev/null +++ b/package/package.json @@ -0,0 +1,39 @@ +{ + "name": "trpc-nuxt", + "type": "module", + "version": "0.0.3", + "publishConfig": { + "directory": "package" + }, + "packageManager": "pnpm@7.1.0", + "license": "MIT", + "main": "./dist/module.cjs", + "types": "./dist/types.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "import": "./dist/module.mjs", + "require": "./dist/module.cjs" + }, + "./api": { + "import": "./dist/runtime/api.mjs", + "types": "./dist/runtime/api.d.ts" + } + }, + "files": [ + "dist", + "*.d.ts" + ], + "dependencies": { + "@nuxt/kit": "^3.0.0-rc.3", + "@trpc/client": "^9.23.3", + "@trpc/server": "^9.23.2", + "fs-extra": "^10.1.0", + "h3": "^0.7.8", + "pathe": "^0.3.0", + "ufo": "^0.8.4" + }, + "scripts": { + "postpublish": "rimraf ./package" + } +} diff --git a/package/playground/.gitignore b/package/playground/.gitignore new file mode 100644 index 0000000..d233591 --- /dev/null +++ b/package/playground/.gitignore @@ -0,0 +1,7 @@ +node_modules +*.log* +.nuxt +.nitro +.cache +.output +.env \ No newline at end of file diff --git a/package/playground/README.md b/package/playground/README.md new file mode 100644 index 0000000..7257bce --- /dev/null +++ b/package/playground/README.md @@ -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 +``` diff --git a/package/playground/app.vue b/package/playground/app.vue new file mode 100644 index 0000000..943c67f --- /dev/null +++ b/package/playground/app.vue @@ -0,0 +1,18 @@ + + + diff --git a/package/playground/nuxt.config.ts b/package/playground/nuxt.config.ts new file mode 100644 index 0000000..33d6bbf --- /dev/null +++ b/package/playground/nuxt.config.ts @@ -0,0 +1,13 @@ +import { defineNuxtConfig } from 'nuxt' +import Module from '..' + +// https://v3.nuxtjs.org/api/configuration/nuxt.config +export default defineNuxtConfig({ + modules: [Module], + runtimeConfig: { + baseURL: 'http://localhost:3000', + }, + typescript: { + strict: true, + }, +}) diff --git a/package/playground/package.json b/package/playground/package.json new file mode 100644 index 0000000..51466b1 --- /dev/null +++ b/package/playground/package.json @@ -0,0 +1,4 @@ +{ + "name": "playground", + "private": true +} diff --git a/package/playground/server/trpc/index.ts b/package/playground/server/trpc/index.ts new file mode 100644 index 0000000..dead09a --- /dev/null +++ b/package/playground/server/trpc/index.ts @@ -0,0 +1,82 @@ +// ~/server/trpc/index.ts +import { ZodError, z } from 'zod' +import * as trpc from '@trpc/server' +import type { inferAsyncReturnType } from '@trpc/server' +import type { CompatibilityEvent } from 'h3' +import type { ResponseMetaFnPayload } from 'trpc-nuxt/api' +// import superjson from 'superjson' + +const fakeUsers = [ + { id: 1, username: 'jcena' }, + { id: 2, username: 'dbatista' }, + { id: 3, username: 'jbiden' }, +] + +export const router = trpc + .router>() + .formatError(({ shape, error }) => { + return { + ...shape, + data: { + ...shape.data, + zodError: + error.code === 'BAD_REQUEST' + && error.cause instanceof ZodError + ? error.cause.flatten() + : null, + }, + } + }) + .query('getUsers', { + resolve() { + return fakeUsers + }, + }) + .query('getUser', { + // validate input with Zod + input: z.object({ + username: z.string().min(5), + }), + resolve(req) { + return fakeUsers.find(i => i.username === req.input.username) ?? null + }, + }) + .mutation('createUser', { + input: z.object({ username: z.string().min(5) }), + resolve(req) { + const newUser = { + id: fakeUsers.length + 1, + username: req.input.username, + } + fakeUsers.push(newUser) + return newUser + }, + }) + +export const createContext = (event: CompatibilityEvent) => { + event.res.setHeader('x-ssr', 1) + return {} +} + +export const responseMeta = (opts: ResponseMetaFnPayload) => { + // const nuxtApp = useNuxtApp() + // const client = useClient() + // console.log(opts) + + // if (nuxtApp.nuxtState) { + // nuxtApp.nuxtState.trpc = client.runtime.transformer.serialize({ + // ctx: opts.ctx, + // errors: opts.errors, + // }) + // } + // else { + // nuxtApp.nuxtState = { + // trpc: client.runtime.transformer.serialize({ + // ctx: opts.ctx, + // errors: opts.errors, + // }), + // } + // } + + return {} +} diff --git a/package/pnpm-workspace.yaml b/package/pnpm-workspace.yaml new file mode 100644 index 0000000..4667813 --- /dev/null +++ b/package/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - playground/* diff --git a/package/recipes/authorization.md b/package/recipes/authorization.md new file mode 100644 index 0000000..ab09f89 --- /dev/null +++ b/package/recipes/authorization.md @@ -0,0 +1,102 @@ +## Authorization + +The `createContext`-function is called for each incoming request so here you can add contextual information about the calling user from the request object. + +### Create context from request headers + +```ts +// ~/server/trpc/index.ts +import type { inferAsyncReturnType } from '@trpc/server' +import type { CompatibilityEvent } from 'h3' +import { decodeAndVerifyJwtToken } from '~/somewhere/in/your/app/utils' + +// The app's context - is generated for each incoming request +export async function createContext({ req }: CompatibilityEvent) { + // 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 + async function getUserFromHeader() { + if (req.headers.authorization) { + const user = await decodeAndVerifyJwtToken(req.headers.authorization.split(' ')[1]) + return user + } + return null + } + const user = await getUserFromHeader() + + return { + user, + } +} + +type Context = inferAsyncReturnType + +// [..] Define API handler and app router +``` + +### Option 1: Authorize using resolver + +```ts +import { TRPCError } from '@trpc/server' + +export const router = trpc + .router() + // open for anyone + .query('hello', { + input: z.string().nullish(), + resolve: ({ input, ctx }) => { + return `hello ${input ?? ctx.user?.name ?? 'world'}` + }, + }) + // checked in resolver + .query('secret', { + resolve: ({ ctx }) => { + if (!ctx.user) + throw new TRPCError({ code: 'UNAUTHORIZED' }) + + return { + secret: 'sauce', + } + }, + }) +``` + +### Option 2: Authorize using middleware + +```ts +import * as trpc from '@trpc/server' +import { TRPCError } from '@trpc/server' + +// Merging routers: https://trpc.io/docs/merging-routers + +export const router = trpc + .router() + // this is accessible for everyone + .query('hello', { + input: z.string().nullish(), + resolve: ({ input, ctx }) => { + return `hello ${input ?? ctx.user?.name ?? 'world'}` + }, + }) + .merge( + 'admin.', + trpc.router() + // this protects all procedures defined next in this router + .middleware(async ({ ctx, next }) => { + if (!ctx.user?.isAdmin) + throw new TRPCError({ code: 'UNAUTHORIZED' }) + + return next() + }) + .query('secret', { + resolve: ({ ctx }) => { + return { + secret: 'sauce', + } + }, + }), + ) +``` + +Learn more about authorization [here](https://trpc.io/docs/authorization). diff --git a/package/recipes/error-formatting.md b/package/recipes/error-formatting.md new file mode 100644 index 0000000..b60290c --- /dev/null +++ b/package/recipes/error-formatting.md @@ -0,0 +1,41 @@ +## Error Formatting + +The error formatting in your router will be inferred all the way to your client (& Vue components). + +### Adding custom formatting + +```ts +// ~/server/trpc/index.ts +import * as trpc from '@trpc/server' + +export const router = trpc.router() + .formatError(({ shape, error }) => { + return { + ...shape, + data: { + ...shape.data, + zodError: + error.code === 'BAD_REQUEST' + && error.cause instanceof ZodError + ? error.cause.flatten() + : null, + } + } + }) +``` + +### Usage in Vue + +```html + + + +``` + +Learn more about error formatting [here](https://trpc.io/docs/error-formatting). diff --git a/package/recipes/error-handling.md b/package/recipes/error-handling.md new file mode 100644 index 0000000..1821349 --- /dev/null +++ b/package/recipes/error-handling.md @@ -0,0 +1,15 @@ +## Handling errors + +All errors that occur in a procedure go through the `onError` method before being sent to the client. Here you can handle or change errors. + +```ts +// ~/server/trpc/index.ts +import * as trpc from '@trpc/server' + +export function onError({ error, type, path, input, ctx, req }) { + console.error('Error:', error) + if (error.code === 'INTERNAL_SERVER_ERROR') { + // send to bug reporting + } +} +``` diff --git a/package/recipes/validation.md b/package/recipes/validation.md new file mode 100644 index 0000000..dd810f1 --- /dev/null +++ b/package/recipes/validation.md @@ -0,0 +1,49 @@ +## Validation + +tRPC works out-of-the-box with yup/superstruct/zod/myzod/custom validators. + +### Input Validation + +```ts +// ~/server/trpc/index.ts +import { z } from 'zod' + +export const router = trpc + .router() + .mutation('createUser', { + // validate input with Zod + input: z.object({ + name: z.string().min(5) + }), + async resolve(req) { + // use your ORM of choice + return await UserModel.create({ + data: req.input, + }) + }, + }) +``` + +### Output Validation + +```ts +// ~/server/trpc/index.ts +import { z } from 'zod' + +export const router = trpc + .router() + .query('hello', { + // validate output with Zod + output: z.object({ + greeting: z.string() + }), + // expects return type of { greeting: string } + resolve() { + return { + greeting: 'hello!', + } + }, + }) +``` + +Learn more about input validation [here](https://trpc.io/docs/router#input-validation). diff --git a/package/src/module.ts b/package/src/module.ts new file mode 100644 index 0000000..da4cca0 --- /dev/null +++ b/package/src/module.ts @@ -0,0 +1,61 @@ +import { fileURLToPath } from 'url' +import { dirname, join } from 'pathe' + +import { addServerHandler, defineNuxtModule } from '@nuxt/kit' +import fs from 'fs-extra' + +export interface ModuleOptions { + baseURL: string + trpcURL: string +} + +export default defineNuxtModule({ + meta: { + name: 'trpc-nuxt', + configKey: 'trpc', + }, + defaults: { + baseURL: 'http://localhost:3000', + trpcURL: '/api/trpc', + }, + async setup(options, nuxt) { + const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url)) + nuxt.options.build.transpile.push(runtimeDir) + + const clientPath = join(nuxt.options.buildDir, 'trpc-client.ts') + const handlerPath = join(nuxt.options.buildDir, 'trpc-handler.ts') + + addServerHandler({ + route: `${options.trpcURL}/*`, + handler: handlerPath, + }) + + nuxt.hook('autoImports:extend', (imports) => { + imports.push( + { name: 'useClient', from: clientPath }, + { name: 'useAsyncQuery', from: join(runtimeDir, 'client') }, + ) + }) + + await fs.ensureDir(dirname(clientPath)) + + await fs.writeFile(clientPath, ` + import * as trpc from '@trpc/client' + import type { router } from '~/server/trpc' + + const client = trpc.createTRPCClient({ + url: '${options.baseURL}${options.trpcURL}', + }) + + export const useClient = () => client + `) + + await fs.writeFile(handlerPath, ` + import { createTRPCHandler } from 'trpc-nuxt/api' + import * as functions from '~/server/trpc' + + export default createTRPCHandler(functions) + `) + }, +}) + diff --git a/package/src/runtime/api.ts b/package/src/runtime/api.ts new file mode 100644 index 0000000..5101a1a --- /dev/null +++ b/package/src/runtime/api.ts @@ -0,0 +1,92 @@ +import { resolveHTTPResponse } from '@trpc/server' +import type { + AnyRouter, + ProcedureType, + ResponseMeta, + TRPCError, + inferRouterContext, + inferRouterError, +} from '@trpc/server' +import { createURL } from 'ufo' +import type { CompatibilityEvent } from 'h3' +import { defineEventHandler, isMethod, useBody } from 'h3' +import type { TRPCResponse } from '@trpc/server/dist/declarations/src/rpc' + +type MaybePromise = T | Promise + +export type CreateContextFn = (event: CompatibilityEvent) => MaybePromise> + +export interface ResponseMetaFnPayload { + data: TRPCResponse>[] + ctx?: inferRouterContext + paths?: string[] + type: ProcedureType | 'unknown' + errors: TRPCError[] +} + +export type ResponseMetaFn = (opts: ResponseMetaFnPayload) => ResponseMeta + +export interface OnErrorPayload { + error: TRPCError + type: ProcedureType | 'unknown' + path: string | undefined + req: CompatibilityEvent['req'] + input: unknown + ctx: undefined | inferRouterContext +} + +export type OnErrorFn = (opts: OnErrorPayload) => void + +export function createTRPCHandler({ + router, + createContext, + responseMeta, + onError, +}: { + router: Router + createContext?: CreateContextFn + responseMeta?: ResponseMetaFn + onError?: OnErrorFn +}) { + const url = '/api/trpc' + + return defineEventHandler(async (event) => { + const { + req, + res, + } = event + + const $url = createURL(req.url) + + event.context.hello = 'world' + + const httpResponse = await resolveHTTPResponse({ + router, + req: { + method: req.method, + headers: req.headers, + body: isMethod(event, 'GET') ? null : await useBody(event), + query: $url.searchParams, + }, + path: $url.pathname.substring(url.length + 1), + createContext: async () => createContext?.(event), + responseMeta, + onError: (o) => { + onError?.({ + ...o, + req, + }) + }, + }) + + const { status, headers, body } = httpResponse + + res.statusCode = status + + Object.keys(headers).forEach((key) => { + res.setHeader(key, headers[key]) + }) + + return body + }) +} diff --git a/package/src/runtime/client.ts b/package/src/runtime/client.ts new file mode 100644 index 0000000..d7f5551 --- /dev/null +++ b/package/src/runtime/client.ts @@ -0,0 +1,64 @@ +import type { + AsyncData, + AsyncDataOptions, + KeyOfRes, + PickFrom, + _Transform, +} from 'nuxt/dist/app/composables/asyncData' +import type { ProcedureRecord, inferHandlerInput, inferProcedureInput, inferProcedureOutput } from '@trpc/server' +import type { TRPCClientErrorLike } from '@trpc/client' +import { objectHash } from 'ohash' +// @ts-expect-error: Resolved by Nuxt +import { useAsyncData, useState } from '#imports' +// @ts-expect-error: Resolved by Nuxt +import { useClient } from '#build/trpc-client' +// @ts-expect-error: Resolved by Nuxt +import type { router } from '~/server/trpc' + +type AppRouter = typeof router + +type inferProcedures< + TObj extends ProcedureRecord, +> = { + [TPath in keyof TObj]: { + input: inferProcedureInput + output: inferProcedureOutput + }; +} + +type TQueries = AppRouter['_def']['queries'] +type TError = TRPCClientErrorLike + +type TQueryValues = inferProcedures + +export async function useAsyncQuery< + TPath extends keyof TQueryValues & string, + TOutput extends TQueryValues[TPath]['output'] = TQueryValues[TPath]['output'], + Transform extends _Transform = _Transform, + PickKeys extends KeyOfRes = KeyOfRes, +>( + pathAndInput: [path: TPath, ...args: inferHandlerInput], + options: AsyncDataOptions = {}, +): Promise, PickKeys>, TError>> { + const client = useClient() + const key = `${pathAndInput[0]}-${objectHash(pathAndInput[1] ? JSON.stringify(pathAndInput[1]) : '')}` + const serverError = useState(`error-${key}`, () => null) + const { error, data, ...rest } = await useAsyncData( + key, + () => client.query(...pathAndInput), + options, + ) + + // @ts-expect-error: Resolved by Nuxt + if (process.server && error.value && !serverError.value) + serverError.value = error.value as any + + if (data.value) + serverError.value = null + + return { + ...rest, + data, + error: serverError, + } as any +} diff --git a/package/tsconfig.json b/package/tsconfig.json new file mode 100644 index 0000000..9dd826f --- /dev/null +++ b/package/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./playground/.nuxt/tsconfig.json" +}