Custom client adapter
In addition to the provided React and Svelte client adapters, ptsq
offers the flexibility to create custom client adapters tailored to specific use cases or frameworks.
By creating a custom client adapter, you can seamlessly integrate ptsq
with any frontend framework or library of your choice. Whether you're working with Angular, Vue.js, or another frontend technology, a custom client adapter allows you to leverage the power and flexibility of ptsq
within your preferred development environment.
Custom client adapters enable you to define how ptsq
interacts with your frontend application, including data fetching, state management, and UI updates. This level of customization ensures that ptsq
seamlessly integrates with your frontend architecture, providing a smooth and efficient developer experience.
Whether you're building a complex web application or a lightweight mobile app, custom client adapters empower you to leverage the full potential of ptsq
while adhering to the specific requirements and conventions of your frontend stack.
Caution
This chapter requires very good knowledge of Typescript and good knowledge of the PTSQ as it uses low-level functions.
Defining query and mutation types
export type Query<
_TDescription extends string | undefined,
TDefinition extends {
args?: any;
output: any;
},
> = {
query: (requestInput: TDefinition['args']) => Promise<TDefinition['output']>;
};
import type { RequestOptions } from './createProxyClient';
export type Mutation<
_TDescription extends string | undefined,
TDefinition extends {
args?: any;
output: any;
},
> = {
mutate: (requestInput: TDefinition['args']) => Promise<TDefinition['output']>;
};
Conditional methods
Imagine we want to show like infiniteQuery
only if server accepts cursor
property.
export type Query<
_TDescription extends string | undefined,
TDefinition extends {
args?: any;
output: any;
},
> = {
query: (requestInput: TDefinition['args']) => Promise<TDefinition['output']>;
} & (TDefinition['args'] extends { pageParam: any }
? {
infiniteQuery: (
requestInput: Omit<TDefinition['args'], 'pageParam'>,
) => Promise<TDefinition['output']>;
}
: {});
Client type
Client type is for casting the result runtime Proxy-based client to correct types.
import type {
Route as ClientRoute,
Router as ClientRouter,
} from '@ptsq/client';
import type {
inferClientResolverArgs,
inferClientResolverOutput,
} from '@ptsq/server';
export type Client<TRouter extends ClientRouter> = {
[K in keyof TRouter['_def']['routes']]: TRouter['_def']['routes'][K] extends ClientRouter
? Client<TRouter['_def']['routes'][K]>
: TRouter['_def']['routes'][K] extends ClientRoute<'query'>
? Query<
TRouter['_def']['routes'][K]['_def']['description'],
{
args: inferClientResolverArgs<
TRouter['_def']['routes'][K]['_def']['argsSchema']
>;
output: inferClientResolverOutput<
TRouter['_def']['routes'][K]['_def']['outputSchema']
>;
}
>
: TRouter['_def']['routes'][K] extends ClientRoute<'mutation'>
? Mutation<
TRouter['_def']['routes'][K]['_def']['description'],
{
args: inferClientResolverArgs<
TRouter['_def']['routes'][K]['_def']['argsSchema']
>;
output: inferClientResolverOutput<
TRouter['_def']['routes'][K]['_def']['outputSchema']
>;
}
>
: never;
};
This approach ensures that @ptsq/server
is only required for development purposes, while @ptsq/client
remains a production dependency.
Client runtime
import {
createProxyUntypedClient,
httpFetch,
UndefinedAction,
type Router as ClientRouter,
type CreateProxyClientArgs,
} from '@ptsq/client';
import type { Client } from './types';
export const createCustomClient = <TRouter extends ClientRouter>(
options: CreateProxyClientArgs,
): ReactClientRouter<TRouter> =>
createProxyUntypedClient<[any, any]>({
route: [],
fetch: ({ route, type, args }) => {
switch (type) {
case 'cachedQuery':
return cached({
queryKey: [...route],
fetch: (context) =>
httpFetch({
...options,
body: {
route: route.join('.'),
type: 'query',
input: args[0],
},
signal: context.signal,
}),
...args[1],
});
default:
throw new UndefinedAction();
}
},
}) as ReactClientRouter<TRouter>;
This simple client does not allow mutations, it's not very practical, it's for simplicity.
Of course, you can define as many methods as you want and create a runtime for them in the switch in fetch
function in the createProxyUntypedClient
.