Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/plugins/trpc/res/client/v10/next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type { AnyRouter } from '@trpc/server';
import type { NextPageContext } from 'next';
import { type CreateTRPCNext, createTRPCNext as _createTRPCNext } from '@trpc/next';
import type { DeepOverrideAtPath } from './utils';
import type { ClientType } from '../routers';

export function createTRPCNext<
TRouter extends AnyRouter,
Expand Down
12 changes: 12 additions & 0 deletions packages/plugins/trpc/res/client/v10/nuxt.ts
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>;
}
1 change: 0 additions & 1 deletion packages/plugins/trpc/res/client/v10/react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type { AnyRouter } from '@trpc/server';
import type { CreateTRPCReactOptions } from '@trpc/react-query/shared';
import { type CreateTRPCReact, createTRPCReact as _createTRPCReact } from '@trpc/react-query';
import type { DeepOverrideAtPath } from './utils';
import type { ClientType } from '../routers';

export function createTRPCReact<
TRouter extends AnyRouter,
Expand Down
14 changes: 14 additions & 0 deletions packages/plugins/trpc/res/client/v10/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,17 @@ export type DeepOverrideAtPath<T, R, Path extends string | undefined = undefined
: Path extends keyof T
? Omit<T, Path> & Record<Path, DeepOverride<T[Path], R>>
: never;

// Utility type from 'trpc-nuxt'
export type KeysOf<T> = Array<T extends T ? (keyof T extends string ? keyof T : never) : never>;

// Utility type from 'trpc-nuxt'
export type PickFrom<T, K extends Array<string>> = T extends Array<any>
? T
: T extends Record<string, any>
? keyof T extends K[number]
? T
: K[number] extends never
? T
: Pick<T, K[number]>
: T;
1 change: 0 additions & 1 deletion packages/plugins/trpc/res/client/v11/next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type { AnyTRPCRouter as AnyRouter } from '@trpc/server';
import type { NextPageContext } from 'next';
import { type CreateTRPCNext, createTRPCNext as _createTRPCNext } from '@trpc/next';
import type { DeepOverrideAtPath } from './utils';
import type { ClientType } from '../routers';

export function createTRPCNext<
TRouter extends AnyRouter,
Expand Down
12 changes: 12 additions & 0 deletions packages/plugins/trpc/res/client/v11/nuxt.ts
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>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Missing import of ClientType.

The type ClientType used on line 11 is not imported or defined in this file, which will lead to TypeScript compilation errors. Ensure that ClientType is properly imported from its module.

Apply this diff to import ClientType:

 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

Committable suggestion was skipped due to low confidence.

}
1 change: 0 additions & 1 deletion packages/plugins/trpc/res/client/v11/react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type { AnyTRPCRouter as AnyRouter } from '@trpc/server';
import type { CreateTRPCReactOptions } from '@trpc/react-query/shared';
import { type CreateTRPCReact, createTRPCReact as _createTRPCReact } from '@trpc/react-query';
import type { DeepOverrideAtPath } from './utils';
import type { ClientType } from '../routers';

export function createTRPCReact<
TRouter extends AnyRouter,
Expand Down
32 changes: 0 additions & 32 deletions packages/plugins/trpc/res/client/v11/utils.ts

This file was deleted.

1 change: 1 addition & 0 deletions packages/plugins/trpc/res/client/v11/utils.ts
285 changes: 285 additions & 0 deletions packages/plugins/trpc/src/client-helper/index.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 '..';
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

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;

default:
throw new PluginError(name, `Unsupported operation: "${operation}"`);
}

return { genericBase, argsType, resultType, argsOptional };
}
Loading
Loading