This commit is contained in:
OrJDev
2022-09-26 20:46:10 +03:00
commit 4063212f39
12 changed files with 2758 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
node_modules/
dist/
+102
View File
@@ -0,0 +1,102 @@
# SolidJS tRPC
## Getting Started
I recommend using [Create JD App](https://github.com/OrJDev/create-jd-app) but if you want to create a project from scratch, you can follow the steps below:
### Installation
```bash
npm install solid-trpc
```
### Creating A Client
```ts
// utils/trpc.ts
import { IAppRouter } from "api/src/routers/_route"; // your trpc appRouter type
import { createSolidQueryHooks } from "solid-trpc";
export const trpc = createSolidQueryHooks<IAppRouter>();
export const client = trpc.createClient({
url: "/api/trpc", // your trpc server url
});
```
### TRPC Provider
```tsx
// App.tsx
import { QueryClient } from "@tanstack/solid-query";
import { Component } from "solid-js";
import { TRPCProvider } from "solid-trpc";
import { client } from "./utils/trpc"; // the client we created above
import Home from "./pages/Home"; // any page that uses trpc
interface IAppProps {}
const queryClient = new QueryClient();
const App: Component<IAppProps> = ({}) => {
return (
<TRPCProvider client={client} queryClient={queryClient}>
<Home />
</TRPCProvider>
);
};
export default App;
```
### Queries
```tsx
// pages/Home.tsx
import { Component } from "solid-js";
import { trpc } from "../../utils/trpc";
interface IHomeProps {}
const Home: Component<IHomeProps> = ({}) => {
const test = trpc.createQuery(() => ["example.test", { name: "example" }]);
return (
<div>
<h1>{test.data ?? "no data || yet"}</h1>
</div>
);
};
export default Home;
```
### Mutations
```tsx
// pages/Home.tsx
import { Component, onMount } from "solid-js";
import { trpc } from "../../utils/trpc";
interface IHomeProps {}
const Home: Component<IHomeProps> = ({}) => {
const test = trpc.createMutation(() => "example.mTest");
onMount(() => {
test.mutateAsync({ number: 3 });
});
return (
<div>
<h1>{test.data ?? "no data || yet"}</h1>
</div>
);
};
export default Home;
```
## What is different
- No SSR
- Replace "use" with "create" (e.g. useQuery -> createQuery)
- Query paths (key and input) are being treated as Solid Accessor (callback) instead of a string (eg: useQuery(["MyKey", {...}]) -> createQuery(()=> ["MyKey", {...}]))
+11
View File
@@ -0,0 +1,11 @@
module.exports = {
env: {
test: {
presets: [
['@babel/preset-env', { targets: { node: 'current' } }],
'@babel/preset-typescript'
],
plugins: ['babel-plugin-jsx-dom-expressions']
}
}
};
+1985
View File
File diff suppressed because it is too large Load Diff
+53
View File
@@ -0,0 +1,53 @@
{
"name": "solid-trpc",
"description": "SolidJS tRPC",
"author": "OrJDev",
"license": "MIT",
"version": "0.0.2",
"publishConfig": {
"access": "public"
},
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"solid": "./dist/index.jsx",
"default": "./dist/index.js"
}
},
"files": [
"dist"
],
"sideEffects": false,
"scripts": {
"build": "tsc && rollup -c && node scripts/cp",
"patch": "npm version patch --no-git-tag-version"
},
"devDependencies": {
"@babel/core": "^7.18.13",
"@babel/preset-typescript": "^7.18.6",
"@rollup/plugin-babel": "5.3.1",
"@rollup/plugin-node-resolve": "13.3.0",
"@tanstack/query-core": "^4.6.1",
"@tanstack/solid-query": "^4.6.1",
"@trpc/client": "^9.27.2",
"@trpc/server": "^9.27.2",
"@types/node": "^18.7.14",
"babel-preset-solid": "^1.5.3",
"rollup": "^2.79.0",
"solid-js": "^1.5.3",
"typescript": "^4.8.2"
},
"peerDependencies": {
"@tanstack/query-core": "^4.6.1",
"@tanstack/solid-query": "^4.6.1",
"@trpc/client": "^9.12.0",
"@trpc/server": "^9.12.0",
"solid-js": "^1.5.3"
},
"keywords": [
"solidjs",
"trpc"
]
}
+24
View File
@@ -0,0 +1,24 @@
import babel from "@rollup/plugin-babel";
import nodeResolve from "@rollup/plugin-node-resolve";
export default {
input: "src/index.tsx",
output: [
{
file: "dist/index.js",
format: "es",
},
],
external: ["solid-js", "solid-js/web"],
plugins: [
nodeResolve({
extensions: [".js", ".ts", ".tsx"],
}),
babel({
extensions: [".js", ".ts", ".tsx"],
babelHelpers: "bundled",
presets: ["solid", "@babel/preset-typescript"],
exclude: "node_modules/**",
}),
],
};
+19
View File
@@ -0,0 +1,19 @@
import { existsSync } from "fs";
import fs from "fs/promises";
import path from "path";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
async function main() {
if (!existsSync(path.join(__dirname, "../dist"))) {
await fs.mkdir(path.join(__dirname, "../dist"));
}
await fs.copyFile(
path.join(__dirname, "../README.MD"),
path.join(__dirname, "../dist/README.MD")
);
}
main().catch((e) => {
console.log(e);
});
+253
View File
@@ -0,0 +1,253 @@
import {
CreateTRPCClientOptions,
TRPCClient,
TRPCClientErrorLike,
TRPCRequestOptions,
createTRPCClient,
} from "@trpc/client";
import type {
AnyRouter,
ProcedureRecord,
inferHandlerInput,
inferProcedureInput,
inferProcedureOutput,
inferSubscriptionOutput,
} from "@trpc/server";
import {
CreateInfiniteQueryOptions,
CreateInfiniteQueryResult,
CreateMutationOptions,
CreateMutationResult,
CreateQueryOptions,
CreateQueryResult,
createInfiniteQuery as __createInfiniteQuery,
createMutation as __createMutation,
createQuery as __createQuery,
} from "@tanstack/solid-query";
import {
Context,
createEffect,
onCleanup,
useContext as __useContext,
} from "solid-js";
import { TRPCContext, TRPCContextState } from "./types";
import { getClientArgs } from "./utils";
export type OutputWithCursor<TData, TCursor extends any = any> = {
cursor: TCursor | null;
data: TData;
};
type OmitContext<T> = Omit<T, "context"> & {
context?: TRPCRequestOptions["context"];
};
export interface CreateTRPCQueryOptions<TPath, TInput, TOutput, TData, TError>
extends OmitContext<
CreateQueryOptions<TOutput, TError, TData, () => [TPath, TInput]>
>,
TRPCRequestOptions {}
export interface CreateTRPCInfiniteQueryOptions<TPath, TInput, TOutput, TError>
extends OmitContext<
CreateInfiniteQueryOptions<
TOutput,
TError,
TOutput,
TOutput,
() => [TPath, TInput]
>
>,
TRPCRequestOptions {}
export interface CreateTRPCMutationOptions<
TInput,
TError,
TOutput,
TContext = unknown
> extends OmitContext<CreateMutationOptions<TOutput, TError, TInput, TContext>>,
TRPCRequestOptions {}
type inferInfiniteQueryNames<
TObj extends ProcedureRecord<any, any, any, any, any, any>
> = {
[TPath in keyof TObj]: inferProcedureInput<TObj[TPath]> extends {
cursor?: any;
}
? TPath
: never;
}[keyof TObj];
type inferProcedures<
TObj extends ProcedureRecord<any, any, any, any, any, any>
> = {
[TPath in keyof TObj]: {
input: inferProcedureInput<TObj[TPath]>;
output: inferProcedureOutput<TObj[TPath]>;
};
};
export function createSolidQueryHooks<TRouter extends AnyRouter>() {
type TQueries = TRouter["_def"]["queries"];
type TSubscriptions = TRouter["_def"]["subscriptions"];
type TError = TRPCClientErrorLike<TRouter>;
type TInfiniteQueryNames = inferInfiniteQueryNames<TQueries>;
type TQueryValues = inferProcedures<TRouter["_def"]["queries"]>;
type TMutationValues = inferProcedures<TRouter["_def"]["mutations"]>;
type ProviderContext = TRPCContextState<TRouter>;
const Context = TRPCContext as Context<ProviderContext>;
function createClient(
opts: CreateTRPCClientOptions<TRouter>
): TRPCClient<TRouter> {
return createTRPCClient(opts);
}
function useContext() {
return __useContext(Context);
}
function createQuery<
TPath extends keyof TQueryValues & string,
TQueryFnData = TQueryValues[TPath]["output"],
TData = TQueryValues[TPath]["output"]
>(
pathAndInput: () => [
path: TPath,
...args: inferHandlerInput<TQueries[TPath]>
],
opts?: CreateTRPCQueryOptions<
TPath,
TQueryValues[TPath]["input"],
TQueryFnData,
TData,
TError
>
): CreateQueryResult<TData, TError> {
const ctx = useContext();
if (
typeof window === "undefined" &&
opts?.enabled !== false &&
!ctx.queryClient.getQueryCache().find(pathAndInput())
) {
ctx.prefetchQuery(pathAndInput as any, opts as any);
}
return __createQuery(pathAndInput, () =>
(ctx.client as any).query(...getClientArgs(pathAndInput(), opts))
);
}
function createMutation<
TPath extends keyof TMutationValues & string,
TContext = unknown
>(
path: () => TPath | [TPath],
opts?: CreateTRPCMutationOptions<
TMutationValues[TPath]["input"],
TError,
TMutationValues[TPath]["output"],
TContext
>
): CreateMutationResult<
TMutationValues[TPath]["output"],
TError,
TMutationValues[TPath]["input"],
TContext
> {
const ctx = useContext();
return __createMutation((input) => {
const curr = path();
const actualPath = Array.isArray(curr) ? curr[0] : curr;
return (ctx.client.mutation as any)(actualPath, input, opts);
}, opts as any);
}
function createInfiniteQuery<TPath extends TInfiniteQueryNames & string>(
pathAndInput: () => [
path: TPath,
input: Omit<TQueryValues[TPath]["input"], "cursor">
],
opts?: CreateTRPCInfiniteQueryOptions<
TPath,
Omit<TQueryValues[TPath]["input"], "cursor">,
TQueryValues[TPath]["output"],
TError
>
): CreateInfiniteQueryResult<TQueryValues[TPath]["output"], TError> {
const [path, input] = pathAndInput();
const { client, prefetchInfiniteQuery, queryClient } = useContext();
if (
typeof window === "undefined" &&
opts?.enabled !== false &&
!queryClient.getQueryCache().find(pathAndInput())
) {
prefetchInfiniteQuery(pathAndInput as any, opts as any);
}
return __createInfiniteQuery(
pathAndInput as any,
({ pageParam }) => {
const actualInput = { ...((input as any) ?? {}), cursor: pageParam };
return (client as any).query(
...getClientArgs([path, actualInput], opts)
);
},
opts as any
);
}
function createSubscription<
TPath extends keyof TSubscriptions & string,
TOutput extends inferSubscriptionOutput<TRouter, TPath>
>(
pathAndInput: () => [
path: TPath,
...args: inferHandlerInput<TSubscriptions[TPath]>
],
opts: {
enabled?: boolean;
onError?: (err: TError) => void;
onNext: (data: TOutput) => void;
}
) {
const enabled = opts?.enabled ?? true;
const { client } = useContext();
return createEffect(() => {
if (!enabled) {
return;
}
const [path, input] = pathAndInput();
let isStopped = false;
const unsub = client.subscription(path, (input ?? undefined) as any, {
onError: (err) => {
if (!isStopped) {
opts.onError?.(err);
}
},
onNext: (res) => {
if (res.type === "data" && !isStopped) {
opts.onNext(res.data);
}
},
});
onCleanup(() => {
isStopped = true;
unsub();
});
});
}
return {
createClient,
useContext,
createQuery,
createMutation,
createInfiniteQuery,
createSubscription,
};
}
export { default as TRPCProvider } from "./provider";
+89
View File
@@ -0,0 +1,89 @@
import {
QueryClient,
QueryClientProvider,
QueryClientProviderProps,
} from "@tanstack/solid-query";
import { TRPCClient } from "@trpc/client";
import { AnyRouter } from "@trpc/server";
import { Context, JSX } from "solid-js";
import { TRPCContext, TRPCContextState } from "./types";
import { getClientArgs } from "./utils";
export default function TRPCProvider<TRouter extends AnyRouter>(props: {
queryClient: QueryClient;
client: TRPCClient<TRouter>;
children: JSX.Element;
queryClientOpts?: Omit<QueryClientProviderProps, "client">;
}) {
const Context = TRPCContext as Context<TRPCContextState<TRouter>>;
return (
<Context.Provider
value={{
queryClient: props.queryClient,
client: props.client,
fetchQuery: (pathAndInput, opts) =>
props.queryClient.fetchQuery(
pathAndInput,
() =>
(props.client as any).query(...getClientArgs(pathAndInput, opts)),
opts
),
fetchInfiniteQuery: (pathAndInput, opts) =>
props.queryClient.fetchInfiniteQuery(
pathAndInput,
({ pageParam }) => {
const [path, input] = pathAndInput;
const actualInput = { ...(input as any), cursor: pageParam };
return (props.client as any).query(
...getClientArgs([path, actualInput], opts)
);
},
opts
),
prefetchQuery: (pathAndInput, opts) =>
props.queryClient.prefetchQuery(
pathAndInput,
() =>
(props.client as any).query(...getClientArgs(pathAndInput, opts)),
opts
),
prefetchInfiniteQuery: (pathAndInput, opts) =>
props.queryClient.prefetchInfiniteQuery(
pathAndInput,
({ pageParam }) => {
const [path, input] = pathAndInput;
const actualInput = { ...(input as any), cursor: pageParam };
return (props.client as any).query(
...getClientArgs([path, actualInput], opts)
);
},
opts
),
/**
* @deprecated use `invalidateQueries`
*/
invalidateQuery: (...args: any[]) =>
props.queryClient.invalidateQueries(...args),
invalidateQueries: (...args: any[]) =>
props.queryClient.invalidateQueries(...args),
refetchQueries: (...args: any[]) =>
props.queryClient.refetchQueries(...args),
cancelQuery: (pathAndInput) =>
props.queryClient.cancelQueries(pathAndInput),
setQueryData: (...args) => props.queryClient.setQueryData(...args),
getQueryData: (...args) => props.queryClient.getQueryData(...args),
setInfiniteQueryData: (...args) =>
props.queryClient.setQueryData(...args),
getInfiniteQueryData: (...args) =>
props.queryClient.getQueryData(...args),
}}
>
<QueryClientProvider
client={props.queryClient}
{...((props.queryClientOpts ?? {}) as any)}
>
{props.children}
</QueryClientProvider>
</Context.Provider>
);
}
+197
View File
@@ -0,0 +1,197 @@
import { TRPCClient, TRPCClientError, TRPCRequestOptions } from "@trpc/client";
import type {
AnyRouter,
inferHandlerInput,
inferProcedureInput,
inferProcedureOutput,
} from "@trpc/server";
import { createContext } from "solid-js";
import {
CancelOptions,
FetchInfiniteQueryOptions,
FetchQueryOptions,
InfiniteData,
InvalidateOptions,
InvalidateQueryFilters,
QueryClient,
RefetchOptions,
RefetchQueryFilters,
SetDataOptions,
} from "@tanstack/solid-query";
import { Updater } from "@tanstack/query-core";
interface TRPCFetchQueryOptions<TInput, TError, TOutput>
extends FetchQueryOptions<TInput, TError, TOutput>,
TRPCRequestOptions {}
interface TRPCFetchInfiniteQueryOptions<TInput, TError, TOutput>
extends FetchInfiniteQueryOptions<TInput, TError, TOutput>,
TRPCRequestOptions {}
export interface TRPCContextState<TRouter extends AnyRouter> {
queryClient: QueryClient;
client: TRPCClient<TRouter>;
/**
* @link https://react-query.tanstack.com/guides/prefetching
*/
fetchQuery<
TPath extends keyof TRouter["_def"]["queries"] & string,
TProcedure extends TRouter["_def"]["queries"][TPath],
TOutput extends inferProcedureOutput<TProcedure>,
TInput extends inferProcedureInput<TProcedure>
>(
pathAndInput: [path: TPath, ...args: inferHandlerInput<TProcedure>],
opts?: TRPCFetchQueryOptions<TInput, TRPCClientError<TRouter>, TOutput>
): Promise<TOutput>;
/**
* @link https://react-query.tanstack.com/guides/prefetching
*/
fetchInfiniteQuery<
TPath extends keyof TRouter["_def"]["queries"] & string,
TProcedure extends TRouter["_def"]["queries"][TPath],
TOutput extends inferProcedureOutput<TProcedure>,
TInput extends inferProcedureInput<TProcedure>
>(
pathAndInput: [path: TPath, ...args: inferHandlerInput<TProcedure>],
opts?: TRPCFetchInfiniteQueryOptions<
TInput,
TRPCClientError<TRouter>,
TOutput
>
): Promise<InfiniteData<TOutput>>;
/**
* @link https://react-query.tanstack.com/guides/prefetching
*/
prefetchQuery<
TPath extends keyof TRouter["_def"]["queries"] & string,
TProcedure extends TRouter["_def"]["queries"][TPath],
TOutput extends inferProcedureOutput<TProcedure>,
TInput extends inferProcedureInput<TProcedure>
>(
pathAndInput: [path: TPath, ...args: inferHandlerInput<TProcedure>],
opts?: TRPCFetchQueryOptions<TInput, TRPCClientError<TRouter>, TOutput>
): Promise<void>;
/**
* @link https://react-query.tanstack.com/guides/prefetching
*/
prefetchInfiniteQuery<
TPath extends keyof TRouter["_def"]["queries"] & string,
TProcedure extends TRouter["_def"]["queries"][TPath],
TOutput extends inferProcedureOutput<TProcedure>,
TInput extends inferProcedureInput<TProcedure>
>(
pathAndInput: [path: TPath, ...args: inferHandlerInput<TProcedure>],
opts?: TRPCFetchInfiniteQueryOptions<
TInput,
TRPCClientError<TRouter>,
TOutput
>
): Promise<void>;
/**
* @deprecated use `invalidateQueries`
*/
invalidateQuery<
TPath extends keyof TRouter["_def"]["queries"] & string,
TInput extends inferProcedureInput<TRouter["_def"]["queries"][TPath]>
>(
pathAndInput: [TPath, TInput?],
options?: InvalidateOptions
): Promise<void>;
/**
* @link https://react-query.tanstack.com/guides/query-invalidation
*/
invalidateQueries<
TPath extends keyof TRouter["_def"]["queries"] & string,
TInput extends inferProcedureInput<TRouter["_def"]["queries"][TPath]>
>(
pathAndInput?: [TPath, TInput?] | TPath,
filters?: InvalidateQueryFilters,
options?: InvalidateOptions
): Promise<void>;
/**
* @link https://react-query.tanstack.com/guides/query-invalidation
*/
invalidateQueries(
filters?: InvalidateQueryFilters,
options?: InvalidateOptions
): Promise<void>;
/**
* @link https://react-query.tanstack.com/reference/QueryClient#queryclientrefetchqueries
*/
refetchQueries<
TPath extends keyof TRouter["_def"]["queries"] & string,
TInput extends inferProcedureInput<TRouter["_def"]["queries"][TPath]>
>(
pathAndInput: [TPath, TInput?],
filters?: RefetchQueryFilters,
options?: RefetchOptions
): Promise<void>;
/**
* @link https://react-query.tanstack.com/reference/QueryClient#queryclientrefetchqueries
*/
refetchQueries(
filters?: RefetchQueryFilters,
options?: RefetchOptions
): Promise<void>;
/**
* @link https://react-query.tanstack.com/guides/query-cancellation
*/
cancelQuery<
TPath extends keyof TRouter["_def"]["queries"] & string,
TInput extends inferProcedureInput<TRouter["_def"]["queries"][TPath]>
>(
pathAndInput: [TPath, TInput?],
options?: CancelOptions
): Promise<void>;
/**
* @link https://react-query.tanstack.com/reference/QueryClient#queryclientsetquerydata
*/
setQueryData<
TPath extends keyof TRouter["_def"]["queries"] & string,
TInput extends inferProcedureInput<TRouter["_def"]["queries"][TPath]>,
TOutput extends inferProcedureOutput<TRouter["_def"]["queries"][TPath]>
>(
pathAndInput: [TPath, TInput?],
updater: Updater<TOutput | undefined, TOutput>,
options?: SetDataOptions
): void;
/**
* @link https://react-query.tanstack.com/reference/QueryClient#queryclientgetquerydata
*/
getQueryData<
TPath extends keyof TRouter["_def"]["queries"] & string,
TInput extends inferProcedureInput<TRouter["_def"]["queries"][TPath]>,
TOutput extends inferProcedureOutput<TRouter["_def"]["queries"][TPath]>
>(
pathAndInput: [TPath, TInput?]
): TOutput | undefined;
/**
* @link https://react-query.tanstack.com/reference/QueryClient#queryclientgetquerydata
*/
setInfiniteQueryData<
TPath extends keyof TRouter["_def"]["queries"] & string,
TInput extends inferProcedureInput<TRouter["_def"]["queries"][TPath]>,
TOutput extends inferProcedureOutput<TRouter["_def"]["queries"][TPath]>
>(
pathAndInput: [TPath, TInput?],
updater: Updater<InfiniteData<TOutput> | undefined, InfiniteData<TOutput>>,
options?: SetDataOptions
): void;
/**
* @link https://react-query.tanstack.com/reference/QueryClient#queryclientgetquerydata
*/
getInfiniteQueryData<
TPath extends keyof TRouter["_def"]["queries"] & string,
TInput extends inferProcedureInput<TRouter["_def"]["queries"][TPath]>,
TOutput extends inferProcedureOutput<TRouter["_def"]["queries"][TPath]>
>(
pathAndInput: [TPath, TInput?]
): InfiniteData<TOutput> | undefined;
}
export const TRPCContext = createContext(null as any);
+7
View File
@@ -0,0 +1,7 @@
export function getClientArgs<TPathAndInput extends unknown[], TOptions>(
pathAndInput: TPathAndInput,
opts: TOptions
) {
const [path, input] = pathAndInput;
return [path, input, opts] as const;
}
+16
View File
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"declaration": true,
"allowSyntheticDefaultImports": true,
"target": "esnext",
"newLine": "LF",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"outDir": "./dist",
"module": "esnext"
},
"include": ["./src"],
"exclude": ["node_modules", "dist"]
}