-
-
Notifications
You must be signed in to change notification settings - Fork 127
feat(trpc): support client helpers for Nuxt #1762
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| /* eslint-disable */ | ||
|
|
||
| import type { AnyRouter } from '@trpc/server'; | ||
| import { createTRPCNuxtClient as _createTRPCNuxtClient } from 'trpc-nuxt/client'; | ||
| import type { DeepOverrideAtPath } from './utils'; | ||
|
|
||
| export function createTRPCNuxtClient<TRouter extends AnyRouter, TPath extends string | undefined = undefined>( | ||
| opts: Parameters<typeof _createTRPCNuxtClient<TRouter>>[0] | ||
| ) { | ||
| const r = _createTRPCNuxtClient<TRouter>(opts); | ||
| return r as DeepOverrideAtPath<typeof r, ClientType<TRouter>, TPath>; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| /* eslint-disable */ | ||
|
|
||
| import type { AnyTRPCRouter as AnyRouter } from '@trpc/server'; | ||
| import { createTRPCNuxtClient as _createTRPCNuxtClient } from 'trpc-nuxt/client'; | ||
| import type { DeepOverrideAtPath } from './utils'; | ||
|
|
||
| export function createTRPCNuxtClient<TRouter extends AnyRouter, TPath extends string | undefined = undefined>( | ||
| opts: Parameters<typeof _createTRPCNuxtClient<TRouter>>[0] | ||
| ) { | ||
| const r = _createTRPCNuxtClient<TRouter>(opts); | ||
| return r as DeepOverrideAtPath<typeof r, ClientType<TRouter>, TPath>; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing import of The type Apply this diff to import import type { AnyTRPCRouter as AnyRouter } from '@trpc/server';
import { createTRPCNuxtClient as _createTRPCNuxtClient } from 'trpc-nuxt/client';
import type { DeepOverrideAtPath } from './utils';
+import type { ClientType } from './types'; // Adjust the import path accordingly
|
||
| } | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ../v10/utils.ts |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,285 @@ | ||
| import { PluginError, type PluginOptions } from '@zenstackhq/sdk'; | ||
| import { getPrismaClientImportSpec } from '@zenstackhq/sdk/prisma'; | ||
| import fs from 'fs'; | ||
| import { lowerCaseFirst } from 'lower-case-first'; | ||
| import path from 'path'; | ||
| import { | ||
| InterfaceDeclarationStructure, | ||
| Project, | ||
| PropertySignatureStructure, | ||
| SourceFile, | ||
| StructureKind, | ||
| } from 'ts-morph'; | ||
| import { upperCaseFirst } from 'upper-case-first'; | ||
| import { name } from '..'; | ||
ymc9 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| import { SupportedClientHelpers } from '../utils'; | ||
| import * as NextHelpers from './next'; | ||
| import * as NuxtHelpers from './nuxt'; | ||
| import * as ReactHelpers from './react'; | ||
|
|
||
| const helpers = { | ||
| react: ReactHelpers, | ||
| next: NextHelpers, | ||
| nuxt: NuxtHelpers, | ||
| }; | ||
|
|
||
| export function generateClientTypingForModel( | ||
| project: Project, | ||
| generateClientHelpers: SupportedClientHelpers[], | ||
| model: string, | ||
| options: PluginOptions, | ||
| generateOperations: Array<{ name: string; baseType: string }>, | ||
| version: string, | ||
| outDir: string | ||
| ) { | ||
| for (const clientType of generateClientHelpers) { | ||
| const sf = project.createSourceFile( | ||
| path.resolve(outDir, `client/${upperCaseFirst(model)}.${clientType}.type.ts`), | ||
| undefined, | ||
| { | ||
| overwrite: true, | ||
| } | ||
| ); | ||
|
|
||
| sf.addStatements([`/* eslint-disable */`]); | ||
|
|
||
| generateImports(clientType, sf, options, version); | ||
|
|
||
| // generate a `ClientType` interface that contains typing for query/mutation operations | ||
| const routerTypingStructure: InterfaceDeclarationStructure = { | ||
| kind: StructureKind.Interface, | ||
| name: 'ClientType', | ||
| isExported: true, | ||
| typeParameters: ['AppRouter extends AnyRouter', `Context = AppRouter['_def']['_config']['$types']['ctx']`], | ||
| properties: [] as PropertySignatureStructure[], | ||
| }; | ||
|
|
||
| for (const { name: generateOpName, baseType: baseOpType } of generateOperations) { | ||
| routerTypingStructure.properties?.push({ | ||
| kind: StructureKind.PropertySignature, | ||
| name: generateOpName, | ||
| type: (writer) => { | ||
| helpers[clientType].generateProcedureTyping(writer, generateOpName, model, baseOpType, version); | ||
| }, | ||
| }); | ||
| } | ||
|
|
||
| sf.addInterface(routerTypingStructure); | ||
| } | ||
| } | ||
|
|
||
| function generateImports( | ||
| clientHelperType: SupportedClientHelpers, | ||
| sourceFile: SourceFile, | ||
| options: PluginOptions, | ||
| version: string | ||
| ) { | ||
| const importingDir = sourceFile.getDirectoryPath(); | ||
| const prismaImport = getPrismaClientImportSpec(importingDir, options); | ||
| sourceFile.addStatements([ | ||
| `import type { Prisma } from '${prismaImport}';`, | ||
| `import type { TRPCClientErrorLike, TRPCRequestOptions } from '@trpc/client';`, | ||
| ]); | ||
|
|
||
| // generate framework-specific imports | ||
| helpers[clientHelperType].generateRouterTypingImports(sourceFile, version); | ||
| } | ||
|
|
||
| export function createClientHelperEntries( | ||
| project: Project, | ||
| outputDir: string, | ||
| generateClientHelpers: SupportedClientHelpers[], | ||
| models: string[], | ||
| version: string | ||
| ) { | ||
| // generate utils | ||
| const content = fs.readFileSync(path.join(__dirname, `../res/client/${version}/utils.ts`), 'utf-8'); | ||
| project.createSourceFile(path.resolve(outputDir, 'client', `utils.ts`), content, { | ||
| overwrite: true, | ||
| }); | ||
|
|
||
| for (const client of generateClientHelpers) { | ||
| createClientHelperEntryForType(project, client, models, version, outputDir); | ||
| } | ||
| } | ||
|
|
||
| function createClientHelperEntryForType( | ||
| project: Project, | ||
| clientHelperType: SupportedClientHelpers, | ||
| models: string[], | ||
| version: string, | ||
| outputDir: string | ||
| ) { | ||
| const content = fs.readFileSync(path.join(__dirname, `../res/client/${version}/${clientHelperType}.ts`), 'utf-8'); | ||
| const sf = project.createSourceFile(path.resolve(outputDir, 'client', `${clientHelperType}.ts`), content, { | ||
| overwrite: true, | ||
| }); | ||
|
|
||
| sf.addInterface({ | ||
| name: 'ClientType', | ||
| typeParameters: ['AppRouter extends AnyRouter'], | ||
| isExported: true, | ||
| properties: models.map((model) => { | ||
| sf.addImportDeclaration({ | ||
| namedImports: [{ name: 'ClientType', alias: `${upperCaseFirst(model)}ClientType` }], | ||
| moduleSpecifier: `./${upperCaseFirst(model)}.${clientHelperType}.type`, | ||
| }); | ||
| return { | ||
| name: lowerCaseFirst(model), | ||
| type: `${upperCaseFirst(model)}ClientType<AppRouter>`, | ||
| } as PropertySignatureStructure; | ||
| }), | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Given a model and Prisma operation, returns related TS types. | ||
| */ | ||
| export function getPrismaOperationTypes(model: string, operation: string) { | ||
| // TODO: find a way to derive from Prisma Client API's generic types | ||
| // instead of duplicating them | ||
ymc9 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| const capModel = upperCaseFirst(model); | ||
| const capOperation = upperCaseFirst(operation); | ||
|
|
||
| let genericBase = `Prisma.${capModel}${capOperation}Args`; | ||
| const getPayload = `Prisma.${capModel}GetPayload<T>`; | ||
| const selectSubset = `Prisma.SelectSubset<T, ${genericBase}>`; | ||
|
|
||
| let argsType: string; | ||
| let resultType: string; | ||
| const argsOptional = ['findMany', 'findFirst', 'findFirstOrThrow', 'createMany', 'deleteMany', 'count'].includes( | ||
| operation | ||
| ); | ||
|
|
||
| switch (operation) { | ||
| case 'findUnique': | ||
| case 'findUniqueOrThrow': | ||
| case 'findFirst': | ||
| case 'findFirstOrThrow': | ||
| argsType = selectSubset; | ||
| resultType = getPayload; | ||
| break; | ||
|
|
||
| case 'findMany': | ||
| argsType = selectSubset; | ||
| resultType = `Array<${getPayload}>`; | ||
| break; | ||
|
|
||
| case 'create': | ||
| argsType = selectSubset; | ||
| resultType = getPayload; | ||
| break; | ||
|
|
||
| case 'createMany': | ||
| argsType = selectSubset; | ||
| resultType = `Prisma.BatchPayload`; | ||
| break; | ||
|
|
||
| case 'update': | ||
| argsType = selectSubset; | ||
| resultType = getPayload; | ||
| break; | ||
|
|
||
| case 'updateMany': | ||
| argsType = selectSubset; | ||
| resultType = `Prisma.BatchPayload`; | ||
| break; | ||
|
|
||
| case 'upsert': | ||
| argsType = selectSubset; | ||
| resultType = getPayload; | ||
| break; | ||
|
|
||
| case 'delete': | ||
| argsType = selectSubset; | ||
| resultType = getPayload; | ||
| break; | ||
|
|
||
| case 'deleteMany': | ||
| argsType = selectSubset; | ||
| resultType = `Prisma.BatchPayload`; | ||
| break; | ||
|
|
||
| case 'count': | ||
| argsType = `Prisma.Subset<T, ${genericBase}>`; | ||
| resultType = `'select' extends keyof T | ||
| ? T['select'] extends true | ||
| ? number | ||
| : Prisma.GetScalarType<T['select'], Prisma.${capModel}CountAggregateOutputType> | ||
| : number`; | ||
| break; | ||
|
|
||
| case 'aggregate': | ||
| argsType = `Prisma.Subset<T, ${genericBase}>`; | ||
| resultType = `Prisma.Get${capModel}AggregateType<T>`; | ||
| break; | ||
|
|
||
| case 'groupBy': | ||
| genericBase = `Prisma.${capModel}GroupByArgs, | ||
| HasSelectOrTake extends Prisma.Or< | ||
| Prisma.Extends<'skip', Prisma.Keys<T>>, | ||
| Prisma.Extends<'take', Prisma.Keys<T>> | ||
| >, | ||
| OrderByArg extends Prisma.True extends HasSelectOrTake | ||
| ? { orderBy: Prisma.${capModel}GroupByArgs['orderBy'] } | ||
| : { orderBy?: Prisma.${capModel}GroupByArgs['orderBy'] }, | ||
| OrderFields extends Prisma.ExcludeUnderscoreKeys<Prisma.Keys<Prisma.MaybeTupleToUnion<T['orderBy']>>>, | ||
| ByFields extends Prisma.MaybeTupleToUnion<T['by']>, | ||
| ByValid extends Prisma.Has<ByFields, OrderFields>, | ||
| HavingFields extends Prisma.GetHavingFields<T['having']>, | ||
| HavingValid extends Prisma.Has<ByFields, HavingFields>, | ||
| ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, | ||
| InputErrors extends ByEmpty extends Prisma.True | ||
| ? \`Error: "by" must not be empty.\` | ||
| : HavingValid extends Prisma.False | ||
| ? { | ||
| [P in HavingFields]: P extends ByFields | ||
| ? never | ||
| : P extends string | ||
| ? \`Error: Field "\${P}" used in "having" needs to be provided in "by".\` | ||
| : [ | ||
| Error, | ||
| 'Field ', | ||
| P, | ||
| \` in "having" needs to be provided in "by"\`, | ||
| ] | ||
| }[HavingFields] | ||
| : 'take' extends Prisma.Keys<T> | ||
| ? 'orderBy' extends Prisma.Keys<T> | ||
| ? ByValid extends Prisma.True | ||
| ? {} | ||
| : { | ||
| [P in OrderFields]: P extends ByFields | ||
| ? never | ||
| : \`Error: Field "\${P}" in "orderBy" needs to be provided in "by"\` | ||
| }[OrderFields] | ||
| : 'Error: If you provide "take", you also need to provide "orderBy"' | ||
| : 'skip' extends Prisma.Keys<T> | ||
| ? 'orderBy' extends Prisma.Keys<T> | ||
| ? ByValid extends Prisma.True | ||
| ? {} | ||
| : { | ||
| [P in OrderFields]: P extends ByFields | ||
| ? never | ||
| : \`Error: Field "\${P}" in "orderBy" needs to be provided in "by"\` | ||
| }[OrderFields] | ||
| : 'Error: If you provide "skip", you also need to provide "orderBy"' | ||
| : ByValid extends Prisma.True | ||
| ? {} | ||
| : { | ||
| [P in OrderFields]: P extends ByFields | ||
| ? never | ||
| : \`Error: Field "\${P}" in "orderBy" needs to be provided in "by"\` | ||
| }[OrderFields] | ||
| `; | ||
| argsType = `Prisma.SubsetIntersection<T, Prisma.${capModel}GroupByArgs, OrderByArg> & InputErrors`; | ||
| resultType = `{} extends InputErrors ? Prisma.Get${capModel}GroupByPayload<T> : InputErrors`; | ||
| break; | ||
ymc9 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| default: | ||
| throw new PluginError(name, `Unsupported operation: "${operation}"`); | ||
| } | ||
|
|
||
| return { genericBase, argsType, resultType, argsOptional }; | ||
| } | ||
ymc9 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Uh oh!
There was an error while loading. Please reload this page.