├── .gitignore ├── README.MD ├── package-lock.json ├── package.json ├── rollup.config.js ├── scripts └── cp.js ├── src ├── adapter.ts ├── index.ts ├── types.ts └── utils.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | ### Base 2 | 3 | ```ts 4 | import { createSolidAPIHandler } from "solid-start-trpc"; 5 | 6 | const handler = createSolidAPIHandler({ 7 | router: appRouter, 8 | createContext: () => null, 9 | }); 10 | export const get = handler; 11 | export const post = handler; 12 | 13 | // for version 0.1.6 + 14 | export const GET = handler; 15 | export const POST = handler; 16 | ``` 17 | 18 | ### Ctx 19 | 20 | ```ts 21 | const createContext = (opts) => ({ req: opts.req, res: opts.res }); 22 | ``` 23 | 24 | ### Response Meta 25 | 26 | ```ts 27 | const handler = createSolidAPIHandler({ 28 | router: appRouter, 29 | createContext, 30 | responseMeta: ({ ctx }) => { 31 | if (ctx?.req.headers.get("x-random-header")) { 32 | return { 33 | headers: { 34 | "x-random-header": `hello-${ctx.req.headers.get("x-random-header")}`, 35 | }, 36 | }; 37 | } 38 | return {}; 39 | }, 40 | }); 41 | ``` 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solid-start-trpc", 3 | "version": "0.0.16", 4 | "type": "module", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "scripts": { 8 | "build": "tsc && rollup -c && node scripts/cp", 9 | "patch": "npm version patch --no-git-tag-version" 10 | }, 11 | "exports": { 12 | ".": { 13 | "solid": "./dist/index.js", 14 | "default": "./dist/index.js" 15 | } 16 | }, 17 | "sideEffects": false, 18 | "files": [ 19 | "dist" 20 | ], 21 | "devDependencies": { 22 | "@rollup/plugin-babel": "^6.0.0", 23 | "@rollup/plugin-node-resolve": "^15.0.0", 24 | "@solidjs/meta": "^0.28.0", 25 | "@trpc/client": "^10.1.0", 26 | "@trpc/server": "^10.1.0", 27 | "@types/node": "^18.7.14", 28 | "solid-js": "^1.5.7", 29 | "solid-start": "^0.2.1", 30 | "typescript": "^4.8.2" 31 | }, 32 | "peerDependencies": { 33 | "@trpc/client": "^10.0.0", 34 | "@trpc/server": "^10.0.0", 35 | "solid-js": "^1.5.7", 36 | "solid-start": "^0.2.1" 37 | }, 38 | "keywords": [ 39 | "SolidJS", 40 | "tRPC", 41 | "SolidStart" 42 | ], 43 | "author": "OrJDev", 44 | "license": "ISC" 45 | } 46 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from "@rollup/plugin-babel"; 2 | import nodeResolve from "@rollup/plugin-node-resolve"; 3 | 4 | export default { 5 | input: "src/index.ts", 6 | output: [ 7 | { 8 | file: "dist/index.js", 9 | format: "es", 10 | }, 11 | ], 12 | external: ["solid-js", "solid-js/web", "solid-start"], 13 | plugins: [ 14 | nodeResolve({ 15 | extensions: [".js", ".ts", ".tsx"], 16 | }), 17 | babel({ 18 | extensions: [".js", ".ts", ".tsx"], 19 | babelHelpers: "bundled", 20 | presets: ["solid", "@babel/preset-typescript"], 21 | exclude: "node_modules/**", 22 | }), 23 | ], 24 | }; 25 | -------------------------------------------------------------------------------- /scripts/cp.js: -------------------------------------------------------------------------------- 1 | import { existsSync } from "fs"; 2 | import fs from "fs/promises"; 3 | import path from "path"; 4 | import { fileURLToPath } from "url"; 5 | 6 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 7 | 8 | async function main() { 9 | if (!existsSync(path.join(__dirname, "../dist"))) { 10 | await fs.mkdir(path.join(__dirname, "../dist")); 11 | } 12 | await fs.copyFile( 13 | path.join(__dirname, "../README.MD"), 14 | path.join(__dirname, "../dist/README.MD") 15 | ); 16 | } 17 | main().catch((e) => { 18 | console.log(e); 19 | }); 20 | -------------------------------------------------------------------------------- /src/adapter.ts: -------------------------------------------------------------------------------- 1 | import { type AnyRouter } from "@trpc/server"; 2 | import { type HTTPRequest } from "@trpc/server/dist/declarations/src/http/internals/types"; 3 | import { type ApiHandler, type APIEvent } from "solid-start/api/types"; 4 | import { 5 | type ICreateSolidAPIHandlerOpts, 6 | type createSolidAPIHandlerContext, 7 | } from "./types"; 8 | import { getPath, notFoundError } from "./utils"; 9 | import { resolveHTTPResponse } from "@trpc/server/http"; 10 | 11 | export function createSolidAPIHandler( 12 | opts: ICreateSolidAPIHandlerOpts 13 | ): ApiHandler { 14 | return async (args: APIEvent) => { 15 | const path = getPath(args); 16 | if (path === null) { 17 | return notFoundError(opts); 18 | } 19 | const res: createSolidAPIHandlerContext["res"] = { 20 | headers: {}, 21 | }; 22 | const url = new URL(args.request.url); 23 | const req: HTTPRequest = { 24 | query: url.searchParams, 25 | method: args.request.method, 26 | headers: Object.fromEntries(args.request.headers), 27 | body: await args.request.text(), 28 | }; 29 | const result = await resolveHTTPResponse({ 30 | router: opts.router, 31 | responseMeta: opts.responseMeta, 32 | req, 33 | path, 34 | createContext: async () => 35 | await opts.createContext?.({ 36 | req: args.request, 37 | res, 38 | }), 39 | }); 40 | const mRes = new Response(result.body, { 41 | status: result.status, 42 | }); 43 | for (const [key, value] of Object.entries( 44 | result.headers ? { ...res.headers, ...result.headers } : res.headers 45 | )) { 46 | if (typeof value === "undefined") { 47 | continue; 48 | } 49 | if (typeof value === "string") { 50 | mRes.headers.set(key, value); 51 | continue; 52 | } 53 | for (const v of value) { 54 | mRes.headers.append(key, v); 55 | } 56 | } 57 | return mRes; 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./adapter"; 2 | export * from "./types"; 3 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { type AnyRouter, type inferRouterContext } from "@trpc/server"; 2 | import { type ResponseMetaFn } from "@trpc/server/dist/http/internals/types"; 3 | 4 | export type createSolidAPIHandlerContext = { 5 | req: Request; 6 | res: { 7 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 8 | headers: Record; 9 | }; 10 | }; 11 | export type CreateContextFn = ( 12 | ctx: createSolidAPIHandlerContext 13 | ) => inferRouterContext | Promise>; 14 | 15 | export type ICreateProps = { 16 | router: TRouter; 17 | createContext: CreateContextFn; 18 | }; 19 | 20 | export type ICreateSolidAPIHandlerOpts = { 21 | router: TRouter; 22 | createContext: CreateContextFn; 23 | responseMeta?: ResponseMetaFn; 24 | }; 25 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { AnyRouter, TRPCError } from "@trpc/server"; 2 | import { TRPCErrorResponse } from "@trpc/server/rpc"; 3 | import { APIEvent } from "solid-start/api/types"; 4 | import { ICreateSolidAPIHandlerOpts } from "./types"; 5 | 6 | export function getPath(args: APIEvent): string | null { 7 | const p: any = args.params.trpc; 8 | if (typeof p === "string") { 9 | return p; 10 | } 11 | if (Array.isArray(p)) { 12 | return p.join("/"); 13 | } 14 | return null; 15 | } 16 | 17 | export function notFoundError( 18 | opts: ICreateSolidAPIHandlerOpts 19 | ) { 20 | const error = opts.router.getErrorShape({ 21 | error: new TRPCError({ 22 | message: 23 | 'Query "trpc" not found - is the file named `[trpc]`.ts or `[...trpc].ts`?', 24 | code: "INTERNAL_SERVER_ERROR", 25 | }), 26 | type: "unknown", 27 | ctx: undefined, 28 | path: undefined, 29 | input: undefined, 30 | }); 31 | const json: TRPCErrorResponse = { 32 | id: -1, 33 | error, 34 | }; 35 | return new Response(JSON.stringify(json), { status: 500 }); 36 | } 37 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "allowSyntheticDefaultImports": true, 5 | "target": "esnext", 6 | "newLine": "LF", 7 | "moduleResolution": "node", 8 | "strict": false, 9 | "jsx": "preserve", 10 | "jsxImportSource": "solid-js", 11 | "outDir": "./dist", 12 | "module": "esnext", 13 | "types": ["node", "vite/client"] 14 | }, 15 | "include": ["./src"], 16 | "exclude": ["node_modules", "dist"] 17 | } 18 | --------------------------------------------------------------------------------