├── README.md ├── packages ├── typescript-extension │ ├── .gitignore │ ├── tsconfig.json │ ├── README.md │ ├── tsup.config.ts │ ├── CHANGELOG.md │ ├── src │ │ ├── change-callback.ts │ │ ├── tsfs.ts │ │ ├── project.ts │ │ ├── index.ts │ │ └── theme.ts │ ├── package.json │ └── scripts │ │ └── build-types.js ├── components │ ├── preact-inject.js │ ├── src │ │ ├── index.ts │ │ ├── types │ │ │ └── index.ts │ │ ├── components │ │ │ ├── tab │ │ │ │ ├── utils.ts │ │ │ │ ├── types.ts │ │ │ │ ├── base.tsx │ │ │ │ ├── draggable.tsx │ │ │ │ ├── dnd-sensors.ts │ │ │ │ ├── store.ts │ │ │ │ ├── group.tsx │ │ │ │ └── playground.tsx │ │ │ ├── utils.ts │ │ │ ├── queryBuilder │ │ │ │ ├── getDefaultOperation.ts │ │ │ │ └── index.tsx │ │ │ ├── sidebarOverlay.tsx │ │ │ ├── playground.tsx │ │ │ ├── toolbar.tsx │ │ │ ├── docs.tsx │ │ │ ├── provider.tsx │ │ │ ├── settings.tsx │ │ │ └── editor.tsx │ │ ├── editor │ │ │ ├── keymap.ts │ │ │ ├── theme.ts │ │ │ ├── createTRPCProxy.ts │ │ │ ├── extensions.ts │ │ │ └── transform-and-run-queries.ts │ │ └── utils │ │ │ └── playground-request.ts │ ├── tsconfig.json │ ├── README.md │ ├── tsup.config.ts │ ├── package.json │ └── CHANGELOG.md ├── trpc-playground │ ├── handlers │ │ ├── h3.d.ts │ │ ├── koa.d.ts │ │ ├── fetch.d.ts │ │ ├── next.d.ts │ │ ├── node.d.ts │ │ ├── express.d.ts │ │ ├── fastify.d.ts │ │ ├── h3.cjs │ │ ├── aws-lambda.d.ts │ │ ├── fetch.cjs │ │ ├── koa.cjs │ │ ├── next.cjs │ │ ├── node.cjs │ │ ├── express.cjs │ │ ├── fastify.cjs │ │ └── aws-lambda.cjs │ ├── src │ │ ├── html-version.ts │ │ ├── index.ts │ │ ├── handlers │ │ │ ├── koa.ts │ │ │ ├── node.ts │ │ │ ├── fetch.ts │ │ │ ├── aws-lambda.ts │ │ │ ├── next.ts │ │ │ ├── express.ts │ │ │ ├── h3.ts │ │ │ └── fastify.ts │ │ ├── config.ts │ │ ├── zod-resolve-types.ts │ │ ├── handler.ts │ │ └── get-default-input.ts │ ├── tsconfig.json │ ├── tsconfig.build.json │ ├── .eslintrc.cjs │ ├── tsup.config.ts │ ├── package.json │ ├── CHANGELOG.md │ └── README.md ├── types │ ├── README.md │ ├── tsconfig.json │ ├── CHANGELOG.md │ ├── package.json │ └── src │ │ └── index.d.ts ├── html │ ├── tsconfig.json │ ├── scripts │ │ ├── tsconfig.json │ │ ├── .eslintrc.cjs │ │ ├── serve.ts │ │ ├── build.ts │ │ └── template.ts │ ├── README.md │ ├── tailwind.config.cjs │ ├── index.html │ ├── index.d.ts │ ├── vite.config.ts │ ├── src │ │ ├── main.tsx │ │ └── global.css │ ├── postcss.config.cjs │ ├── package.json │ └── CHANGELOG.md ├── query-extension │ ├── tsconfig.json │ ├── src │ │ ├── index.ts │ │ ├── masked-eval.ts │ │ ├── find-cursor.ts │ │ ├── keymap.ts │ │ ├── find-queries.ts │ │ ├── gutter.ts │ │ ├── state.ts │ │ └── line-numbers.ts │ ├── README.md │ ├── tsup.config.ts │ ├── package.json │ └── CHANGELOG.md └── utils │ ├── tsconfig.json │ ├── README.md │ ├── tsup.config.ts │ ├── src │ └── index.ts │ ├── CHANGELOG.md │ └── package.json ├── apps ├── next │ ├── .gitignore │ ├── next-env.d.ts │ ├── pages │ │ └── api │ │ │ ├── trpc │ │ │ └── [trpc].ts │ │ │ └── trpc-playground.ts │ ├── package.json │ ├── tsconfig.json │ ├── next.config.js │ ├── README.md │ └── CHANGELOG.md ├── vite │ ├── client │ │ ├── vite-env.d.ts │ │ ├── preact.d.ts │ │ ├── main.tsx │ │ └── app.tsx │ ├── postcss.config.cjs │ ├── tailwind.config.cjs │ ├── README.md │ ├── index.html │ ├── tsconfig.json │ ├── package.json │ ├── vite.config.ts │ ├── server │ │ └── app.ts │ └── CHANGELOG.md ├── express │ ├── tsconfig.json │ ├── README.md │ ├── package.json │ ├── server.ts │ └── CHANGELOG.md └── router │ ├── tsconfig.json │ ├── package.json │ ├── CHANGELOG.md │ └── index.ts ├── .gitignore ├── pnpm-workspace.yaml ├── .vscode ├── extensions.json └── settings.json ├── .npmrc ├── .github ├── renovate.json5 ├── workflows │ ├── test.yaml │ └── release.yaml └── CONTRIBUTING.md ├── test └── basic.test.ts ├── turbo.json ├── .changeset ├── config.json └── README.md ├── scripts └── tsup-config.ts ├── tsconfig.json ├── dprint.json ├── .eslintrc.cjs ├── LICENSE └── package.json /README.md: -------------------------------------------------------------------------------- 1 | packages/trpc-playground/README.md 2 | -------------------------------------------------------------------------------- /packages/typescript-extension/.gitignore: -------------------------------------------------------------------------------- 1 | src/types -------------------------------------------------------------------------------- /apps/next/.gitignore: -------------------------------------------------------------------------------- 1 | # next.js 2 | /.next/ 3 | /out/ 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | *.tsbuildinfo 4 | .turbo 5 | -------------------------------------------------------------------------------- /apps/vite/client/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | - 'apps/*' 4 | -------------------------------------------------------------------------------- /packages/components/preact-inject.js: -------------------------------------------------------------------------------- 1 | export { Fragment, h } from 'preact' 2 | -------------------------------------------------------------------------------- /packages/components/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components/playground' 2 | -------------------------------------------------------------------------------- /packages/trpc-playground/handlers/h3.d.ts: -------------------------------------------------------------------------------- 1 | export * from '../dist/handlers/h3' 2 | -------------------------------------------------------------------------------- /packages/trpc-playground/handlers/koa.d.ts: -------------------------------------------------------------------------------- 1 | export * from '../dist/handlers/koa' 2 | -------------------------------------------------------------------------------- /packages/trpc-playground/handlers/fetch.d.ts: -------------------------------------------------------------------------------- 1 | export * from '../dist/handlers/fetch' 2 | -------------------------------------------------------------------------------- /packages/trpc-playground/handlers/next.d.ts: -------------------------------------------------------------------------------- 1 | export * from '../dist/handlers/next' 2 | -------------------------------------------------------------------------------- /packages/trpc-playground/handlers/node.d.ts: -------------------------------------------------------------------------------- 1 | export * from '../dist/handlers/node' 2 | -------------------------------------------------------------------------------- /packages/trpc-playground/handlers/express.d.ts: -------------------------------------------------------------------------------- 1 | export * from '../dist/handlers/express' 2 | -------------------------------------------------------------------------------- /packages/trpc-playground/handlers/fastify.d.ts: -------------------------------------------------------------------------------- 1 | export * from '../dist/handlers/fastify' 2 | -------------------------------------------------------------------------------- /packages/trpc-playground/handlers/h3.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../dist/handlers/h3') 2 | -------------------------------------------------------------------------------- /packages/trpc-playground/handlers/aws-lambda.d.ts: -------------------------------------------------------------------------------- 1 | export * from '../dist/handlers/aws-lambda' 2 | -------------------------------------------------------------------------------- /packages/trpc-playground/handlers/fetch.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../dist/handlers/fetch') 2 | -------------------------------------------------------------------------------- /packages/trpc-playground/handlers/koa.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../dist/handlers/koa') 2 | -------------------------------------------------------------------------------- /packages/trpc-playground/handlers/next.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../dist/handlers/next') 2 | -------------------------------------------------------------------------------- /packages/trpc-playground/handlers/node.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../dist/handlers/node') 2 | -------------------------------------------------------------------------------- /packages/trpc-playground/handlers/express.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../dist/handlers/express') 2 | -------------------------------------------------------------------------------- /packages/trpc-playground/handlers/fastify.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../dist/handlers/fastify') 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "dprint.dprint"] 3 | } 4 | -------------------------------------------------------------------------------- /apps/express/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["**/*.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /apps/router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["**/*.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/trpc-playground/handlers/aws-lambda.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../dist/handlers/aws-lambda') 2 | -------------------------------------------------------------------------------- /packages/types/README.md: -------------------------------------------------------------------------------- 1 | # @trpc-playground/types 2 | 3 | TypeScript definitions for the playground 4 | -------------------------------------------------------------------------------- /packages/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/trpc-playground/src/html-version.ts: -------------------------------------------------------------------------------- 1 | export { version } from '@trpc-playground/html/package.json' 2 | -------------------------------------------------------------------------------- /apps/vite/client/preact.d.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 2 | import JSX = preact.JSX 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | public-hoist-pattern[]=*eslint* 2 | public-hoist-pattern[]=@babel/plugin-* 3 | public-hoist-pattern[]=babel-plugin-* 4 | -------------------------------------------------------------------------------- /apps/express/README.md: -------------------------------------------------------------------------------- 1 | # express-app 2 | 3 | This is an Express server configured with tRPC and tRPC Playground middleware. 4 | -------------------------------------------------------------------------------- /packages/components/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src", "tsup.config.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | enabled: false, 3 | extends: ['config:js-lib', 'github>sachinraja/renovate-config:js'], 4 | } 5 | -------------------------------------------------------------------------------- /apps/vite/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const baseConfig = require('../../packages/html/postcss.config.cjs') 2 | module.exports = baseConfig 3 | -------------------------------------------------------------------------------- /packages/html/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src", "index.d.ts", "*.config.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/query-extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src", "tsup.config.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src", "handlers", "tsup.config.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/components/src/types/index.ts: -------------------------------------------------------------------------------- 1 | import { trpc } from '../components/provider' 2 | 3 | export type TrpcClient = ReturnType 4 | -------------------------------------------------------------------------------- /test/basic.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from 'vitest' 2 | 3 | test("message contains 'Hello'", () => { 4 | expect('Hello world').toContain('Hello') 5 | }) 6 | -------------------------------------------------------------------------------- /packages/html/scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.json", 3 | "compilerOptions": { 4 | "target": "esnext" 5 | }, 6 | "include": ["**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/utils/README.md: -------------------------------------------------------------------------------- 1 | # trpc-playground 2 | 3 | playground for tRPC 4 | 5 | https://user-images.githubusercontent.com/58836760/149615216-a2b2725d-abcc-4c59-9c51-e5b9718bf269.mp4 6 | -------------------------------------------------------------------------------- /apps/vite/client/main.tsx: -------------------------------------------------------------------------------- 1 | import '@trpc-playground/html/css' 2 | import { render } from 'preact' 3 | import { App } from './app' 4 | 5 | render(, document.getElementById('app')!) 6 | -------------------------------------------------------------------------------- /packages/trpc-playground/src/index.ts: -------------------------------------------------------------------------------- 1 | export type { TrpcPlaygroundConfig } from '@trpc-playground/types' 2 | export { resolveConfig } from './config' 3 | export * from './zod-resolve-types' 4 | -------------------------------------------------------------------------------- /packages/trpc-playground/src/handlers/koa.ts: -------------------------------------------------------------------------------- 1 | import { getKoaAdapter } from 'uttp/adapters/koa' 2 | import { handler } from '../handler' 3 | 4 | export const koaHandler = getKoaAdapter(handler) 5 | -------------------------------------------------------------------------------- /packages/trpc-playground/src/handlers/node.ts: -------------------------------------------------------------------------------- 1 | import { getNodeAdapter } from 'uttp/adapters/node' 2 | import { handler } from '../handler' 3 | 4 | export const nodeHandler = getNodeAdapter(handler) 5 | -------------------------------------------------------------------------------- /packages/trpc-playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "resolveJsonModule": true 5 | }, 6 | "include": ["src", "tsup.config.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /apps/vite/tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | const baseConfig = require('../../packages/html/tailwind.config.cjs') 2 | module.exports = { ...baseConfig, content: ['../../packages/components/src/components/**/*.tsx'] } 3 | -------------------------------------------------------------------------------- /packages/trpc-playground/src/handlers/fetch.ts: -------------------------------------------------------------------------------- 1 | import { getFetchAdapter } from 'uttp/adapters/fetch' 2 | import { handler } from '../handler' 3 | 4 | export const fetchHandler = getFetchAdapter(handler) 5 | -------------------------------------------------------------------------------- /packages/html/README.md: -------------------------------------------------------------------------------- 1 | # @trpc-playground/html 2 | 3 | static html build of the playground 4 | 5 | Exports a variable `cdnHtml` which contains the html with scripts linked from a CDN. Also exports the built css file. 6 | -------------------------------------------------------------------------------- /packages/trpc-playground/src/handlers/aws-lambda.ts: -------------------------------------------------------------------------------- 1 | import { getAwsLambdaAdapter } from 'uttp/adapters/aws-lambda' 2 | import { handler } from '../handler' 3 | 4 | export const awsLambdaHandler = getAwsLambdaAdapter(handler) 5 | -------------------------------------------------------------------------------- /packages/typescript-extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "module": "es2020" 5 | }, 6 | "include": [ 7 | "src", 8 | "tsup.config.ts" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "dprint.dprint", 3 | "dprint.path": "node_modules/dprint/dprint", 4 | // ignore @tailwind rules 5 | "css.lint.unknownAtRules": "ignore", 6 | "html.validate.scripts": false 7 | } 8 | -------------------------------------------------------------------------------- /apps/next/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /packages/query-extension/src/index.ts: -------------------------------------------------------------------------------- 1 | export { gutter } from './gutter' 2 | export { keymap } from './keymap' 3 | export { lineNumbers } from './line-numbers' 4 | export { maskedEval } from './masked-eval' 5 | export { state } from './state' 6 | -------------------------------------------------------------------------------- /packages/typescript-extension/README.md: -------------------------------------------------------------------------------- 1 | # @trpc-playground/typescript-extension 2 | 3 | copy of [prisma/text-editors/src/extensions/prisma-query](https://github.com/prisma/text-editors/tree/main/src/extensions/prisma-query) with no changes 4 | -------------------------------------------------------------------------------- /apps/next/pages/api/trpc/[trpc].ts: -------------------------------------------------------------------------------- 1 | import { appRouter } from '@router' 2 | import * as trpcNext from '@trpc/server/adapters/next' 3 | 4 | export default trpcNext.createNextApiHandler({ 5 | router: appRouter, 6 | createContext: () => ({}), 7 | }) 8 | -------------------------------------------------------------------------------- /apps/vite/README.md: -------------------------------------------------------------------------------- 1 | # vite-app 2 | 3 | This is a Vite frontend with an Express backend. Vite HMR reloads the components on changes and all package files are resolved to their source so they are watched. The backend is restarted with Nodemon on changes. 4 | -------------------------------------------------------------------------------- /packages/html/scripts/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | overrides: [ 3 | { 4 | files: '*.ts', 5 | parserOptions: { 6 | tsconfigRootDir: __dirname, 7 | project: ['./tsconfig.json'], 8 | }, 9 | }, 10 | ], 11 | } 12 | -------------------------------------------------------------------------------- /packages/components/README.md: -------------------------------------------------------------------------------- 1 | # @trpc-playground/components 2 | 3 | Preact components for the playground 4 | 5 | This package does not include any css. The css is exported from [`@trpc-playground/html`](https://github.com/sachinraja/trpc-playground/tree/main/packages/html). 6 | -------------------------------------------------------------------------------- /packages/query-extension/README.md: -------------------------------------------------------------------------------- 1 | # @trpc-playground/query-extension 2 | 3 | copy of [prisma/text-editors/src/extensions/prisma-query](https://github.com/prisma/text-editors/tree/main/src/extensions/prisma-query) with changes to search for trpc-playground query syntax instead 4 | -------------------------------------------------------------------------------- /packages/utils/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | import { tsupBundledConfig } from '../../scripts/tsup-config' 3 | 4 | const config = defineConfig({ 5 | ...tsupBundledConfig, 6 | entry: ['src/index.ts'], 7 | }) 8 | 9 | export default config 10 | -------------------------------------------------------------------------------- /packages/components/src/components/tab/utils.ts: -------------------------------------------------------------------------------- 1 | import { Tab } from './types' 2 | 3 | export const createNewDefaultTab = (): Tab => ({ 4 | id: Math.random().toFixed(5).slice(2), 5 | name: 'New Tab', 6 | doc: "await trpc.greeting.query({ text: 'client' })\nexport {}", 7 | }) 8 | -------------------------------------------------------------------------------- /packages/trpc-playground/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "emitDeclarationOnly": true, 6 | "noEmit": false, 7 | "declarationDir": "dist" 8 | }, 9 | "include": ["src"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/query-extension/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | import { tsupBundledConfig } from '../../scripts/tsup-config' 3 | 4 | const config = defineConfig({ 5 | ...tsupBundledConfig, 6 | entry: ['src/index.ts'], 7 | }) 8 | 9 | export default config 10 | -------------------------------------------------------------------------------- /packages/typescript-extension/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | import { tsupBundledConfig } from '../../scripts/tsup-config' 3 | 4 | const config = defineConfig({ 5 | ...tsupBundledConfig, 6 | entry: ['src/index.ts'], 7 | }) 8 | 9 | export default config 10 | -------------------------------------------------------------------------------- /packages/trpc-playground/src/handlers/next.ts: -------------------------------------------------------------------------------- 1 | import { TrpcPlaygroundConfig } from '@trpc-playground/types' 2 | import { NextApiHandler } from 'next' 3 | import { nodeHandler } from './node' 4 | 5 | export const nextHandler: (config: TrpcPlaygroundConfig) => Promise = nodeHandler 6 | -------------------------------------------------------------------------------- /packages/components/src/components/tab/types.ts: -------------------------------------------------------------------------------- 1 | export type Tab = { 2 | id: string 3 | name: string 4 | doc: string 5 | } 6 | 7 | export type Headers = Record 8 | 9 | export interface GlobalState { 10 | tabs: Tab[] 11 | headers: Headers 12 | currentTabId: string 13 | } 14 | -------------------------------------------------------------------------------- /packages/trpc-playground/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ignorePatterns: ['handlers'], 3 | overrides: [ 4 | { 5 | files: '*.ts', 6 | parserOptions: { 7 | tsconfigRootDir: __dirname, 8 | project: ['./tsconfig.json'], 9 | }, 10 | }, 11 | ], 12 | } 13 | -------------------------------------------------------------------------------- /packages/trpc-playground/src/handlers/express.ts: -------------------------------------------------------------------------------- 1 | import { TrpcPlaygroundConfig } from '@trpc-playground/types' 2 | import { Handler } from 'express' 3 | import { getExpressAdapter } from 'uttp/adapters/express' 4 | import { handler } from '../handler' 5 | 6 | export const expressHandler = getExpressAdapter(handler) 7 | -------------------------------------------------------------------------------- /apps/router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "router-app", 3 | "version": "1.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "type-check": "tsc" 8 | }, 9 | "dependencies": { 10 | "@trpc/server": "10.5.0", 11 | "superjson": "^1.8.1", 12 | "zod": "3.14.4" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/html/tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ['../components/src/components/**/*.tsx'], 3 | theme: { 4 | extend: { 5 | colors: { 6 | primary: 'var(--primary-color)', 7 | secondary: 'var(--secondary-color)', 8 | }, 9 | }, 10 | }, 11 | plugins: [], 12 | } 13 | -------------------------------------------------------------------------------- /packages/trpc-playground/src/handlers/h3.ts: -------------------------------------------------------------------------------- 1 | import { TrpcPlaygroundConfig } from '@trpc-playground/types' 2 | import { EventHandler } from 'h3' 3 | import { getH3Adapter } from 'uttp/adapters/h3' 4 | import { handler } from '../handler' 5 | 6 | export const h3Handler: (config: TrpcPlaygroundConfig) => Promise = getH3Adapter(handler) 7 | -------------------------------------------------------------------------------- /packages/utils/src/index.ts: -------------------------------------------------------------------------------- 1 | import inspect from 'object-inspect' 2 | import { transform } from 'sucrase-browser' 3 | 4 | export const printObject = (obj: unknown) => inspect(obj, { indent: 2, depth: 0 }) 5 | 6 | export const transformTs = (code: string) => 7 | transform(code, { 8 | transforms: ['typescript', 'imports'], 9 | }).code 10 | -------------------------------------------------------------------------------- /packages/query-extension/src/masked-eval.ts: -------------------------------------------------------------------------------- 1 | export const maskedEval = async (code: string, context: Record) => { 2 | const globalContext = { exports: {} } 3 | const mask = { ...globalContext, ...context } 4 | 5 | const asyncFunc = (new Function(`return async() => { with(this) {${code}} }`)).call(mask) 6 | return asyncFunc() 7 | } 8 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseBranch": "origin/main", 3 | "pipeline": { 4 | "build": { 5 | "dependsOn": [ 6 | "^build" 7 | ], 8 | "outputs": [ 9 | "dist/**" 10 | ] 11 | }, 12 | "type-check": { 13 | "dependsOn": [ 14 | "^build" 15 | ], 16 | "outputs": [] 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /apps/vite/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vite App 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | tRPC Playground 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/trpc-playground/src/handlers/fastify.ts: -------------------------------------------------------------------------------- 1 | import { TrpcPlaygroundConfig } from '@trpc-playground/types' 2 | import { FastifyPluginCallback } from 'fastify' 3 | import { getFastifyAdapter } from 'uttp/adapters/fastify' 4 | import { handler } from '../handler' 5 | 6 | export const getFastifyPlugin: (config: TrpcPlaygroundConfig) => Promise = getFastifyAdapter( 7 | handler, 8 | ) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@1.6.3/schema.json", 3 | "changelog": ["@changesets/changelog-github", { "repo": "sachinraja/trpc-playground" }], 4 | "commit": false, 5 | "linked": [["@trpc-playground/html", "trpc-playground"]], 6 | "access": "public", 7 | "baseBranch": "main", 8 | "updateInternalDependencies": "patch", 9 | "ignore": [] 10 | } 11 | -------------------------------------------------------------------------------- /apps/vite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "useDefineForClassFields": true, 6 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 7 | "esModuleInterop": false, 8 | "module": "ESNext", 9 | "resolveJsonModule": true, 10 | "skipLibCheck": true 11 | }, 12 | "include": ["client", "server", "vite.config.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /scripts/tsup-config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | 3 | export const tsupDefaultConfig = defineConfig({ 4 | format: ['cjs', 'esm'], 5 | dts: { 6 | resolve: true, 7 | }, 8 | clean: true, 9 | }) 10 | 11 | // for packages that are bundled in @trpc-playground/html 12 | export const tsupBundledConfig = defineConfig({ 13 | ...tsupDefaultConfig, 14 | format: ['esm'], 15 | target: 'node14', 16 | }) 17 | -------------------------------------------------------------------------------- /packages/html/index.d.ts: -------------------------------------------------------------------------------- 1 | import { DeepRequiredClientConfig, RenderOptions } from '@trpc-playground/types' 2 | 3 | export type RenderPlaygroundPageOptions = { 4 | clientConfig: DeepRequiredClientConfig 5 | } & { 6 | version?: RenderOptions['version'] 7 | cdnUrl: NonNullable 8 | } 9 | 10 | declare function renderPlaygroundPage(args: RenderPlaygroundPageOptions): string 11 | 12 | export { renderPlaygroundPage } 13 | -------------------------------------------------------------------------------- /apps/express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-app", 3 | "version": "1.0.4", 4 | "private": true, 5 | "scripts": { 6 | "dev": "tsx watch server.ts", 7 | "type-check": "tsc" 8 | }, 9 | "dependencies": { 10 | "@trpc/server": "10.5.0", 11 | "express": "4.18.2", 12 | "trpc-playground": "workspace:1.0.4" 13 | }, 14 | "devDependencies": { 15 | "@types/express": "4.17.15", 16 | "tsx": "3.12.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/html/vite.config.ts: -------------------------------------------------------------------------------- 1 | import preact from '@preact/preset-vite' 2 | import { UserConfig } from 'vite' 3 | 4 | const config: UserConfig = { 5 | build: { 6 | rollupOptions: { 7 | output: { 8 | // need index.css to keep same filename (without hash) for "exports" 9 | assetFileNames: 'assets/[name].[ext]', 10 | }, 11 | }, 12 | }, 13 | plugins: [ 14 | preact(), 15 | ], 16 | } 17 | 18 | export default config 19 | -------------------------------------------------------------------------------- /apps/next/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-app", 3 | "version": "1.0.4", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "clean": "rimraf .next", 10 | "type-check": "tsc" 11 | }, 12 | "dependencies": { 13 | "@trpc/server": "10.5.0", 14 | "next": "13.0.7", 15 | "react": "18.2.0", 16 | "react-dom": "18.2.0", 17 | "trpc-playground": "workspace:1.0.4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/components/src/components/utils.ts: -------------------------------------------------------------------------------- 1 | import { Atom } from 'jotai' 2 | 3 | /** 4 | * create type safe initialValues 5 | * @see https://jotai.org/docs/api/core#type-script 6 | */ 7 | export const createInitialValues = () => { 8 | const initialValues: (readonly [Atom, unknown])[] = [] 9 | const get = () => initialValues 10 | const set = (anAtom: Atom, value: Value) => { 11 | initialValues.push([anAtom, value]) 12 | } 13 | return { get, set } 14 | } 15 | -------------------------------------------------------------------------------- /packages/html/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { DeepRequiredClientConfig } from '@trpc-playground/types' 2 | import { render } from 'preact' 3 | import './global.css' 4 | 5 | // dprint-ignore 6 | import { Playground } from '@trpc-playground/components' 7 | 8 | ; // eslint-disable-next-line @typescript-eslint/no-explicit-any 9 | ;((window as any).TrpcPlayground) = { 10 | init(element: Element, config: DeepRequiredClientConfig) { 11 | render(, element) 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /packages/html/scripts/serve.ts: -------------------------------------------------------------------------------- 1 | import cors from 'cors' 2 | import express from 'express' 3 | import path from 'node:path' 4 | import { fileURLToPath } from 'node:url' 5 | 6 | const port = 45245 7 | const __dirname = path.dirname(fileURLToPath(import.meta.url)) 8 | 9 | const app = express() 10 | app.use(cors()) 11 | app.use('/@trpc-playground/html', express.static(path.resolve(__dirname, '..'))) 12 | 13 | app.listen(port, () => { 14 | console.log(`CDN server listening at http://localhost:${port}`) 15 | }) 16 | -------------------------------------------------------------------------------- /apps/router/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # router-app 2 | 3 | ## 1.0.0 4 | 5 | ### Major Changes 6 | 7 | - [`d226d7b`](https://github.com/sachinraja/trpc-playground/commit/d226d7b5829b79cce8bee3f70d6635f2f76fd796) Thanks [@sachinraja](https://github.com/sachinraja)! - update deps, fix bugs 8 | 9 | ## 1.0.0-next.0 10 | 11 | ### Major Changes 12 | 13 | - [`413fca6`](https://github.com/sachinraja/trpc-playground/commit/413fca6c4c4cb50b690d3fb76ea1b43a713275ef) Thanks [@sachinraja](https://github.com/sachinraja)! - support tRPC v10 14 | -------------------------------------------------------------------------------- /packages/html/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('tailwindcss')(), 4 | require('autoprefixer')(), 5 | require('postcss-prefix-selector')({ 6 | exclude: ['body'], 7 | prefix: '.trpc-playground', 8 | transform(prefix, selector, prefixedSelector) { 9 | if (['html', '.trpc-playground'].includes(selector)) { 10 | return prefix 11 | } else { 12 | return prefixedSelector 13 | } 14 | }, 15 | }), 16 | ], 17 | } 18 | -------------------------------------------------------------------------------- /packages/components/src/components/queryBuilder/getDefaultOperation.ts: -------------------------------------------------------------------------------- 1 | import { ResolvedRouterSchema } from '@trpc-playground/types' 2 | 3 | interface GenerateFnInputs { 4 | operationName: string 5 | types: ResolvedRouterSchema | null 6 | } 7 | 8 | export const getDefaultOperation = ( 9 | { operationName, types }: GenerateFnInputs, 10 | ): { value: string; inputLength: number } | null => { 11 | if (!types) return null 12 | return (operationName in types.mutations ? types.mutations : types.queries)[operationName].default 13 | } 14 | -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /apps/vite/client/app.tsx: -------------------------------------------------------------------------------- 1 | import { Playground } from '@trpc-playground/components' 2 | 3 | export function App() { 4 | return ( 5 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /packages/query-extension/src/find-cursor.ts: -------------------------------------------------------------------------------- 1 | import { EditorState } from '@codemirror/state' 2 | 3 | export function findFirstCursor(state: EditorState) { 4 | // A SelectionRange is a cursor. Even if the user has a "selection", their cursor is still at the edge of the selection 5 | const cursors = state.selection.asSingle().ranges 6 | return { pos: cursors[0].head } 7 | } 8 | 9 | export function isCursorInRange(state: EditorState, from: number, to: number) { 10 | const cursor = findFirstCursor(state) 11 | return (cursor?.pos >= from && cursor?.pos <= to) || false 12 | } 13 | -------------------------------------------------------------------------------- /packages/components/src/components/tab/base.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx' 2 | import { ComponentChildren, ComponentProps } from 'preact' 3 | import { forwardRef } from 'preact/compat' 4 | 5 | type BaseTabProps = { 6 | children: ComponentChildren 7 | } & ComponentProps<'div'> 8 | 9 | export const BaseTab = forwardRef(({ children, className, ...props }, ref) => ( 10 | 15 | {children} 16 | 17 | )) 18 | -------------------------------------------------------------------------------- /packages/html/src/global.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | .trpc-playground { 6 | --primary-color: #0e0e0e; 7 | --secondary-color: #151515; 8 | --primary-color-triplet: 14, 14, 14; 9 | --secondary-color-triplet: 21, 21, 21; 10 | } 11 | 12 | body { 13 | margin: 0; 14 | } 15 | 16 | .cm-editor { 17 | height: 100%; 18 | width: 100%; 19 | } 20 | 21 | .scroll::-webkit-scrollbar { 22 | width: 12px; 23 | height: 4px; 24 | } 25 | 26 | .scroll::-webkit-scrollbar-thumb { 27 | border-radius: 10px; 28 | background-color: rgba(var(--primary-color-triplet), 0.4); 29 | } -------------------------------------------------------------------------------- /apps/next/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "target": "es5", 5 | "lib": [ 6 | "dom", 7 | "dom.iterable", 8 | "esnext" 9 | ], 10 | "baseUrl": ".", 11 | "paths": { 12 | "@router": [ 13 | "../router" 14 | ], 15 | "@router/*": [ 16 | "../router/*" 17 | ] 18 | }, 19 | "allowJs": true, 20 | "resolveJsonModule": true, 21 | "incremental": true 22 | }, 23 | "include": [ 24 | "next-env.d.ts", 25 | "**/*.ts", 26 | "**/*.tsx" 27 | ], 28 | "exclude": [ 29 | "node_modules" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /packages/components/src/components/sidebarOverlay.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentChildren } from 'preact' 2 | 3 | type SidebarOverlayProps = { 4 | hide: () => void 5 | children: ComponentChildren 6 | } 7 | 8 | export const SidebarOverlay = ({ hide, children }: SidebarOverlayProps) => { 9 | return ( 10 | 11 | e.stopPropagation()} 13 | className='absolute h-screen top-0 z-10 overflow-auto bg-secondary right-0 w-4/12' 14 | > 15 | {children} 16 | 17 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "skipLibCheck": true, 9 | "allowSyntheticDefaultImports": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noEmit": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "jsxImportSource": "preact", 15 | "jsxFactory": "h", 16 | "jsxFragmentFactory": "Fragment" 17 | }, 18 | "include": [ 19 | "test", 20 | "scripts" 21 | ], 22 | "exclude": [ 23 | "node_modules", 24 | "dist" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - next 8 | pull_request: 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - uses: pnpm/action-setup@v2.2.1 18 | with: 19 | version: 7 20 | 21 | - uses: actions/setup-node@v3 22 | with: 23 | node-version: 14 24 | cache: pnpm 25 | 26 | - name: Install dependencies 27 | run: pnpm install 28 | 29 | - name: Build 30 | run: pnpm build 31 | 32 | - name: Lint 33 | run: pnpm lint 34 | -------------------------------------------------------------------------------- /apps/next/next.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | // const path = require('node:path') 3 | 4 | // const packagesDirPath = path.resolve(__dirname, '..', '..', 'packages') 5 | 6 | /** @type {import('next').NextConfig} */ 7 | module.exports = { 8 | experimental: { 9 | externalDir: true, 10 | }, 11 | // webpack(config) { 12 | // config.resolve.alias = { 13 | // ...config.resolve.alias, 14 | // '@trpc-playground/html': path.resolve(packagesDirPath, 'html', 'dist', 'index.js'), 15 | // 'trpc-playground': path.resolve(packagesDirPath, 'trpc-playground', 'src'), 16 | // } 17 | 18 | // return config 19 | // }, 20 | } 21 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Setup 2 | 3 | This project uses the [pnpm](https://pnpm.io/) package manager. You must have it installed to contribute. 4 | 5 | 1. Install: 6 | 7 | ```sh 8 | pnpm i 9 | ``` 10 | 11 | 2. Build packages: 12 | 13 | ```sh 14 | pnpm build 15 | ``` 16 | 17 | # Testing 18 | 19 | Go to `apps/vite` and run `pnpm dev`. View the playground at http://localhost:3001 (the Express/tRPC server is at http://localhost:3000). You can edit the router at `apps/router/index.ts` and the Express server will automatically reload and the playground will resolve the new types. If you want to change how often the playground types are refreshed, you can change `refreshTypesTimeout` in `apps/vite/server/app.ts` to a different value (in milliseconds). 20 | -------------------------------------------------------------------------------- /packages/components/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import { defineConfig } from 'tsup' 3 | import { tsupBundledConfig } from '../../scripts/tsup-config' 4 | 5 | const preactCompatPlugin = { 6 | name: 'preact-compat', 7 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 8 | setup(build: any) { 9 | const preact = path.join(process.cwd(), 'node_modules', 'preact', 'compat', 'dist', 'compat.module.js') 10 | 11 | build.onResolve({ filter: /^(react-dom|react)$/ }, () => { 12 | return { path: preact } 13 | }) 14 | }, 15 | } 16 | 17 | const config = defineConfig({ 18 | ...tsupBundledConfig, 19 | dts: true, 20 | entry: ['src/index.ts'], 21 | inject: ['preact-inject.js'], 22 | esbuildPlugins: [preactCompatPlugin], 23 | }) 24 | 25 | export default config 26 | -------------------------------------------------------------------------------- /apps/next/pages/api/trpc-playground.ts: -------------------------------------------------------------------------------- 1 | import { appRouter } from '@router' 2 | import { NextApiHandler } from 'next' 3 | import { nextHandler } from 'trpc-playground/handlers/next' 4 | 5 | const setupHandler = nextHandler({ 6 | router: appRouter, 7 | trpcApiEndpoint: '/api/trpc', 8 | playgroundEndpoint: '/api/trpc-playground', 9 | polling: { 10 | interval: 4000, 11 | }, 12 | // this option is for development, it should be removed on users' configs 13 | renderOptions: { 14 | cdnUrl: 'http://localhost:45245', 15 | version: null, 16 | }, 17 | request: { 18 | superjson: true, 19 | }, 20 | }) 21 | 22 | const handler: NextApiHandler = async (req, res) => { 23 | const playgroundHandler = await setupHandler 24 | await playgroundHandler(req, res) 25 | } 26 | 27 | export default handler 28 | -------------------------------------------------------------------------------- /dprint.json: -------------------------------------------------------------------------------- 1 | { 2 | "incremental": true, 3 | "indentWidth": 2, 4 | "typescript": { 5 | "quoteStyle": "preferSingle", 6 | "semiColons": "asi" 7 | }, 8 | "prettier": { 9 | "singleQuote": true, 10 | "associations": [ 11 | "**/*.yaml", 12 | "**/*.json5" 13 | ] 14 | }, 15 | "includes": ["**/*.{ts,tsx,js,jsx,cjs,mjs,json,json5,md,yaml}"], 16 | "excludes": ["**/node_modules", "pnpm-lock.yaml", "dist", ".next", "packages/typescript-extension/src/types"], 17 | "plugins": [ 18 | "https://plugins.dprint.dev/typescript-0.67.0.wasm", 19 | "https://plugins.dprint.dev/json-0.15.1.wasm", 20 | "https://plugins.dprint.dev/markdown-0.13.1.wasm", 21 | "https://plugins.dprint.dev/prettier-0.6.1.exe-plugin@2d54d5f8dbdeb087762fadc4840c688327f5deab447c94b32ca3850106db9224" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /packages/components/src/editor/keymap.ts: -------------------------------------------------------------------------------- 1 | import { closeBracketsKeymap, completionKeymap } from '@codemirror/autocomplete' 2 | import { defaultKeymap, historyKeymap, redo } from '@codemirror/commands' 3 | import { foldKeymap } from '@codemirror/language' 4 | import { lintKeymap } from '@codemirror/lint' 5 | import { searchKeymap } from '@codemirror/search' 6 | import { Extension } from '@codemirror/state' 7 | import { keymap as keymapFacet } from '@codemirror/view' 8 | 9 | export const baseKeymap = (): Extension => [ 10 | keymapFacet.of([ 11 | ...closeBracketsKeymap, 12 | ...defaultKeymap, 13 | ...searchKeymap, 14 | ...completionKeymap, 15 | ...foldKeymap, 16 | ...lintKeymap, 17 | ...historyKeymap, 18 | { 19 | run: redo, 20 | key: 'Mod-Shift-z', 21 | preventDefault: true, 22 | }, 23 | ]), 24 | ] 25 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits 16 | fetch-depth: 0 17 | 18 | - uses: pnpm/action-setup@v2.2.1 19 | with: 20 | version: 7 21 | 22 | - uses: actions/setup-node@v3 23 | with: 24 | node-version: 14 25 | cache: pnpm 26 | 27 | - name: Install dependencies 28 | run: pnpm install 29 | 30 | - name: Create release PR 31 | uses: changesets/action@v1 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | -------------------------------------------------------------------------------- /packages/utils/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @trpc-playground/utils 2 | 3 | ## 1.0.1 4 | 5 | ### Patch Changes 6 | 7 | - [`012cb1e`](https://github.com/sachinraja/trpc-playground/commit/012cb1e3888e69ddc76abf55492b2d3441989fa8) Thanks [@sachinraja](https://github.com/sachinraja)! - fix esm exports and print full object depth 8 | 9 | ## 1.0.0 10 | 11 | ### Major Changes 12 | 13 | - [`d226d7b`](https://github.com/sachinraja/trpc-playground/commit/d226d7b5829b79cce8bee3f70d6635f2f76fd796) Thanks [@sachinraja](https://github.com/sachinraja)! - update deps, fix bugs 14 | 15 | ## 1.0.0-next.0 16 | 17 | ### Major Changes 18 | 19 | - [`413fca6`](https://github.com/sachinraja/trpc-playground/commit/413fca6c4c4cb50b690d3fb76ea1b43a713275ef) Thanks [@sachinraja](https://github.com/sachinraja)! - support tRPC v10 20 | 21 | ## 0.1.0 22 | 23 | ### Minor Changes 24 | 25 | - aa288c7: first release 26 | -------------------------------------------------------------------------------- /packages/typescript-extension/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @trpc-playground/typescript-extension 2 | 3 | ## 1.0.0 4 | 5 | ### Major Changes 6 | 7 | - [`d226d7b`](https://github.com/sachinraja/trpc-playground/commit/d226d7b5829b79cce8bee3f70d6635f2f76fd796) Thanks [@sachinraja](https://github.com/sachinraja)! - update deps, fix bugs 8 | 9 | ## 1.0.0-next.0 10 | 11 | ### Major Changes 12 | 13 | - [`413fca6`](https://github.com/sachinraja/trpc-playground/commit/413fca6c4c4cb50b690d3fb76ea1b43a713275ef) Thanks [@sachinraja](https://github.com/sachinraja)! - support tRPC v10 14 | 15 | ## 0.2.0 16 | 17 | ### Minor Changes 18 | 19 | - [`c2f5e54`](https://github.com/sachinraja/trpc-playground/commit/c2f5e543056786b10ec1ebf59f32567a102de611) Thanks [@sachinraja](https://github.com/sachinraja)! - add interactive UI 20 | 21 | ## 0.1.0 22 | 23 | ### Minor Changes 24 | 25 | - aa288c7: first release 26 | -------------------------------------------------------------------------------- /packages/trpc-playground/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | import { tsupDefaultConfig } from '../../scripts/tsup-config' 3 | import htmlPkg from '../html/package.json' 4 | 5 | const config = defineConfig({ 6 | ...tsupDefaultConfig, 7 | entry: ['src/index.ts', 'src/handlers/*'], 8 | esbuildPlugins: [{ 9 | name: 'replace-version', 10 | setup(build) { 11 | build.onResolve({ filter: /^\.\/html-version$/ }, (args) => { 12 | return { 13 | path: args.path, 14 | namespace: 'html-version', 15 | } 16 | }) 17 | build.onLoad({ filter: /.*/, namespace: 'html-version' }, () => { 18 | console.log('replacing version') 19 | return { 20 | contents: JSON.stringify(htmlPkg), 21 | loader: 'json', 22 | } 23 | }) 24 | }, 25 | }], 26 | dts: false, 27 | }) 28 | 29 | export default config 30 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | }, 6 | parser: '@typescript-eslint/parser', 7 | extends: [ 8 | 'eslint:recommended', 9 | 'plugin:@typescript-eslint/recommended', 10 | 'prettier', 11 | ], 12 | plugins: [ 13 | '@typescript-eslint', 14 | ], 15 | ignorePatterns: ['node_modules', 'dist'], 16 | rules: { 17 | '@typescript-eslint/no-non-null-assertion': 'off', 18 | }, 19 | parserOptions: { 20 | extraFileExtensions: ['.cjs'], 21 | }, 22 | overrides: [ 23 | { 24 | files: '*.ts', 25 | parserOptions: { 26 | tsconfigRootDir: __dirname, 27 | project: ['./tsconfig.json', 'packages/*/tsconfig.json', 'apps/*/tsconfig.json'], 28 | }, 29 | }, 30 | { 31 | files: '*.cjs', 32 | rules: { 33 | '@typescript-eslint/no-var-requires': 'off', 34 | }, 35 | }, 36 | ], 37 | } 38 | -------------------------------------------------------------------------------- /apps/vite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-app", 3 | "version": "1.0.4", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "dev": "run-p dev:*", 8 | "dev:server": "tsx watch server/app.ts", 9 | "dev:client": "vite", 10 | "clean": "rimraf dist", 11 | "type-check": "tsc" 12 | }, 13 | "dependencies": { 14 | "@trpc-playground/components": "workspace:1.0.2", 15 | "cors": "2.8.5", 16 | "express": "4.18.2", 17 | "trpc-playground": "workspace:1.0.4" 18 | }, 19 | "devDependencies": { 20 | "@babel/core": "^7.20.5", 21 | "@preact/preset-vite": "2.5.0", 22 | "@trpc/server": "10.5.0", 23 | "@types/cors": "2.8.13", 24 | "@types/express": "4.17.15", 25 | "autoprefixer": "10.4.13", 26 | "postcss": "8.4.20", 27 | "postcss-prefix-selector": "1.16.0", 28 | "tailwindcss": "3.2.4", 29 | "tsx": "3.12.1", 30 | "vite": "4.0.1", 31 | "zod": "3.20.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/components/src/components/playground.tsx: -------------------------------------------------------------------------------- 1 | import { DeepRequiredClientConfig } from '@trpc-playground/types' 2 | import { ComponentChildren } from 'preact' 3 | import { Editor } from './editor' 4 | import { PlaygroundProvider } from './provider' 5 | import { TabGroup } from './tab/group' 6 | import { Toolbar } from './toolbar' 7 | 8 | export type PlaygroundProps = { 9 | config: DeepRequiredClientConfig 10 | children?: ComponentChildren 11 | } 12 | 13 | export const Playground = ({ config, children }: PlaygroundProps) => { 14 | return ( 15 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | {children} 26 | 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /packages/components/src/utils/playground-request.ts: -------------------------------------------------------------------------------- 1 | import { PlaygroundRequestOperation, ResolvedRouterSchema } from '@trpc-playground/types' 2 | 3 | type BodyObject = Record 4 | 5 | type MakePlaygroundRequestOptions = { 6 | playgroundEndpoint: string 7 | body?: BodyObject 8 | } 9 | 10 | export async function makePlaygroundRequest( 11 | operation: 'getRouterSchema', 12 | options: MakePlaygroundRequestOptions, 13 | ): Promise 14 | 15 | export async function makePlaygroundRequest( 16 | operation: Operation, 17 | { playgroundEndpoint, body }: MakePlaygroundRequestOptions, 18 | ) { 19 | const requestBody = JSON.stringify({ operation, ...body }) 20 | 21 | const response = await fetch(playgroundEndpoint, { 22 | method: 'POST', 23 | headers: { 24 | 'Content-Type': 'application/json', 25 | Accept: 'application/json', 26 | }, 27 | body: requestBody, 28 | }) 29 | 30 | return response.json() 31 | } 32 | -------------------------------------------------------------------------------- /packages/components/src/editor/theme.ts: -------------------------------------------------------------------------------- 1 | import { EditorView } from '@codemirror/view' 2 | 3 | export const baseTheme = EditorView.theme({ 4 | '&': { 5 | fontSize: '14px', 6 | fontFamily: 'JetBrains Mono', 7 | width: 'calc(100% - 30px)', 8 | backgroundColor: 'var(--primary-color) !important', 9 | }, 10 | '.cm-activeLine': { 11 | backgroundColor: 'rgba(var(--secondary-color-triplet), .5) !important', 12 | }, 13 | '.cm-selectionBackground': { 14 | backgroundColor: '#323232 !important', 15 | }, 16 | }) 17 | 18 | export const thisTsTheme = EditorView.theme({ 19 | '.cm-foldMarker': { 20 | width: '12px', 21 | height: '12px', 22 | marginLeft: '8px', 23 | 24 | '&.folded': { 25 | transform: 'rotate(-90deg)', 26 | }, 27 | }, 28 | '.cm-foldPlaceholder': { background: 'transparent', border: 'none' }, 29 | '.cm-tooltip': { 30 | maxWidth: '800px', 31 | zIndex: '999', 32 | }, 33 | '.cm-diagnostic, .cm-quickinfo-tooltip': { 34 | fontFamily: 'JetBrains Mono', 35 | }, 36 | }) 37 | -------------------------------------------------------------------------------- /packages/query-extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@trpc-playground/query-extension", 3 | "version": "1.0.1", 4 | "publishConfig": { 5 | "access": "public" 6 | }, 7 | "type": "module", 8 | "description": "parse query syntax for trpc-playground", 9 | "license": "Apache-2.0", 10 | "main": "dist/index.cjs", 11 | "types": "dist/index.d.ts", 12 | "exports": { 13 | "./package.json": "./package.json", 14 | ".": { 15 | "import": "./dist/index.js", 16 | "default": "./dist/index.cjs" 17 | } 18 | }, 19 | "files": [ 20 | "dist" 21 | ], 22 | "scripts": { 23 | "build": "tsup", 24 | "clean": "rimraf dist", 25 | "type-check": "tsc", 26 | "prepublishOnly": "pnpm build" 27 | }, 28 | "sideEffects": false, 29 | "dependencies": { 30 | "@codemirror/language": "^6.3.2", 31 | "@codemirror/state": "^6.1.4", 32 | "@codemirror/view": "^6.7.1", 33 | "@trpc-playground/utils": "workspace:1.0.1", 34 | "lodash": "^4.17.21" 35 | }, 36 | "devDependencies": { 37 | "@types/lodash": "4.14.191" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/components/src/components/tab/draggable.tsx: -------------------------------------------------------------------------------- 1 | import { useSortable } from '@dnd-kit/sortable' 2 | import { CSS } from '@dnd-kit/utilities' 3 | import { ComponentChildren, ComponentProps } from 'preact' 4 | import { useMemo } from 'preact/hooks' 5 | 6 | export type DraggableProps = { 7 | id: string 8 | className?: string 9 | children: ComponentChildren 10 | } 11 | 12 | export const Draggable = ({ id, className, children }: DraggableProps) => { 13 | const { 14 | attributes, 15 | listeners, 16 | setNodeRef, 17 | transform, 18 | transition, 19 | } = useSortable({ id }) 20 | 21 | const style = useMemo(() => ({ 22 | transform: CSS.Transform.toString(transform), 23 | transition, 24 | }), [transform, transition]) 25 | 26 | const props: ComponentProps<'div'> = useMemo(() => ({ 27 | ref: setNodeRef, 28 | ...listeners, 29 | ...attributes, 30 | style, 31 | }), [setNodeRef, listeners, attributes, style]) 32 | 33 | return ( 34 | 35 | {children} 36 | 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /apps/express/server.ts: -------------------------------------------------------------------------------- 1 | import * as trpcExpress from '@trpc/server/adapters/express' 2 | import express from 'express' 3 | import { expressHandler } from 'trpc-playground/handlers/express' 4 | import { appRouter } from '../router' 5 | 6 | const runApp = async () => { 7 | const app = express() 8 | 9 | const trpcApiEndpoint = '/api/trpc' 10 | const playgroundEndpoint = '/api/trpc-playground' 11 | 12 | app.use( 13 | trpcApiEndpoint, 14 | trpcExpress.createExpressMiddleware({ 15 | router: appRouter, 16 | createContext: () => ({}), 17 | }), 18 | ) 19 | 20 | app.use( 21 | playgroundEndpoint, 22 | await expressHandler({ 23 | trpcApiEndpoint, 24 | playgroundEndpoint, 25 | router: appRouter, 26 | // this option is for development, it should be removed on users' configs 27 | renderOptions: { 28 | cdnUrl: 'http://localhost:45245', 29 | version: null, 30 | }, 31 | }), 32 | ) 33 | 34 | app.listen(3000, () => { 35 | console.log('listening at http://localhost:3000') 36 | }) 37 | } 38 | 39 | runApp() 40 | -------------------------------------------------------------------------------- /packages/types/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @trpc-playground/types 2 | 3 | ## 1.0.0 4 | 5 | ### Major Changes 6 | 7 | - [`d226d7b`](https://github.com/sachinraja/trpc-playground/commit/d226d7b5829b79cce8bee3f70d6635f2f76fd796) Thanks [@sachinraja](https://github.com/sachinraja)! - update deps, fix bugs 8 | 9 | ## 1.0.0-next.0 10 | 11 | ### Major Changes 12 | 13 | - [`413fca6`](https://github.com/sachinraja/trpc-playground/commit/413fca6c4c4cb50b690d3fb76ea1b43a713275ef) Thanks [@sachinraja](https://github.com/sachinraja)! - support tRPC v10 14 | 15 | ## 0.2.0 16 | 17 | ### Minor Changes 18 | 19 | - [`c2f5e54`](https://github.com/sachinraja/trpc-playground/commit/c2f5e543056786b10ec1ebf59f32567a102de611) Thanks [@sachinraja](https://github.com/sachinraja)! - add interactive UI 20 | 21 | ## 0.1.1 22 | 23 | ### Patch Changes 24 | 25 | - [`1b2f93e`](https://github.com/sachinraja/trpc-playground/commit/1b2f93e780c3bddbf17d09c2a8f14e74e85b3fcb) Thanks [@sachinraja](https://github.com/sachinraja)! - add superjson transformer option 26 | 27 | ## 0.1.0 28 | 29 | ### Minor Changes 30 | 31 | - aa288c7: first release 32 | -------------------------------------------------------------------------------- /packages/components/src/components/tab/dnd-sensors.ts: -------------------------------------------------------------------------------- 1 | import { KeyboardSensor as LibKeyboardSensor, MouseSensor as LibMouseSensor } from '@dnd-kit/core' 2 | import { KeyboardEvent, MouseEvent } from 'react' 3 | 4 | export class MouseSensor extends LibMouseSensor { 5 | static activators = [ 6 | { 7 | eventName: 'onMouseDown' as const, 8 | handler: ({ nativeEvent: event }: MouseEvent) => { 9 | return shouldHandleEvent(event.target as HTMLElement) 10 | }, 11 | }, 12 | ] 13 | } 14 | 15 | export class KeyboardSensor extends LibKeyboardSensor { 16 | static activators = [ 17 | { 18 | eventName: 'onKeyDown' as const, 19 | handler: ({ nativeEvent: event }: KeyboardEvent) => { 20 | return shouldHandleEvent(event.target as HTMLElement) 21 | }, 22 | }, 23 | ] 24 | } 25 | 26 | function shouldHandleEvent(element: HTMLElement | null) { 27 | let cur = element 28 | 29 | while (cur) { 30 | if (cur.dataset && cur.dataset.noDnd) { 31 | return false 32 | } 33 | cur = cur.parentElement 34 | } 35 | 36 | return true 37 | } 38 | -------------------------------------------------------------------------------- /packages/components/src/editor/createTRPCProxy.ts: -------------------------------------------------------------------------------- 1 | interface ProxyCallbackOptions { 2 | path: string[] 3 | args: unknown[] 4 | } 5 | type ProxyCallback = (opts: ProxyCallbackOptions) => unknown 6 | 7 | type ClientCallbacks = Record void> 8 | 9 | export const createTRPCProxy = (client: ClientCallbacks) => { 10 | return createProxy(({ args, path }) => { 11 | const clientCallType = path.pop()! 12 | if (!['query', 'mutate'].includes(clientCallType)) return 13 | 14 | const fullPath = path.join('.') 15 | return client[clientCallType](fullPath, args[0]) 16 | }) 17 | } 18 | 19 | const createProxy = (cb: ProxyCallback, ...path: string[]) => { 20 | const proxy: unknown = new Proxy( 21 | // eslint-disable-next-line @typescript-eslint/no-empty-function 22 | () => {}, 23 | { 24 | get(_target, name) { 25 | if (typeof name === 'string') { 26 | return createProxy(cb, ...path, name) 27 | } 28 | }, 29 | apply(_1, _2, args) { 30 | return cb({ args, path }) 31 | }, 32 | }, 33 | ) 34 | 35 | return proxy 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Sachin Raja 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /apps/vite/vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import { defineConfig } from 'vite' 3 | import baseConfig from '../../packages/html/vite.config' 4 | 5 | const packagesDirPath = path.resolve(__dirname, '..', '..', 'packages') 6 | 7 | const createAliases = (packageNames: string[]) => 8 | packageNames.reduce((acc, packageName) => { 9 | acc[`@trpc-playground/${packageName}`] = path.resolve(packagesDirPath, packageName, 'src') 10 | return acc 11 | }, {} as Record) 12 | 13 | // https://vitejs.dev/config/ 14 | export default defineConfig({ 15 | server: { 16 | port: 3001, 17 | }, 18 | plugins: baseConfig.plugins, 19 | resolve: { 20 | alias: { 21 | ...createAliases([ 22 | 'components', 23 | 'query-extension', 24 | 'typescript-extension', 25 | 'utils', 26 | ]), 27 | 'trpc-playground': path.resolve(packagesDirPath, 'trpc-playground', 'src'), 28 | // should point to the source css file so that it changes when the component files change 29 | '@trpc-playground/html/css': path.resolve(packagesDirPath, 'html', 'src', 'global.css'), 30 | }, 31 | }, 32 | }) 33 | -------------------------------------------------------------------------------- /apps/router/index.ts: -------------------------------------------------------------------------------- 1 | import { initTRPC, router } from '@trpc/server' 2 | import superjson from 'superjson' 3 | import { z } from 'zod' 4 | 5 | const users = [ 6 | { name: 'user1', id: 0 }, 7 | { name: 'user2', id: 1 }, 8 | { name: 'user3', id: 2 }, 9 | ] 10 | 11 | const t = initTRPC.create({ transformer: superjson }) 12 | 13 | export const appRouter = t.router({ 14 | getUsers: t.procedure.query(() => { 15 | return users 16 | }), 17 | b: router().query('hi', { 18 | resolve() { 19 | return 'bruh' 20 | }, 21 | }).interop(), 22 | getUsersById: t.procedure 23 | .input(z.object({ 24 | userId: z.number(), 25 | })) 26 | .query(({ input }) => { 27 | return users.find(({ id }) => id === input.userId) 28 | }), 29 | removeLastUser: t.procedure.mutation(() => { 30 | users.splice(users.length - 1, 1) 31 | return users 32 | }), 33 | insertUser: t.procedure 34 | .input(z.object({ 35 | id: z.number(), 36 | name: z.string(), 37 | })) 38 | .mutation(({ input }) => { 39 | users.push(input) 40 | return input 41 | }), 42 | }) 43 | 44 | export type AppRouter = typeof appRouter 45 | -------------------------------------------------------------------------------- /packages/types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@trpc-playground/types", 3 | "type": "module", 4 | "version": "1.0.0", 5 | "publishConfig": { 6 | "access": "public" 7 | }, 8 | "description": "types for trpc-playground", 9 | "repository": { 10 | "directory": "packages/types", 11 | "type": "git", 12 | "url": "https://github.com/sachinraja/trpc-playground" 13 | }, 14 | "homepage": "https://github.com/sachinraja/trpc-playground#readme", 15 | "bugs": { 16 | "url": "https://github.com/sachinraja/trpc-playground/issues" 17 | }, 18 | "author": "Sachin Raja ", 19 | "license": "MIT", 20 | "keywords": [ 21 | "trpc", 22 | "playground", 23 | "types" 24 | ], 25 | "types": "src/index.d.ts", 26 | "exports": { 27 | "./package.json": "./package.json", 28 | ".": { 29 | "types": "./src/index.d.ts" 30 | } 31 | }, 32 | "files": [ 33 | "src" 34 | ], 35 | "scripts": { 36 | "type-check": "tsc" 37 | }, 38 | "peerDependencies": { 39 | "@trpc/server": "^10" 40 | }, 41 | "dependencies": { 42 | "ts-essentials": "^9.3.0" 43 | }, 44 | "devDependencies": { 45 | "@trpc/server": "10.5.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /apps/next/README.md: -------------------------------------------------------------------------------- 1 | # next-app 2 | 3 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). It is meant for testing the Next.js api handler. For developing the components with HMR and testing the Express handler, use the [Vite app](https://github.com/sachinraja/trpc-playground/tree/main/apps/vite). 4 | 5 | ## Getting Started 6 | 7 | First, run the development server: 8 | 9 | ```bash 10 | pnpm dev 11 | ``` 12 | 13 | Open http://localhost:3000/api/trpc-playground with your browser to see the playground. 14 | 15 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 16 | 17 | ## Learn More 18 | 19 | To learn more about Next.js, take a look at the following resources: 20 | 21 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 22 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 23 | 24 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 25 | -------------------------------------------------------------------------------- /packages/typescript-extension/src/change-callback.ts: -------------------------------------------------------------------------------- 1 | import { Extension, Facet } from '@codemirror/state' 2 | import { EditorView } from '@codemirror/view' 3 | import debounce from 'lodash/debounce' 4 | import noop from 'lodash/noop' 5 | import over from 'lodash/over' 6 | 7 | /** 8 | * A Facet that stores all registered `onChange` callbacks 9 | */ 10 | export type OnChange = (code: string, view: EditorView) => void 11 | const OnChangeFacet = Facet.define({ 12 | combine: input => { 13 | // If multiple `onChange` callbacks are registered, chain them (call them one after another) 14 | return over(input) 15 | }, 16 | }) 17 | 18 | /** 19 | * An extension that calls a (debounced) function when the editor content changes 20 | */ 21 | export const onChangeCallback = (onChange?: OnChange): Extension => { 22 | return [ 23 | OnChangeFacet.of(debounce(onChange || noop, 300)), 24 | EditorView.updateListener.of(({ view, docChanged }) => { 25 | if (docChanged) { 26 | // Call the onChange callback 27 | const content = view.state.sliceDoc(0) 28 | const onChange = view.state.facet(OnChangeFacet) 29 | onChange(content, view) 30 | } 31 | }), 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /packages/trpc-playground/src/config.ts: -------------------------------------------------------------------------------- 1 | import { TrpcPlaygroundConfig } from '@trpc-playground/types' 2 | import lodash from 'lodash' 3 | import { zodResolveTypes } from '.' 4 | import { version } from './html-version' 5 | 6 | const defineConfig = >(config: T): T => config 7 | 8 | const getDefaultConfig = () => 9 | defineConfig({ 10 | resolveTypes: zodResolveTypes, 11 | polling: { 12 | enable: true, 13 | interval: 4000, 14 | }, 15 | renderOptions: { 16 | cdnUrl: '//cdn.jsdelivr.net/npm', 17 | version, 18 | }, 19 | request: { 20 | globalHeaders: {}, 21 | superjson: false, 22 | }, 23 | server: { 24 | serveHtml: true, 25 | }, 26 | }) 27 | 28 | export const resolveConfig = (config: TrpcPlaygroundConfig) => { 29 | const resolvedConfig = lodash.merge({}, getDefaultConfig(), config) 30 | // fix for version being null 31 | const resolvedVersion = config?.renderOptions?.version === null 32 | ? null 33 | : resolvedConfig.renderOptions.version 34 | 35 | return { 36 | ...resolvedConfig, 37 | renderOptions: { 38 | ...resolvedConfig.renderOptions, 39 | version: resolvedVersion, 40 | }, 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /apps/vite/server/app.ts: -------------------------------------------------------------------------------- 1 | import * as trpcExpress from '@trpc/server/adapters/express' 2 | import cors from 'cors' 3 | import express from 'express' 4 | import { expressHandler } from '../../../packages/trpc-playground/src/handlers/express' 5 | import { appRouter } from '../../router' 6 | 7 | const runApp = async () => { 8 | const app = express() 9 | // Vite client makes requests from different port 10 | app.use(cors()) 11 | 12 | const trpcApiEndpoint = '/api/trpc' 13 | const playgroundEndpoint = '/api/trpc-playground' 14 | 15 | const trpcMiddleware = trpcExpress.createExpressMiddleware({ 16 | router: appRouter, 17 | createContext: () => ({}), 18 | }) 19 | app.use( 20 | trpcApiEndpoint, 21 | (req, res, next) => { 22 | trpcMiddleware(req, res, next) 23 | }, 24 | ) 25 | 26 | app.use( 27 | playgroundEndpoint, 28 | await expressHandler({ 29 | trpcApiEndpoint, 30 | playgroundEndpoint, 31 | router: appRouter, 32 | renderOptions: { 33 | cdnUrl: 'http://localhost:45245', 34 | version: null, 35 | }, 36 | }), 37 | ) 38 | 39 | app.listen(3000, () => { 40 | console.log('express server listening at http://localhost:3000') 41 | }) 42 | } 43 | 44 | runApp() 45 | -------------------------------------------------------------------------------- /packages/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@trpc-playground/utils", 3 | "type": "module", 4 | "version": "1.0.1", 5 | "publishConfig": { 6 | "access": "public" 7 | }, 8 | "description": "utilities for tRPC playground", 9 | "repository": { 10 | "directory": "packages/utils", 11 | "type": "git", 12 | "url": "https://github.com/sachinraja/trpc-playground" 13 | }, 14 | "homepage": "https://github.com/sachinraja/trpc-playground#readme", 15 | "bugs": { 16 | "url": "https://github.com/sachinraja/trpc-playground/issues" 17 | }, 18 | "author": "Sachin Raja ", 19 | "license": "MIT", 20 | "keywords": [ 21 | "trpc", 22 | "playground", 23 | "utils" 24 | ], 25 | "main": "dist/index.cjs", 26 | "module": "dist/index.js", 27 | "types": "dist/index.d.ts", 28 | "exports": { 29 | "./package.json": "./package.json", 30 | ".": "./dist/index.js" 31 | }, 32 | "files": [ 33 | "dist" 34 | ], 35 | "scripts": { 36 | "build": "tsup", 37 | "clean": "rimraf dist", 38 | "prepublishOnly": "pnpm build", 39 | "type-check": "tsc" 40 | }, 41 | "dependencies": { 42 | "object-inspect": "^1.12.2", 43 | "sucrase-browser": "^3.16.0" 44 | }, 45 | "devDependencies": { 46 | "@types/object-inspect": "1.8.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/typescript-extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@trpc-playground/typescript-extension", 3 | "version": "1.0.0", 4 | "publishConfig": { 5 | "access": "public" 6 | }, 7 | "type": "module", 8 | "description": "tsserver support for codemirror", 9 | "license": "Apache-2.0", 10 | "main": "dist/index.cjs", 11 | "types": "dist/index.d.ts", 12 | "exports": { 13 | "./package.json": "./package.json", 14 | ".": "./dist/index.js" 15 | }, 16 | "files": [ 17 | "dist" 18 | ], 19 | "scripts": { 20 | "build": "run-s build:*", 21 | "build:types": "node scripts/build-types.js", 22 | "build:js": "tsup", 23 | "clean": "rimraf dist src/types", 24 | "prepublishOnly": "pnpm build", 25 | "prepare": "pnpm build:types", 26 | "type-check": "tsc" 27 | }, 28 | "sideEffects": false, 29 | "dependencies": { 30 | "@codemirror/autocomplete": "^6.4.0", 31 | "@codemirror/lang-javascript": "^6.1.2", 32 | "@codemirror/lint": "^6.1.0", 33 | "@codemirror/state": "^6.1.4", 34 | "@codemirror/view": "^6.7.1", 35 | "@typescript/vfs": "^1.4.0", 36 | "localforage": "^1.10.0", 37 | "lodash": "^4.17.21" 38 | }, 39 | "peerDependencies": { 40 | "typescript": "^4" 41 | }, 42 | "devDependencies": { 43 | "@types/lodash": "4.14.191", 44 | "@types/node": "18.11.17", 45 | "fast-glob": "3.2.12", 46 | "typescript": "4.9.4" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/components/src/components/toolbar.tsx: -------------------------------------------------------------------------------- 1 | import { atom, useAtom } from 'jotai' 2 | import { ComponentChildren } from 'preact' 3 | import { Docs } from './docs' 4 | import { configAtom } from './provider' 5 | import { Settings } from './settings' 6 | 7 | const showDocsAtom = atom(false) 8 | const showSettingsAtom = atom(false) 9 | 10 | export const Toolbar = () => { 11 | const [config] = useAtom(configAtom) 12 | const [showDocs, setShowDocs] = useAtom(showDocsAtom) 13 | const [showSettings, setShowSettings] = useAtom(showSettingsAtom) 14 | 15 | return ( 16 | 17 | {config.trpcApiEndpoint} 18 | 19 | setShowDocs(true)}>Docs 20 | setShowSettings(true)}>Settings 21 | 22 | {showDocs && setShowDocs(false)} />} 23 | {showSettings && setShowSettings(false)} />} 24 | 25 | ) 26 | } 27 | 28 | type NavButtonProps = { 29 | onClick?: () => void 30 | children: ComponentChildren 31 | } 32 | const NavButton = ({ children, onClick }: NavButtonProps) => { 33 | return ( 34 | 38 | {children} 39 | 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /packages/query-extension/src/keymap.ts: -------------------------------------------------------------------------------- 1 | import { EditorState, Extension } from '@codemirror/state' 2 | import { keymap as keymapFacet } from '@codemirror/view' 3 | import { findFirstCursor } from './find-cursor' 4 | import { Query } from './find-queries' 5 | import { OnErrorFacet, OnExecuteFacet, queryStateField } from './state' 6 | 7 | export function runQueryUnderCursor(state: EditorState) { 8 | const onExecute = state.facet(OnExecuteFacet) 9 | const onError = state.facet(OnErrorFacet) 10 | 11 | if (!onExecute) { 12 | return false 13 | } 14 | 15 | const firstCursor = findFirstCursor(state) 16 | if (!firstCursor) { 17 | return true 18 | } 19 | 20 | let query: Query | null = null 21 | let initArgs: Promise 22 | state 23 | .field(queryStateField) 24 | .between(firstCursor.pos, firstCursor.pos, (from, to, q) => { 25 | initArgs = q.init() 26 | query = q.query 27 | return false 28 | }) 29 | 30 | if (!query) { 31 | return true 32 | } 33 | 34 | initArgs!.then(() => onExecute(query!)).catch((e) => { 35 | if (onError) return onError(e) 36 | throw e 37 | }) 38 | 39 | return true 40 | } 41 | 42 | /** 43 | * Shortcuts relating to the Query extension 44 | */ 45 | export function keymap(): Extension { 46 | return [ 47 | keymapFacet.of([ 48 | { 49 | key: 'Alt-q', 50 | run: ({ state }) => { 51 | runQueryUnderCursor(state) 52 | return true 53 | }, 54 | }, 55 | ]), 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /packages/query-extension/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @trpc-playground/query-extension 2 | 3 | ## 1.0.1 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [[`012cb1e`](https://github.com/sachinraja/trpc-playground/commit/012cb1e3888e69ddc76abf55492b2d3441989fa8)]: 8 | - @trpc-playground/utils@1.0.1 9 | 10 | ## 1.0.0 11 | 12 | ### Major Changes 13 | 14 | - [`d226d7b`](https://github.com/sachinraja/trpc-playground/commit/d226d7b5829b79cce8bee3f70d6635f2f76fd796) Thanks [@sachinraja](https://github.com/sachinraja)! - update deps, fix bugs 15 | 16 | ### Patch Changes 17 | 18 | - Updated dependencies [[`d226d7b`](https://github.com/sachinraja/trpc-playground/commit/d226d7b5829b79cce8bee3f70d6635f2f76fd796)]: 19 | - @trpc-playground/utils@1.0.0 20 | 21 | ## 1.0.0-next.0 22 | 23 | ### Major Changes 24 | 25 | - [`413fca6`](https://github.com/sachinraja/trpc-playground/commit/413fca6c4c4cb50b690d3fb76ea1b43a713275ef) Thanks [@sachinraja](https://github.com/sachinraja)! - support tRPC v10 26 | 27 | ### Patch Changes 28 | 29 | - Updated dependencies [[`413fca6`](https://github.com/sachinraja/trpc-playground/commit/413fca6c4c4cb50b690d3fb76ea1b43a713275ef)]: 30 | - @trpc-playground/utils@1.0.0-next.0 31 | 32 | ## 0.2.0 33 | 34 | ### Minor Changes 35 | 36 | - [`c2f5e54`](https://github.com/sachinraja/trpc-playground/commit/c2f5e543056786b10ec1ebf59f32567a102de611) Thanks [@sachinraja](https://github.com/sachinraja)! - add interactive UI 37 | 38 | ## 0.1.0 39 | 40 | ### Minor Changes 41 | 42 | - aa288c7: first release 43 | 44 | ### Patch Changes 45 | 46 | - Updated dependencies [aa288c7] 47 | - @trpc-playground/utils@0.1.0 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "type": "module", 4 | "private": true, 5 | "scripts": { 6 | "format": "dprint fmt", 7 | "lint": "run-p lint:*", 8 | "lint:format": "dprint check", 9 | "lint:types": "turbo run type-check", 10 | "lint:js": "eslint .", 11 | "build": "turbo run build --scope=\"@trpc-playground/**\" --scope=\"trpc-playground\" --no-deps", 12 | "clean": "pnpm -r clean", 13 | "dev:vite-server": "pnpm --filter vite-app dev:server", 14 | "dev:vite-client": "pnpm --filter vite-app dev:client", 15 | "dev:serve-html": "pnpm --filter @trpc-playground/html serve-dev", 16 | "dev": "run-p dev:*", 17 | "prod": "pnpm build && run-p prod:*", 18 | "prod:next-server": "pnpm --filter next-app dev", 19 | "prod:serve-html": "pnpm --filter @trpc-playground/html serve-dev" 20 | }, 21 | "devDependencies": { 22 | "@changesets/changelog-github": "0.4.7", 23 | "@changesets/cli": "2.25.2", 24 | "@types/node": "18.11.17", 25 | "@typescript-eslint/eslint-plugin": "5.46.1", 26 | "@typescript-eslint/parser": "5.46.1", 27 | "dprint": "0.33.0", 28 | "eslint": "8.30.0", 29 | "eslint-config-prettier": "8.5.0", 30 | "npm-run-all": "4.1.5", 31 | "preact": "10.11.3", 32 | "rimraf": "3.0.2", 33 | "tsup": "6.5.0", 34 | "turbo": "1.6.3", 35 | "typescript": "4.9.4" 36 | }, 37 | "sideEffects": false, 38 | "pnpm": { 39 | "packageExtensions": { 40 | "@lezer/javascript": { 41 | "dependencies": { 42 | "@lezer/common": "*" 43 | } 44 | } 45 | } 46 | }, 47 | "packageManager": "pnpm@7.9.0" 48 | } 49 | -------------------------------------------------------------------------------- /packages/components/src/components/docs.tsx: -------------------------------------------------------------------------------- 1 | import { QueryDefaultAndType, ResolvedRouterSchema } from '@trpc-playground/types' 2 | import { useAtom } from 'jotai' 3 | import React from 'react' 4 | import { typesAtom } from './provider' 5 | import { SidebarOverlay } from './sidebarOverlay' 6 | 7 | type DocsProps = { 8 | hide: () => void 9 | } 10 | 11 | export const Docs = ({ hide }: DocsProps) => { 12 | const [types] = useAtom(typesAtom) 13 | 14 | return ( 15 | 16 | {types && ( 17 | <> 18 | 22 | 26 | > 27 | )} 28 | 29 | ) 30 | } 31 | 32 | interface DocsForOperationTypeProps { 33 | label: string 34 | operations: QueryDefaultAndType 35 | } 36 | 37 | const DocsForOperationType: React.FC = ({ label, operations }) => ( 38 | 39 | 40 | {label} 41 | {Object.keys(operations).length} 42 | 43 | {Object.entries(operations).map(([name, def]) => ( 44 | 45 | {name} 46 | 47 | {def.type} 48 | 49 | 50 | ))} 51 | 52 | ) 53 | -------------------------------------------------------------------------------- /packages/components/src/editor/extensions.ts: -------------------------------------------------------------------------------- 1 | import { autocompletion, closeBrackets } from '@codemirror/autocomplete' 2 | import { history } from '@codemirror/commands' 3 | import { javascript } from '@codemirror/lang-javascript' 4 | import { bracketMatching, indentOnInput } from '@codemirror/language' 5 | import { highlightSelectionMatches } from '@codemirror/search' 6 | import { EditorState, Extension } from '@codemirror/state' 7 | import { oneDark } from '@codemirror/theme-one-dark' 8 | import { 9 | drawSelection, 10 | dropCursor, 11 | highlightActiveLine, 12 | highlightActiveLineGutter, 13 | highlightSpecialChars, 14 | rectangularSelection, 15 | } from '@codemirror/view' 16 | import * as queryExtension from '@trpc-playground/query-extension' 17 | import { tsTheme, typescript } from '@trpc-playground/typescript-extension' 18 | import { baseKeymap } from './keymap' 19 | import { baseTheme, thisTsTheme } from './theme' 20 | 21 | const basicSetup: Extension = [ 22 | highlightActiveLineGutter(), 23 | highlightSpecialChars(), 24 | history(), 25 | drawSelection(), 26 | dropCursor(), 27 | EditorState.allowMultipleSelections.of(true), 28 | indentOnInput(), 29 | bracketMatching(), 30 | closeBrackets(), 31 | rectangularSelection(), 32 | highlightActiveLine(), 33 | highlightSelectionMatches(), 34 | baseKeymap(), 35 | ] 36 | 37 | export const baseExtension: Extension = [ 38 | baseTheme, 39 | oneDark, 40 | ] 41 | 42 | export const tsExtension: Extension = [ 43 | basicSetup, 44 | baseExtension, 45 | thisTsTheme, 46 | tsTheme, 47 | javascript({ typescript: true }), 48 | queryExtension.gutter(), 49 | queryExtension.lineNumbers(), 50 | queryExtension.keymap(), 51 | typescript(), 52 | ] 53 | -------------------------------------------------------------------------------- /packages/html/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@trpc-playground/html", 3 | "version": "1.0.4", 4 | "publishConfig": { 5 | "access": "public" 6 | }, 7 | "description": "html for trpc-playground", 8 | "type": "module", 9 | "repository": { 10 | "directory": "packages/html", 11 | "type": "git", 12 | "url": "https://github.com/sachinraja/trpc-playground" 13 | }, 14 | "homepage": "https://github.com/sachinraja/trpc-playground#readme", 15 | "bugs": { 16 | "url": "https://github.com/sachinraja/trpc-playground/issues" 17 | }, 18 | "author": "Sachin Raja ", 19 | "license": "MIT", 20 | "keywords": [ 21 | "trpc", 22 | "html", 23 | "playground" 24 | ], 25 | "types": "index.d.ts", 26 | "main": "dist/index.cjs", 27 | "style": "dist/assets/index.css", 28 | "exports": { 29 | "./package.json": "./package.json", 30 | ".": { 31 | "types": "./index.d.ts", 32 | "import": "./dist/index.js", 33 | "default": "./dist/index.cjs" 34 | }, 35 | "./css": "./dist/assets/index.css" 36 | }, 37 | "files": [ 38 | "dist", 39 | "index.d.ts" 40 | ], 41 | "scripts": { 42 | "build": "vite build && pnpm build-cdn-html", 43 | "build-cdn-html": "tsx scripts/build.ts", 44 | "serve-dev": "tsx scripts/serve.ts", 45 | "clean": "rimraf dist", 46 | "prepublishOnly": "pnpm build", 47 | "type-check": "tsc" 48 | }, 49 | "dependencies": { 50 | "vite": "^4.0.1", 51 | "xss": "^1.0.14" 52 | }, 53 | "devDependencies": { 54 | "@babel/core": "^7.20.5", 55 | "@preact/preset-vite": "2.5.0", 56 | "@trpc-playground/components": "workspace:1.0.2", 57 | "@trpc-playground/types": "workspace:1.0.0", 58 | "@types/cors": "2.8.13", 59 | "@types/express": "4.17.15", 60 | "autoprefixer": "10.4.13", 61 | "cors": "2.8.5", 62 | "esbuild": "0.14.34", 63 | "express": "4.18.2", 64 | "node-html-parser": "6.1.4", 65 | "postcss": "8.4.20", 66 | "postcss-prefix-selector": "1.16.0", 67 | "tailwindcss": "3.2.4", 68 | "tsx": "3.12.1" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /packages/trpc-playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "trpc-playground", 3 | "type": "module", 4 | "version": "1.0.4", 5 | "publishConfig": { 6 | "access": "public" 7 | }, 8 | "description": "playground for running tRPC queries in the browser", 9 | "repository": { 10 | "directory": "packages/trpc-playground", 11 | "type": "git", 12 | "url": "https://github.com/sachinraja/trpc-playground" 13 | }, 14 | "homepage": "https://github.com/sachinraja/trpc-playground#readme", 15 | "bugs": { 16 | "url": "https://github.com/sachinraja/trpc-playground/issues" 17 | }, 18 | "author": "Sachin Raja ", 19 | "license": "MIT", 20 | "keywords": [ 21 | "trpc", 22 | "playground" 23 | ], 24 | "main": "dist/index.cjs", 25 | "module": "dist/index.js", 26 | "types": "dist/index.d.ts", 27 | "exports": { 28 | "./package.json": "./package.json", 29 | ".": { 30 | "import": "./dist/index.js", 31 | "default": "./dist/index.cjs" 32 | }, 33 | "./handlers/*": { 34 | "types": "./dist/handlers/*.d.ts", 35 | "import": "./dist/handlers/*.js", 36 | "default": "./dist/handlers/*.cjs" 37 | } 38 | }, 39 | "files": [ 40 | "dist", 41 | "handlers" 42 | ], 43 | "scripts": { 44 | "build": "pnpm clean && run-p build:*", 45 | "build:js": "tsup", 46 | "build:types": "tsc -p tsconfig.build.json", 47 | "clean": "rimraf dist", 48 | "prepublishOnly": "pnpm build", 49 | "watch": "tsup --watch --no-dts", 50 | "type-check": "tsc" 51 | }, 52 | "peerDependencies": { 53 | "@trpc/server": "^10", 54 | "zod": "^3" 55 | }, 56 | "dependencies": { 57 | "@trpc-playground/html": "workspace:1.0.4", 58 | "@trpc-playground/types": "workspace:1.0.0", 59 | "lodash": "^4.17.21", 60 | "uttp": "^0.1.3", 61 | "zod-to-ts": "^1.1.2" 62 | }, 63 | "devDependencies": { 64 | "@trpc/server": "10.5.0", 65 | "@types/aws-lambda": "8.10.109", 66 | "@types/express": "4.17.15", 67 | "@types/koa": "2.13.5", 68 | "@types/lodash": "4.14.191", 69 | "fastify": "4.10.2", 70 | "h3": "1.0.2", 71 | "next": "13.0.7", 72 | "zod": "3.20.2" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/components/src/components/tab/store.ts: -------------------------------------------------------------------------------- 1 | import { EditorView } from '@codemirror/view' 2 | import { atom } from 'jotai' 3 | import { GlobalState } from './types' 4 | import { createNewDefaultTab } from './utils' 5 | 6 | const defaultConfig = { 7 | tabs: [createNewDefaultTab()], 8 | headers: {}, 9 | currentTabId: '0', 10 | } 11 | 12 | export const getInitialState = (): GlobalState => { 13 | const config = localStorage.getItem('trpc-playground-config') 14 | 15 | if (config !== null) { 16 | try { 17 | return JSON.parse(config) || defaultConfig 18 | // eslint-disable-next-line no-empty 19 | } catch {} 20 | } 21 | return defaultConfig 22 | } 23 | 24 | const stateAtom = atom(getInitialState()) 25 | 26 | const localStorageStateAtom = < 27 | TKey extends keyof GlobalState, 28 | >(key: TKey) => 29 | atom( 30 | (get) => get(stateAtom)[key], 31 | (get, set, update: (previousValue: GlobalState[TKey]) => GlobalState[TKey]) => { 32 | const oldValue = get(stateAtom) 33 | const newValue = { ...oldValue, [key]: update(oldValue[key]) } 34 | 35 | set(stateAtom, newValue) 36 | localStorage.setItem('trpc-playground-config', JSON.stringify(newValue)) 37 | }, 38 | ) 39 | 40 | export const headersAtom = localStorageStateAtom('headers') 41 | export const tabsAtom = localStorageStateAtom('tabs') 42 | export const currentTabIdAtom = localStorageStateAtom('currentTabId') 43 | 44 | export const previousTabIdAtom = atom('0') 45 | 46 | export const updateCurrentTabIdAtom = atom( 47 | (get) => get(currentTabIdAtom), 48 | (get, set, update: string) => { 49 | set(previousTabIdAtom, get(currentTabIdAtom)) 50 | set(currentTabIdAtom, () => update) 51 | }, 52 | ) 53 | 54 | export const currentTabAtom = atom((get) => { 55 | return get(tabsAtom).find((tab) => tab.id === get(currentTabIdAtom))! 56 | }) 57 | 58 | export const previousTabAtom = atom((get) => { 59 | return get(tabsAtom).find((tab) => tab.id === get(previousTabIdAtom)) 60 | }) 61 | 62 | export const currentTabIndexAtom = atom((get) => { 63 | return get(tabsAtom).findIndex((tab) => tab.id === get(currentTabIdAtom)) 64 | }) 65 | 66 | export const queryBuilderOpened = atom(false) 67 | export const editorAtom = atom(null) 68 | -------------------------------------------------------------------------------- /packages/html/scripts/build.ts: -------------------------------------------------------------------------------- 1 | import { transform, TransformOptions } from 'esbuild' 2 | import { parse } from 'node-html-parser' 3 | import fs from 'node:fs/promises' 4 | import path from 'node:path' 5 | import { fileURLToPath } from 'node:url' 6 | 7 | const __dirname = path.dirname(fileURLToPath(import.meta.url)) 8 | 9 | const distPath = path.join(__dirname, '..', 'dist') 10 | const indexHtmlPath = path.join(distPath, 'index.html') 11 | 12 | const templatePath = path.join(__dirname, 'template.ts') 13 | 14 | const indexHtml = await fs.readFile(indexHtmlPath, 'utf8') 15 | const parsedHtml = parse(indexHtml) 16 | 17 | const scripts = parsedHtml.querySelectorAll('script') 18 | const stringScripts = scripts.map((script) => { 19 | script.remove() 20 | const src = script.getAttribute('src') 21 | if (src) { 22 | // remove leading slash 23 | script.setAttribute('src', `\${buildCdnUrl('@trpc-playground/html', 'dist/${src.slice(1)}')}`) 24 | } 25 | 26 | return script.toString() 27 | }) 28 | 29 | const links = parsedHtml.querySelectorAll('link') 30 | const stringLinks = links.map((link) => { 31 | link.remove() 32 | const href = link.getAttribute('href') 33 | if (href) { 34 | link.setAttribute('href', `\${buildCdnUrl('@trpc-playground/html', 'dist/${href.slice(1)}')}`) 35 | } 36 | 37 | return link.toString() 38 | }) 39 | 40 | const template = await fs.readFile(templatePath, 'utf8') 41 | 42 | const cdnHtmlDeclaration = template 43 | .replace('{scripts}', stringScripts.join('\n')) 44 | .replace('{links}', stringLinks.join('\n')) 45 | 46 | const transformCdnHtmlTs = async (options: TransformOptions) => { 47 | const { code } = await transform(cdnHtmlDeclaration, { 48 | target: 'node12', 49 | loader: 'ts', 50 | ...options, 51 | }) 52 | return code 53 | } 54 | 55 | const esmJsHtmlPath = path.join(distPath, 'index.js') 56 | const cjsJsHtmlPath = path.join(distPath, 'index.cjs') 57 | 58 | await Promise.all([ 59 | (async () => { 60 | const code = await transformCdnHtmlTs({ 61 | format: 'esm', 62 | }) 63 | await fs.writeFile(esmJsHtmlPath, code) 64 | })(), 65 | (async () => { 66 | const code = await transformCdnHtmlTs({ 67 | format: 'cjs', 68 | }) 69 | await fs.writeFile(cjsJsHtmlPath, code) 70 | })(), 71 | ]) 72 | -------------------------------------------------------------------------------- /packages/components/src/components/provider.tsx: -------------------------------------------------------------------------------- 1 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query' 2 | import { DeepRequiredClientConfig, ResolvedRouterSchema } from '@trpc-playground/types' 3 | import { createTRPCReact, httpBatchLink } from '@trpc/react-query' 4 | import { AnyRouter } from '@trpc/server' 5 | import { atom, Provider as JotaiProvider } from 'jotai' 6 | import { ComponentChildren } from 'preact' 7 | import { useCallback, useMemo } from 'preact/hooks' 8 | import superjson from 'superjson' 9 | import { TrpcClient } from '../types' 10 | import { getInitialState } from './tab/store' 11 | import { createInitialValues } from './utils' 12 | 13 | // need to pass in AnyRouter to satisfy rollup-plugin-dts 14 | export const trpc = createTRPCReact({}) 15 | 16 | type PlaygroundProviderProps = { 17 | config: DeepRequiredClientConfig 18 | children: ComponentChildren 19 | } 20 | 21 | export const trpcClientAtom = atom(null!) 22 | export const configAtom = atom(null!) 23 | export const typesAtom = atom(null) 24 | 25 | export const PlaygroundProvider = ({ config, children }: PlaygroundProviderProps) => { 26 | const queryClient = useMemo(() => new QueryClient(), []) 27 | 28 | // Merge headers in config and global headers from localstorage 29 | const getHeaders = useCallback( 30 | () => ({ ...config.request.globalHeaders, ...getInitialState().headers }), 31 | [config.request.globalHeaders], 32 | ) 33 | 34 | const transformer = config.request.superjson ? superjson : undefined 35 | const trpcClient = useMemo(() => 36 | trpc.createClient({ 37 | links: [httpBatchLink({ url: config.trpcApiEndpoint, headers: getHeaders })], 38 | transformer, 39 | }), []) 40 | 41 | const { get, set } = createInitialValues() 42 | set(trpcClientAtom, trpcClient) 43 | set(configAtom, config) 44 | set(typesAtom, null) 45 | 46 | return ( 47 | 48 | 51 | 52 | 53 | {children} 54 | 55 | 56 | 57 | 58 | ) 59 | } 60 | -------------------------------------------------------------------------------- /packages/trpc-playground/src/zod-resolve-types.ts: -------------------------------------------------------------------------------- 1 | import { ResolvedRouterSchema } from '@trpc-playground/types' 2 | import { AnyProcedure, AnyRouter } from '@trpc/server' 3 | import lodash from 'lodash' 4 | import { AnyZodObject, z, ZodAny } from 'zod' 5 | import { getProcedureSchemas } from './get-default-input' 6 | 7 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 8 | export type Procedures = Record 9 | 10 | export type ProcedureTypes = Record<'queries' | 'mutations', Record> 11 | 12 | const buildTrpcTsType = (router: AnyRouter, procedureTypes: ProcedureTypes) => { 13 | const procedures = router._def.procedures as Procedures 14 | const procedureObject = {} as Record 15 | 16 | Object.entries(procedures) 17 | .filter(([, { _def }]) => _def.query || _def.mutation) 18 | .forEach(([name, procedure]) => { 19 | let procedureTypeDef = '' 20 | 21 | const inputType = procedureTypes.mutations[name] || procedureTypes.queries[name] || '' 22 | if (procedure._def?.query) procedureTypeDef += `query: (${inputType}) => void,` 23 | else if (procedure._def?.mutation) procedureTypeDef += `mutate: (${inputType}) => void,` 24 | 25 | lodash.set(procedureObject, name, `{${procedureTypeDef}}`) 26 | }) 27 | 28 | const buildNestedTrpcObject = (obj: Record): string => { 29 | return Object.entries(obj).map(([name, value]) => { 30 | if (typeof value === 'string') return `'${name}': ${value}` 31 | return `'${name}': {${buildNestedTrpcObject(value)}}` 32 | }).join(',') 33 | } 34 | 35 | return `type Trpc = {${buildNestedTrpcObject(procedureObject)}}\ndeclare var trpc: Trpc;` 36 | } 37 | 38 | export const zodResolveTypes = async (router: AnyRouter): Promise => { 39 | const { schemas, types } = getProcedureSchemas(router._def.procedures) 40 | 41 | return { 42 | tsTypes: buildTrpcTsType(router, types), 43 | ...schemas, 44 | } 45 | } 46 | 47 | export const getInputFromInputParsers = (inputs: ZodAny[]) => { 48 | if (inputs.length === 0) return null 49 | if (inputs.length === 1) return inputs[0] 50 | 51 | const mergedObj = inputs.reduce((mergedObj, inputParser) => { 52 | return mergedObj.merge(inputParser as unknown as AnyZodObject) 53 | }, z.object({})) 54 | 55 | return mergedObj 56 | } 57 | -------------------------------------------------------------------------------- /packages/components/src/editor/transform-and-run-queries.ts: -------------------------------------------------------------------------------- 1 | import { maskedEval } from '@trpc-playground/query-extension' 2 | import { printObject, transformTs } from '@trpc-playground/utils' 3 | import { TrpcClient } from '../types' 4 | import { createTRPCProxy } from './createTRPCProxy' 5 | 6 | type TransformAndRunQueryArgs = { 7 | code: string 8 | trpcClient: TrpcClient 9 | } 10 | 11 | export const transformAndRunQueries = async ({ code, trpcClient }: TransformAndRunQueryArgs) => { 12 | let transformedCode: string 13 | try { 14 | transformedCode = transformTs(code) 15 | } catch (e) { 16 | return printObject(e) 17 | } 18 | 19 | return serialEval({ code: transformedCode, trpcClient }) 20 | } 21 | 22 | type EvalArgs = { 23 | code: string 24 | trpcClient: TrpcClient 25 | } 26 | 27 | export const serialEval = async ({ code, trpcClient }: EvalArgs) => { 28 | // if the code exited because a failed query 29 | let didQueryFail = false 30 | 31 | const queryResponses: unknown[] = [] 32 | 33 | try { 34 | // transform imports because export {} does not make sense in eval function 35 | 36 | await maskedEval(code, { 37 | trpc: createTRPCProxy( 38 | { 39 | async query(path, args) { 40 | try { 41 | const response = await trpcClient.query(path, args) 42 | queryResponses.push(response) 43 | return response 44 | } catch (e) { 45 | // add error response before throwing 46 | queryResponses.push(e) 47 | didQueryFail = true 48 | throw e 49 | } 50 | }, 51 | async mutate(path, args) { 52 | try { 53 | const response = await trpcClient.mutation(path, args) 54 | queryResponses.push(response) 55 | return response 56 | } catch (e) { 57 | queryResponses.push(e) 58 | didQueryFail = true 59 | throw e 60 | } 61 | }, 62 | }, 63 | ), 64 | }) 65 | } catch (e) { 66 | // if the query failed, the response object is already set 67 | if (!didQueryFail) return printObject(e) 68 | } 69 | 70 | return joinQueryResponses(queryResponses) 71 | } 72 | 73 | const joinQueryResponses = (queryResponses: unknown[]) => 74 | `${queryResponses.map((response) => printObject(response)).join(',\n\n')}` 75 | -------------------------------------------------------------------------------- /packages/html/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @trpc-playground/html 2 | 3 | ## 1.0.4 4 | 5 | ### Patch Changes 6 | 7 | - [`1d7112c`](https://github.com/sachinraja/trpc-playground/commit/1d7112cc2987d0daccb611becd3661a9fad8123c) Thanks [@sachinraja](https://github.com/sachinraja)! - fix typescript autocompletion 8 | 9 | ## 1.0.1 10 | 11 | ### Patch Changes 12 | 13 | - [`012cb1e`](https://github.com/sachinraja/trpc-playground/commit/012cb1e3888e69ddc76abf55492b2d3441989fa8) Thanks [@sachinraja](https://github.com/sachinraja)! - fix esm exports and print full object depth 14 | 15 | ## 1.0.0 16 | 17 | ### Major Changes 18 | 19 | - [`d226d7b`](https://github.com/sachinraja/trpc-playground/commit/d226d7b5829b79cce8bee3f70d6635f2f76fd796) Thanks [@sachinraja](https://github.com/sachinraja)! - update deps, fix bugs 20 | 21 | ## 1.0.0-next.0 22 | 23 | ### Major Changes 24 | 25 | - [`413fca6`](https://github.com/sachinraja/trpc-playground/commit/413fca6c4c4cb50b690d3fb76ea1b43a713275ef) Thanks [@sachinraja](https://github.com/sachinraja)! - support tRPC v10 26 | 27 | ## 0.3.3 28 | 29 | ### Patch Changes 30 | 31 | - [`dd904cf`](https://github.com/sachinraja/trpc-playground/commit/dd904cfe853a61e4aeb68a31250b101598794dea) Thanks [@sachinraja](https://github.com/sachinraja)! - sort operations in query builder 32 | 33 | ## 0.3.2 34 | 35 | ### Patch Changes 36 | 37 | - [`3eb67eb`](https://github.com/sachinraja/trpc-playground/commit/3eb67eb100e96d3f804ac34976f26888df923a37) Thanks [@sachinraja](https://github.com/sachinraja)! - fix tab state 38 | 39 | ## 0.3.1 40 | 41 | ### Patch Changes 42 | 43 | - [`e7620f3`](https://github.com/sachinraja/trpc-playground/commit/e7620f3238dd1ceea4264bc227a5a4217b42ea89) Thanks [@sachinraja](https://github.com/sachinraja)! - fix docs 44 | 45 | ## 0.3.0 46 | 47 | ### Minor Changes 48 | 49 | - [`5553920`](https://github.com/sachinraja/trpc-playground/commit/5553920db2bd15da8249d19826a9b7a1ecf1791f) Thanks [@sachinraja](https://github.com/sachinraja)! - update html 50 | 51 | ## 0.1.5 52 | 53 | ### Patch Changes 54 | 55 | - [`1b2f93e`](https://github.com/sachinraja/trpc-playground/commit/1b2f93e780c3bddbf17d09c2a8f14e74e85b3fcb) Thanks [@sachinraja](https://github.com/sachinraja)! - add superjson transformer option 56 | 57 | ## 0.1.4 58 | 59 | ### Patch Changes 60 | 61 | - 2b46455: fix doc resetting after tab name is changed 62 | 63 | ## 0.1.1 64 | 65 | ### Patch Changes 66 | 67 | - 4629f11: fix asset url 68 | 69 | ## 0.1.0 70 | 71 | ### Minor Changes 72 | 73 | - aa288c7: first release 74 | -------------------------------------------------------------------------------- /packages/components/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@trpc-playground/components", 3 | "type": "module", 4 | "version": "1.0.2", 5 | "publishConfig": { 6 | "access": "public" 7 | }, 8 | "description": "core components for trpc-playground", 9 | "repository": { 10 | "directory": "packages/components", 11 | "type": "git", 12 | "url": "https://github.com/sachinraja/trpc-playground" 13 | }, 14 | "homepage": "https://github.com/sachinraja/trpc-playground#readme", 15 | "bugs": { 16 | "url": "https://github.com/sachinraja/trpc-playground/issues" 17 | }, 18 | "author": "Sachin Raja ", 19 | "license": "MIT", 20 | "keywords": [ 21 | "trpc", 22 | "playground" 23 | ], 24 | "main": "dist/index.cjs", 25 | "module": "dist/index.js", 26 | "types": "dist/index.d.ts", 27 | "exports": { 28 | "./package.json": "./package.json", 29 | ".": "./dist/index.js" 30 | }, 31 | "files": [ 32 | "dist" 33 | ], 34 | "scripts": { 35 | "build": "tsup", 36 | "clean": "rimraf dist", 37 | "prepublishOnly": "pnpm build", 38 | "type-check": "tsc" 39 | }, 40 | "dependencies": { 41 | "@codemirror/autocomplete": "^6.4.0", 42 | "@codemirror/commands": "^6.1.2", 43 | "@codemirror/lang-javascript": "^6.1.2", 44 | "@codemirror/lang-json": "^6.0.1", 45 | "@codemirror/language": "^6.3.2", 46 | "@codemirror/lint": "^6.1.0", 47 | "@codemirror/search": "^6.2.3", 48 | "@codemirror/state": "^6.1.4", 49 | "@codemirror/theme-one-dark": "^6.1.0", 50 | "@codemirror/view": "^6.7.1", 51 | "@dnd-kit/core": "^6.0.6", 52 | "@dnd-kit/sortable": "^7.0.1", 53 | "@dnd-kit/utilities": "^3.2.1", 54 | "@heroicons/react": "^2.0.13", 55 | "@tanstack/react-query": "^4.20.4", 56 | "@trpc-playground/query-extension": "workspace:1.0.1", 57 | "@trpc-playground/typescript-extension": "workspace:1.0.0", 58 | "@trpc-playground/utils": "workspace:1.0.1", 59 | "@trpc/client": "10.5.0", 60 | "@trpc/react-query": "10.5.0", 61 | "clsx": "^1.2.1", 62 | "jotai": "^1.11.2", 63 | "object-inspect": "^1.12.2", 64 | "re-resizable": "^6.9.9", 65 | "react-input-autosize": "^3.0.0", 66 | "rodemirror": "^2.0.0", 67 | "superjson": "^1.12.0" 68 | }, 69 | "peerDependencies": { 70 | "preact": "^10" 71 | }, 72 | "devDependencies": { 73 | "@trpc-playground/types": "workspace:1.0.0", 74 | "@trpc/server": "10.5.0", 75 | "@types/object-inspect": "1.8.1", 76 | "@types/react": "17.0.44", 77 | "@types/react-input-autosize": "2.2.1" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /packages/html/scripts/template.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://github.com/graphql/graphql-playground/blob/main/packages/graphql-playground-html/src/render-playground-page.ts 3 | */ 4 | // some variables used after injection 5 | /* eslint-disable @typescript-eslint/no-unused-vars */ 6 | import { ClientConfig, DeepRequiredClientConfig } from '@trpc-playground/types' 7 | import { filterXSS } from 'xss' 8 | import { RenderPlaygroundPageOptions } from '..' 9 | 10 | const CONFIG_ID = 'playground-config' 11 | 12 | const filter = (val: string) => 13 | filterXSS(val, { 14 | // @ts-expect-error this is valid 15 | whiteList: [], 16 | stripIgnoreTag: true, 17 | stripIgnoreTagBody: ['script'], 18 | }) 19 | 20 | const renderConfig = (config: ClientConfig) => { 21 | return filterXSS(`${JSON.stringify(config)}`, { 22 | whiteList: { div: ['id'] }, 23 | }) 24 | } 25 | 26 | const renderPlaygroundPage = ({ version, cdnUrl, clientConfig }: RenderPlaygroundPageOptions) => { 27 | // only send necessary config to client 28 | // this must be updated with the latest properties whenever ClientConfig is updated 29 | const resolvedConfig: DeepRequiredClientConfig = { 30 | trpcApiEndpoint: clientConfig.trpcApiEndpoint, 31 | playgroundEndpoint: clientConfig.playgroundEndpoint, 32 | polling: clientConfig.polling, 33 | request: clientConfig.request, 34 | } 35 | 36 | const buildCdnUrl = (packageName: string, suffix: string) => 37 | filter(`${cdnUrl}/${packageName}${version ? `@${version}` : ''}/${suffix}` || '') 38 | 39 | return (` 40 | 41 | 42 | 43 | 44 | tRPC Playground 45 | {scripts} 46 | {links} 47 | 48 | 49 | 54 | ${renderConfig(resolvedConfig)} 55 | 56 | 57 | 75 | 76 | `) 77 | } 78 | 79 | export { renderPlaygroundPage } 80 | -------------------------------------------------------------------------------- /packages/trpc-playground/src/handler.ts: -------------------------------------------------------------------------------- 1 | import { renderPlaygroundPage } from '@trpc-playground/html' 2 | import { PlaygroundRequestOperation, TrpcPlaygroundConfig } from '@trpc-playground/types' 3 | import { defineHandler, RawRequest } from 'uttp' 4 | import { resolveConfig } from './config' 5 | 6 | export type HTTPBody = { 7 | operation: PlaygroundRequestOperation 8 | } 9 | 10 | export const handler = defineHandler(async (helpers, config: TrpcPlaygroundConfig) => { 11 | const resolvedConfig = resolveConfig(config) 12 | 13 | let htmlPlaygroundPage: string | undefined = undefined 14 | 15 | if (resolvedConfig.server?.serveHtml) { 16 | htmlPlaygroundPage = renderPlaygroundPage({ 17 | ...resolvedConfig.renderOptions, 18 | clientConfig: resolvedConfig, 19 | }) 20 | } 21 | 22 | const types = await resolvedConfig.resolveTypes(config.router) 23 | const stringifiedTypes = JSON.stringify(types) 24 | 25 | return { 26 | async handleRequest(request) { 27 | if (request.method === 'HEAD') { 28 | // can be used for lambda warmup 29 | return { 30 | status: 204, 31 | body: undefined, 32 | } 33 | } 34 | 35 | if (request.method === 'GET' && resolvedConfig.server.serveHtml) { 36 | return { 37 | status: 200, 38 | headers: { 39 | 'Content-Type': 'text/html', 40 | }, 41 | body: htmlPlaygroundPage, 42 | } 43 | } 44 | 45 | if (request.method === 'POST') { 46 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 47 | let body: string | Record | undefined 48 | 49 | // hack for Next.js 50 | const rawBody = (request.rawRequest as RawRequest & { body: unknown }).body 51 | if (typeof rawBody === 'object' && rawBody !== null && 'operation' in rawBody) { 52 | body = rawBody 53 | } else { 54 | try { 55 | body = await helpers.parseBodyAsString(request.rawRequest) 56 | } catch { 57 | return { status: 413, body: undefined } 58 | } 59 | } 60 | 61 | const bodyObject: HTTPBody = typeof body === 'string' ? JSON.parse(body) : body 62 | 63 | if (bodyObject.operation === 'getRouterSchema') { 64 | return { 65 | headers: { 66 | 'Content-Type': 'application/json', 67 | }, 68 | status: 200, 69 | body: stringifiedTypes, 70 | } 71 | } 72 | 73 | // not a valid operation, 400 Bad Request 74 | return { status: 400, body: undefined } 75 | } 76 | 77 | return { status: 400, body: undefined } 78 | }, 79 | adapterOptions: { maxBodySize: resolvedConfig.server.maxBodySize }, 80 | } 81 | }) 82 | -------------------------------------------------------------------------------- /packages/query-extension/src/find-queries.ts: -------------------------------------------------------------------------------- 1 | import { syntaxTree } from '@codemirror/language' 2 | import { EditorState } from '@codemirror/state' 3 | import { RangeSet, RangeSetBuilder, RangeValue } from '@codemirror/state' 4 | import { transformTs } from '@trpc-playground/utils' 5 | import { maskedEval } from './masked-eval' 6 | 7 | export type Query = { 8 | operation: string 9 | args: unknown[] 10 | } 11 | 12 | export class QueryRangeValue extends RangeValue { 13 | public query: Query 14 | public rawArgs?: string[] 15 | 16 | constructor({ operation, args }: Omit & { args?: string[] }) { 17 | super() 18 | 19 | this.query = { 20 | operation, 21 | args: [], 22 | } 23 | this.rawArgs = args 24 | } 25 | 26 | async init() { 27 | if (this.rawArgs) { 28 | this.query.args = await Promise.all(this.rawArgs.map((arg) => { 29 | const argsWithReturn = `return ${arg}` 30 | const transformedCode = transformTs(argsWithReturn) 31 | return maskedEval(transformedCode, {}) 32 | })) 33 | } 34 | } 35 | } 36 | 37 | const queryFunctions = ['query', 'mutate'] 38 | export const findQueries = (state: EditorState): RangeSet => { 39 | const syntax = syntaxTree(state) 40 | const queries = new RangeSetBuilder() 41 | 42 | syntax.iterate({ 43 | enter(node) { 44 | if (node.name !== 'CallExpression') return 45 | 46 | const callExpression = node.node 47 | 48 | // check if the call expression is a query function 49 | // first child should be name of function 50 | const identifier = callExpression.firstChild 51 | 52 | if (identifier?.name !== 'MemberExpression') return 53 | // slice the doc to get the name of the function 54 | const identifierName = state.sliceDoc(identifier.from, identifier.to) 55 | 56 | // if the function is not a query function, return 57 | const queryFunction = identifierName.split('.').pop() 58 | if (!queryFunction || !(queryFunctions.includes(queryFunction))) return 59 | 60 | // get the arguments of the function 61 | const argList = callExpression.lastChild 62 | if (argList?.name !== 'ArgList') return 63 | 64 | // get the arguments 65 | let args: string[] = [] 66 | 67 | let arg = argList.firstChild 68 | while (arg) { 69 | // Skip over unnecessary tokens 70 | if (arg.type.name !== ',') { 71 | args.push(state.sliceDoc(arg.from, arg.to)) 72 | } 73 | 74 | arg = arg.nextSibling 75 | } 76 | 77 | // Ignore away the parenthesis (first and last child of `argsExpression`) 78 | args = args.slice(1, -1) 79 | 80 | const endpointPath = `"${identifierName.split('.').slice(1, -1).join('.')}"` 81 | queries.add( 82 | callExpression.from, 83 | callExpression.to, 84 | new QueryRangeValue({ 85 | operation: queryFunction, 86 | args: [endpointPath, ...args], 87 | }), 88 | ) 89 | }, 90 | }) 91 | 92 | return queries.finish() 93 | } 94 | -------------------------------------------------------------------------------- /packages/types/src/index.d.ts: -------------------------------------------------------------------------------- 1 | import { AnyRouter, Procedure } from '@trpc/server' 2 | import { DeepRequired } from 'ts-essentials' 3 | 4 | export type ClientConfig = { 5 | /** 6 | * The endpoint url that the trpc client makes requests to. 7 | */ 8 | trpcApiEndpoint: string 9 | /** 10 | * The endpoint url for the playground, provides data such as the router types. 11 | */ 12 | playgroundEndpoint: string 13 | polling?: { 14 | /** 15 | * Whether to poll for new types. 16 | * @default true 17 | */ 18 | enable?: boolean 19 | /** 20 | * How often the playground client polls the server for new types in milliseconds. 21 | * If this is `null`, the client will not poll for new types, which is useful in production 22 | * when types will not change. 23 | * @default 10000 24 | */ 25 | interval?: number 26 | } 27 | request?: { 28 | /** 29 | * Headers sent on every tRPC Playground request. 30 | */ 31 | globalHeaders?: Record 32 | /** 33 | * Enable the superjson transformer. 34 | * @default false 35 | */ 36 | superjson?: boolean 37 | } 38 | } 39 | 40 | export type RenderOptions = { 41 | /** 42 | * The version of @trpc-playground/html to use. Specify as `null` to omit `version` from `cdnUrl`. 43 | * @default latest 44 | */ 45 | version?: string | null 46 | /** 47 | * The cdn to import the @trpc-playground/html scripts from. 48 | * @default //cdn.jsdelivr.net/npm 49 | */ 50 | cdnUrl?: string 51 | } 52 | 53 | export type ServerConfig = { 54 | /** 55 | * The trpc router that the playground server handler uses to generate types. 56 | */ 57 | router: AnyRouter 58 | /** 59 | * Resolves the typescript types for the router and returns an array of types to inject. Uses [`zod-to-ts`](https://github.com/sachinraja/zod-to-ts) by default. 60 | */ 61 | resolveTypes?: (router: AnyRouter) => ResolvedRouterSchema | Promise 62 | /** 63 | * Options for rendering the HTML playground page. 64 | */ 65 | renderOptions?: RenderOptions 66 | server?: { 67 | /** 68 | * The maximum body length to accept in playground requests. 69 | */ 70 | maxBodySize?: number 71 | /** 72 | * Whether to serve the HTML playground page. 73 | * @default true 74 | */ 75 | serveHtml?: boolean 76 | } 77 | } 78 | 79 | export type TrpcPlaygroundConfig = ClientConfig & ServerConfig 80 | 81 | export type PlaygroundRequestOperation = 'getRouterSchema' 82 | 83 | export type Awaited = T extends PromiseLike ? U : T 84 | 85 | export type DeepRequiredClientConfig = DeepRequired 86 | 87 | export type DefaultOperationType = { value: string; inputLength: number } 88 | 89 | export type QueryDefaultAndType = Record 90 | 91 | export type ProcedureSchemas = { 92 | queries: QueryDefaultAndType 93 | mutations: QueryDefaultAndType 94 | } 95 | 96 | export type ResolvedRouterSchema = { 97 | tsTypes: string 98 | } & Procedure 99 | -------------------------------------------------------------------------------- /packages/query-extension/src/gutter.ts: -------------------------------------------------------------------------------- 1 | import { Extension } from '@codemirror/state' 2 | import { EditorView, gutter as cmGutter, GutterMarker } from '@codemirror/view' 3 | import { isCursorInRange } from './find-cursor' 4 | import { queryStateField } from './state' 5 | 6 | /** 7 | * invisible = there's no query on this line 8 | * inactive = there's a query on this line, but your cursor is not on it 9 | * active = there's a query on this line, and your cursor is on it 10 | */ 11 | type QueryGutterType = 'invisible' | 'inactive' | 'active' 12 | 13 | /** 14 | * A GutterMarker that marks lines that have valid queries 15 | */ 16 | class QueryGutterMarker extends GutterMarker { 17 | type: QueryGutterType 18 | 19 | constructor(type: QueryGutterType) { 20 | super() 21 | this.type = type 22 | } 23 | 24 | toDOM() { 25 | const div = document.createElement('div') 26 | div.classList.add('cm-query') 27 | div.classList.add(this.type) 28 | 29 | return div 30 | } 31 | } 32 | 33 | export const gutter = (): Extension => { 34 | return [ 35 | cmGutter({ 36 | /** 37 | * Add a line marker that adds a green, grey or transparent line to the gutter: 38 | * 39 | * 1. An invisible line if this line is not part of a query 40 | * 2. A grey line if this line is part of a query, but your cursor is not on it 41 | * 3. A green line if this line is part of a query, and your cursor is on it 42 | */ 43 | lineMarker: (view, line) => { 44 | // If (beginning of) selection range (aka the cursor) is inside the query, add (visible) markers for all lines in query (and invisible ones for others) 45 | // Toggling between visible/invisible instead of adding/removing markers makes it so the editor does not jump when a marker is shown as your cursor moves around 46 | let marker: QueryGutterMarker = new QueryGutterMarker('invisible') 47 | 48 | view.state 49 | .field(queryStateField) 50 | .between(line.from, line.to, (from, to) => { 51 | const queryLineStart = view.state.doc.lineAt(from) // Get line where this range starts 52 | const queryLineEnd = view.state.doc.lineAt(to) // Get line where this range ends 53 | 54 | // If the cursor is anywhere between the lines that the query starts and ends at, then the green bar should be "active" 55 | marker = new QueryGutterMarker( 56 | isCursorInRange(view.state, queryLineStart.from, queryLineEnd.to) 57 | ? 'active' 58 | : 'inactive', 59 | ) 60 | }) 61 | 62 | return marker 63 | }, 64 | }), 65 | // Gutter line marker styles 66 | EditorView.baseTheme({ 67 | '.cm-gutterElement .cm-query': { 68 | height: '100%', 69 | 70 | '&.invisible': { 71 | borderLeft: '3px solid transparent', 72 | }, 73 | '&.inactive': { 74 | borderLeft: '3px solid #E2E8F0', /* blueGray-200 */ 75 | }, 76 | '&.active': { 77 | borderLeft: '3px solid #22C55E', /* green-500 */ 78 | }, 79 | }, 80 | }), 81 | ] 82 | } 83 | -------------------------------------------------------------------------------- /packages/components/src/components/tab/group.tsx: -------------------------------------------------------------------------------- 1 | import { closestCenter, DndContext, DndContextProps, DragOverlay, useSensor, useSensors } from '@dnd-kit/core' 2 | import { arrayMove, horizontalListSortingStrategy, SortableContext } from '@dnd-kit/sortable' 3 | import { PlusIcon } from '@heroicons/react/24/solid' 4 | import { useAtom } from 'jotai' 5 | import { useCallback } from 'preact/hooks' 6 | import { BaseTab } from './base' 7 | import { MouseSensor } from './dnd-sensors' 8 | import { Draggable } from './draggable' 9 | import { PlaygroundTab } from './playground' 10 | import { currentTabAtom, currentTabIndexAtom, tabsAtom, updateCurrentTabIdAtom } from './store' 11 | import { Tab } from './types' 12 | import { createNewDefaultTab } from './utils' 13 | 14 | export const TabGroup = () => { 15 | const [tabs, setTabs] = useAtom(tabsAtom) 16 | const [currentTab] = useAtom(currentTabAtom) 17 | const [currentTabIndex] = useAtom(currentTabIndexAtom) 18 | const [, updateCurrentTabId] = useAtom(updateCurrentTabIdAtom) 19 | 20 | const sensors = useSensors( 21 | useSensor(MouseSensor, { 22 | activationConstraint: { 23 | distance: 10, 24 | }, 25 | }), 26 | ) 27 | 28 | const handleDragEnd = useCallback>(({ active, over }) => { 29 | if (!over) return 30 | 31 | setTabs((tabs) => { 32 | const activeTabIndex = tabs.findIndex((tab) => tab.id === active.id) 33 | const newIndex = tabs.findIndex((tab) => tab.id === over.id) 34 | const newTabs = arrayMove(tabs, activeTabIndex, newIndex) 35 | 36 | updateCurrentTabId(active.id as string) 37 | return newTabs 38 | }) 39 | }, [tabs, currentTab]) 40 | 41 | return ( 42 | 43 | { 47 | updateCurrentTabId(active.id as string) 48 | }} 49 | onDragEnd={handleDragEnd} 50 | > 51 | 52 | {tabs.map((tab, index) => { 53 | return ( 54 | 55 | 59 | 60 | ) 61 | })} 62 | 63 | 64 | 65 | {currentTab && ( 66 | 70 | )} 71 | 72 | 73 | 74 | 75 | { 78 | const newTab = createNewDefaultTab() 79 | const newTabs: Tab[] = [...tabs, newTab] 80 | setTabs(() => newTabs) 81 | updateCurrentTabId(newTab.id) 82 | }} 83 | > 84 | 85 | 86 | 87 | 88 | ) 89 | } 90 | -------------------------------------------------------------------------------- /packages/typescript-extension/scripts/build-types.js: -------------------------------------------------------------------------------- 1 | import glob from 'fast-glob' 2 | import fs from 'node:fs' 3 | import path from 'node:path' 4 | import { fileURLToPath } from 'node:url' 5 | 6 | // The goal of this build step is to generate two artifacts (per dependency): 7 | // 1. Metadata: version + file list of dependency (meta.js) 8 | // 2. Data: A key-value store from file names to file content (data.js) 9 | // 10 | // Both of these will be dynamically required by the TS editor. 11 | 12 | // Dependencies that artifacts need to be generated for 13 | const dependencies = { 14 | // Core TS libs 15 | typescript: { 16 | version: '4.9.4', 17 | src: ['lib/*.d.ts'], 18 | }, 19 | // Node libs 20 | // '@types/node': { 21 | // version: '14', // Because this is the version of Node on Vercel 22 | // src: ['*.d.ts'], 23 | // }, 24 | } 25 | 26 | const __dirname = path.dirname(fileURLToPath(import.meta.url)) 27 | 28 | const DEST_ROOT = path.resolve(__dirname, '..', 'src', 'types') 29 | 30 | const DISCLAIMER = '// This file was generated, do not edit manually\n\n' 31 | 32 | // Clean out the destination 33 | fs.rmSync(DEST_ROOT, { recursive: true, force: true }) 34 | fs.mkdirSync(DEST_ROOT, { recursive: true }) 35 | 36 | console.log('Prebuilding types') 37 | 38 | for (const [dep, { version, src }] of Object.entries(dependencies)) { 39 | console.log(`Using ${dep} version: ${version}`) 40 | 41 | // Prepare destination for this dependency 42 | fs.mkdirSync(path.join(DEST_ROOT, dep), { recursive: true }) 43 | 44 | // Get a list of files in this dependency 45 | const files = await glob( 46 | src, 47 | { absolute: true, cwd: path.join(__dirname, '..', 'node_modules', dep) }, 48 | ) 49 | 50 | const metaFile = path.join(DEST_ROOT, dep, 'meta.js') 51 | // Generate artifact 1: Metadata 52 | fs.writeFileSync( 53 | metaFile, 54 | `${DISCLAIMER}export const version = "${version}"`, 55 | ) 56 | const metaStream = fs.createWriteStream(metaFile) 57 | metaStream.write(DISCLAIMER) 58 | metaStream.write(`export const version = "${version}"\n\n`) 59 | metaStream.write('export const files = [') 60 | files.forEach(f => { 61 | const name = path.basename(f) 62 | metaStream.write(`\n "${name}",`) 63 | }) 64 | metaStream.write('\n]\n') 65 | metaStream.end() 66 | // Generate typedefs so Vite can import it with types 67 | fs.writeFileSync( 68 | path.join(DEST_ROOT, dep, 'meta.d.ts'), 69 | `${DISCLAIMER}export const version: string;\nexport const files: string[];`, 70 | ) 71 | 72 | // Generate artifact 2: A KV pair from file names to file content 73 | const dataStream = fs.createWriteStream(path.join(DEST_ROOT, dep, 'data.js')) 74 | dataStream.write(DISCLAIMER) 75 | dataStream.write(`export const version = "${version}"\n\n`) 76 | dataStream.write('export const files = {') 77 | files.forEach(f => { 78 | const name = path.basename(f) 79 | const content = fs.readFileSync(path.resolve(f), 'utf8') 80 | dataStream.write(`\n"${name}": `) 81 | dataStream.write(`${JSON.stringify(content)},`) 82 | }) 83 | dataStream.write('\n}\n') 84 | dataStream.end() 85 | // Generate typedefs so Vite can import it with types 86 | fs.writeFileSync( 87 | path.join(DEST_ROOT, dep, 'data.d.ts'), 88 | `${DISCLAIMER}export const version: string;\nexport const files: Record;`, 89 | ) 90 | } 91 | -------------------------------------------------------------------------------- /packages/components/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @trpc-playground/components 2 | 3 | ## 1.0.2 4 | 5 | ### Patch Changes 6 | 7 | - [`1d7112c`](https://github.com/sachinraja/trpc-playground/commit/1d7112cc2987d0daccb611becd3661a9fad8123c) Thanks [@sachinraja](https://github.com/sachinraja)! - fix typescript autocompletion 8 | 9 | ## 1.0.1 10 | 11 | ### Patch Changes 12 | 13 | - Updated dependencies [[`012cb1e`](https://github.com/sachinraja/trpc-playground/commit/012cb1e3888e69ddc76abf55492b2d3441989fa8)]: 14 | - @trpc-playground/utils@1.0.1 15 | - @trpc-playground/query-extension@1.0.1 16 | 17 | ## 1.0.0 18 | 19 | ### Major Changes 20 | 21 | - [`d226d7b`](https://github.com/sachinraja/trpc-playground/commit/d226d7b5829b79cce8bee3f70d6635f2f76fd796) Thanks [@sachinraja](https://github.com/sachinraja)! - update deps, fix bugs 22 | 23 | ### Patch Changes 24 | 25 | - Updated dependencies [[`d226d7b`](https://github.com/sachinraja/trpc-playground/commit/d226d7b5829b79cce8bee3f70d6635f2f76fd796)]: 26 | - @trpc-playground/query-extension@1.0.0 27 | - @trpc-playground/typescript-extension@1.0.0 28 | - @trpc-playground/utils@1.0.0 29 | 30 | ## 1.0.0-next.0 31 | 32 | ### Major Changes 33 | 34 | - [`413fca6`](https://github.com/sachinraja/trpc-playground/commit/413fca6c4c4cb50b690d3fb76ea1b43a713275ef) Thanks [@sachinraja](https://github.com/sachinraja)! - support tRPC v10 35 | 36 | ### Patch Changes 37 | 38 | - Updated dependencies [[`413fca6`](https://github.com/sachinraja/trpc-playground/commit/413fca6c4c4cb50b690d3fb76ea1b43a713275ef)]: 39 | - @trpc-playground/query-extension@1.0.0-next.0 40 | - @trpc-playground/typescript-extension@1.0.0-next.0 41 | - @trpc-playground/utils@1.0.0-next.0 42 | 43 | ## 0.2.3 44 | 45 | ### Patch Changes 46 | 47 | - [`dd904cf`](https://github.com/sachinraja/trpc-playground/commit/dd904cfe853a61e4aeb68a31250b101598794dea) Thanks [@sachinraja](https://github.com/sachinraja)! - sort operations in query builder 48 | 49 | ## 0.2.2 50 | 51 | ### Patch Changes 52 | 53 | - [`3eb67eb`](https://github.com/sachinraja/trpc-playground/commit/3eb67eb100e96d3f804ac34976f26888df923a37) Thanks [@sachinraja](https://github.com/sachinraja)! - fix tab state 54 | 55 | ## 0.2.1 56 | 57 | ### Patch Changes 58 | 59 | - [`e7620f3`](https://github.com/sachinraja/trpc-playground/commit/e7620f3238dd1ceea4264bc227a5a4217b42ea89) Thanks [@sachinraja](https://github.com/sachinraja)! - fix docs 60 | 61 | ## 0.2.0 62 | 63 | ### Minor Changes 64 | 65 | - [`c2f5e54`](https://github.com/sachinraja/trpc-playground/commit/c2f5e543056786b10ec1ebf59f32567a102de611) Thanks [@sachinraja](https://github.com/sachinraja)! - add interactive UI 66 | 67 | ### Patch Changes 68 | 69 | - Updated dependencies [[`c2f5e54`](https://github.com/sachinraja/trpc-playground/commit/c2f5e543056786b10ec1ebf59f32567a102de611)]: 70 | - @trpc-playground/query-extension@0.2.0 71 | - @trpc-playground/typescript-extension@0.2.0 72 | 73 | ## 0.1.2 74 | 75 | ### Patch Changes 76 | 77 | - [`1b2f93e`](https://github.com/sachinraja/trpc-playground/commit/1b2f93e780c3bddbf17d09c2a8f14e74e85b3fcb) Thanks [@sachinraja](https://github.com/sachinraja)! - add superjson transformer option 78 | 79 | ## 0.1.1 80 | 81 | ### Patch Changes 82 | 83 | - 2b46455: fix doc resetting after tab name is changed 84 | 85 | ## 0.1.0 86 | 87 | ### Minor Changes 88 | 89 | - aa288c7: first release 90 | 91 | ### Patch Changes 92 | 93 | - Updated dependencies [aa288c7] 94 | - @trpc-playground/query-extension@0.1.0 95 | - @trpc-playground/typescript-extension@0.1.0 96 | - @trpc-playground/utils@0.1.0 97 | -------------------------------------------------------------------------------- /packages/typescript-extension/src/tsfs.ts: -------------------------------------------------------------------------------- 1 | import localforage from 'localforage' 2 | 3 | type LibName = 'typescript' | '@types/node' 4 | type FileName = string 5 | type FileContent = string 6 | 7 | /** 8 | * A virtual file-system to manage files for the TS Language server 9 | */ 10 | export class TSFS { 11 | /** Internal map of file names to their content */ 12 | public fs: Map 13 | 14 | constructor() { 15 | this.fs = new Map() 16 | } 17 | 18 | /** 19 | * Given a lib name, runs a callback function for every file in that library. 20 | * It might fetch files in the library from a cache or from the network 21 | */ 22 | private forFileInLib = async ( 23 | libName: LibName, 24 | cb: (name: string, content: string) => void, 25 | ) => { 26 | // First, we fetch metadata about the library 27 | 28 | // Rollup needs us to use static strings for dynamic imports 29 | const meta = await import('./types/typescript/meta.js') 30 | 31 | // The metadata tells us the version of the library, and gives us a list of file names in the library. 32 | // If our cache already has this version of the library: 33 | // 1. We iterate over the file names in that library and fetch file contents from DB (compressed). 34 | // 2. We iterate over the file names in taht library and call the callback function for every (fileName, fileContent) pair 35 | // 36 | // If our cache does not have this version of the library: 37 | // 1. We fetch all files of this library via the network 38 | // 2. We remove any other versions of the library that were cached (to conserve space) 39 | // 3. We iterate over the files we just fetched and cache them (compressed) 40 | // 4. We iterate over the files we just fetched and call the callback for every (fileName, fileContent) pair 41 | 42 | const isCached = (await localforage.getItem(`ts-lib/${libName}/_version`)) 43 | === meta.version 44 | 45 | // TODO:: Integrity checks? 46 | if (isCached) { 47 | const fileNames = meta.files 48 | const fileContents = (await Promise.all( 49 | fileNames.map(f => localforage.getItem(`ts-lib/${libName}/${meta.version}/${f}`)), 50 | )) as string[] // type-cast is olay because we know this file should exist 51 | 52 | fileNames.forEach((name, i) => cb(name, fileContents[i])) 53 | } else { 54 | // Remove everything, we'll download types and cache them 55 | await localforage.clear() 56 | 57 | // Rollup needs us to use static strings for dynamic imports 58 | const data = await import('./types/typescript/data.js') 59 | 60 | // Add new things to DB 61 | const files = Object.entries(data.files) 62 | 63 | // First, call the callback for all these files to unblock the caller 64 | files.forEach(([name, content]) => cb(name, content)) 65 | 66 | // Then, persist these file contents in DB 67 | await Promise.all([ 68 | localforage.setItem(`ts-lib/${libName}/_version`, meta.version), 69 | ...files.map(([name, content]) => 70 | localforage.setItem( 71 | `ts-lib/${libName}/${data.version}/${name}`, 72 | content, 73 | ) 74 | ), 75 | ]) 76 | } 77 | } 78 | 79 | injectCoreLibs = async () => { 80 | await Promise.all([ 81 | this.forFileInLib('typescript', (name, content) => { 82 | // TS Core libs need to be available at the root path `/` (TSServer requires this) 83 | this.fs.set('/' + name, content) 84 | }), 85 | ]) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /packages/query-extension/src/state.ts: -------------------------------------------------------------------------------- 1 | import { Extension, Facet, RangeSet, StateField } from '@codemirror/state' 2 | import { EditorView } from '@codemirror/view' 3 | import noop from 'lodash/noop' 4 | import over from 'lodash/over' 5 | import { findFirstCursor } from './find-cursor' 6 | import { findQueries, Query, QueryRangeValue } from './find-queries' 7 | 8 | /** 9 | * Facet to allow configuring query execution callback 10 | */ 11 | export type OnExecute = (query: Query) => void 12 | export const OnExecuteFacet = Facet.define({ 13 | combine: input => { 14 | // If multiple `onExecute` callbacks are registered, chain them (call them one after another) 15 | return over(input) 16 | }, 17 | }) 18 | 19 | /** 20 | * Facet to handle errors 21 | */ 22 | export type OnError = (error: Error) => void 23 | export const OnErrorFacet = Facet.define({ 24 | combine: input => { 25 | // If multiple `onError` callbacks are registered, chain them (call them one after another) 26 | return over(input) 27 | }, 28 | }) 29 | 30 | /** 31 | * Facet to allow configuring query enter callback 32 | */ 33 | export type OnEnterQuery = (query: Query) => void 34 | export const OnEnterQueryFacet = Facet.define({ 35 | combine: input => { 36 | // If multiple `onEnterQuery` callbacks are registered, chain them (call them one after another) 37 | return over(input) 38 | }, 39 | }) 40 | 41 | /** 42 | * Facet to allow configuring query leave callback 43 | */ 44 | export type OnLeaveQuery = () => void 45 | export const OnLeaveQueryFacet = Facet.define({ 46 | combine: input => { 47 | // If multiple `onLeaveQuery` callbacks are registered, chain them (call them one after another) 48 | return over(input) 49 | }, 50 | }) 51 | 52 | /** 53 | * State field that tracks which ranges are queries. 54 | * We don't store a DecorationSet directly in the StateField because we need to be able to find the `text` of a query 55 | */ 56 | export const queryStateField = StateField.define< 57 | RangeSet 58 | >({ 59 | create(state) { 60 | return findQueries(state) 61 | }, 62 | 63 | update(value, transaction) { 64 | if (transaction.docChanged) { 65 | return findQueries(transaction.state) 66 | } 67 | 68 | return value 69 | }, 70 | }) 71 | 72 | /** 73 | * An extension that enables query tracking 74 | */ 75 | export function state(config: { 76 | onExecute?: OnExecute 77 | onError?: OnError 78 | onEnterQuery?: OnEnterQuery 79 | onLeaveQuery?: OnLeaveQuery 80 | }): Extension { 81 | return [ 82 | OnExecuteFacet.of(config.onExecute || noop), 83 | OnErrorFacet.of(config.onError || noop), 84 | OnEnterQueryFacet.of(config.onEnterQuery || noop), 85 | OnLeaveQueryFacet.of(config.onLeaveQuery || noop), 86 | queryStateField, 87 | EditorView.updateListener.of(({ view }) => { 88 | const onEnterQuery = view.state.facet(OnEnterQueryFacet) 89 | const onLeaveQuery = view.state.facet(OnLeaveQueryFacet) 90 | 91 | const cursor = findFirstCursor(view.state) 92 | const line = view.state.doc.lineAt(cursor.pos) 93 | 94 | let lineHasQuery = false 95 | view.state 96 | .field(queryStateField) 97 | .between(line.from, line.to, (from, to, value) => { 98 | lineHasQuery = true 99 | onEnterQuery(value.query) 100 | }) 101 | 102 | if (!lineHasQuery) { 103 | onLeaveQuery() 104 | } 105 | }), 106 | ] 107 | } 108 | -------------------------------------------------------------------------------- /packages/typescript-extension/src/project.ts: -------------------------------------------------------------------------------- 1 | import { createSystem, createVirtualTypeScriptEnvironment, VirtualTypeScriptEnvironment } from '@typescript/vfs' 2 | import typescript from 'typescript' 3 | import { TSFS } from './tsfs' 4 | 5 | const TS_PROJECT_ENTRYPOINT = 'index.ts' 6 | export type FileMap = Record 7 | 8 | /** 9 | * A representation of a Typescript project. Only supports single-file projects currently. 10 | */ 11 | export class TypescriptProject { 12 | private fs: TSFS 13 | 14 | /** 15 | * Since this module is lazily initialized, this serves as a way to throttle multiple consecutive `init` requests. 16 | * We want to avoid initializing `tsserver` multiple times. 17 | * 18 | * After construction, it stays in the `procrastinating` state until someone requests something of it. 19 | * Once that happens, it goes through the `initializing` & `ready` states. 20 | */ 21 | private state: 'procrastinating' | 'initializing' | 'ready' 22 | /** When initialization starts, the promise it returns is stored here so that future `init` requests can be throttled */ 23 | private initPromise?: Promise 24 | private tsserver?: VirtualTypeScriptEnvironment 25 | 26 | constructor(entrypointFileContent: string) { 27 | this.fs = new TSFS() 28 | this.fs.fs.set(TS_PROJECT_ENTRYPOINT, entrypointFileContent || ' ') // tsserver ignores files with empty content, so give it something in case `entrypointFileContent` is empty 29 | this.state = 'procrastinating' 30 | this.initPromise = undefined 31 | } 32 | 33 | get entrypoint() { 34 | return TS_PROJECT_ENTRYPOINT 35 | } 36 | 37 | private async init(): Promise { 38 | this.state = 'initializing' 39 | await this.fs.injectCoreLibs() 40 | 41 | const system = createSystem(this.fs.fs) 42 | 43 | this.tsserver = createVirtualTypeScriptEnvironment( 44 | system, 45 | [TS_PROJECT_ENTRYPOINT], 46 | typescript, 47 | { 48 | target: typescript.ScriptTarget.ESNext, 49 | }, 50 | ) 51 | 52 | window.ts = this.tsserver 53 | this.state = 'ready' 54 | } 55 | 56 | public async injectTypes(types: FileMap) { 57 | const ts = await this.env() 58 | for (const [name, content] of Object.entries(types)) { 59 | if (!content.trim()) { 60 | continue 61 | } 62 | 63 | // if tsserver has initialized, we must add files to it, modifying the FS will do nothing 64 | ts.createFile(name, content) 65 | } 66 | } 67 | 68 | public async env(): Promise { 69 | // If this is the first time someone has requested something, start initialization 70 | if (this.state === 'procrastinating') { 71 | this.initPromise = this.init() 72 | await this.initPromise 73 | return this.tsserver! 74 | } 75 | 76 | // If this is already initializing, return the initPromise so avoid double initialization 77 | if (this.state === 'initializing') { 78 | await this.initPromise 79 | return this.tsserver! 80 | } 81 | 82 | // If this is ready, you're good to go 83 | return this.tsserver! 84 | } 85 | 86 | public async lang(): Promise< 87 | VirtualTypeScriptEnvironment['languageService'] 88 | > { 89 | const env = await this.env() 90 | return env.languageService 91 | } 92 | 93 | public destroy() { 94 | this.tsserver?.languageService.dispose() 95 | 96 | this.state = 'procrastinating' 97 | this.initPromise = undefined 98 | this.tsserver = undefined 99 | } 100 | } 101 | 102 | interface ExtendedWindow extends Window { 103 | ts?: VirtualTypeScriptEnvironment 104 | } 105 | declare const window: ExtendedWindow 106 | -------------------------------------------------------------------------------- /packages/components/src/components/tab/playground.tsx: -------------------------------------------------------------------------------- 1 | import { XMarkIcon } from '@heroicons/react/20/solid' 2 | import clsx from 'clsx' 3 | import { atom, useAtom } from 'jotai' 4 | import { useCallback, useMemo, useState } from 'preact/hooks' 5 | import AutosizeInput from 'react-input-autosize' 6 | import { BaseTab } from './base' 7 | import { tabsAtom, updateCurrentTabIdAtom } from './store' 8 | import { createNewDefaultTab } from './utils' 9 | 10 | type TabProps = { 11 | index: number 12 | } 13 | 14 | export const PlaygroundTab = ({ index }: TabProps) => { 15 | const [tabs, setTabs] = useAtom(tabsAtom) 16 | const [currentTabId, updateCurrentTabId] = useAtom(updateCurrentTabIdAtom) 17 | const [isEditingTabName, setIsEditingTabName] = useState(false) 18 | const tabRef = useMemo(() => atom(tabs[index]), [tabs]) 19 | const [tab] = useAtom(tabRef) 20 | 21 | const finishEditingTabName = useCallback(() => { 22 | const trimmedTabName = tab.name.trim() 23 | const tabName = trimmedTabName === '' ? 'New Tab' : trimmedTabName 24 | 25 | setTabs((tabs) => { 26 | const newTabs = [...tabs] 27 | newTabs[index] = { ...tabs[index], name: tabName } 28 | return newTabs 29 | }) 30 | 31 | setIsEditingTabName(false) 32 | }, [tab, setTabs, setIsEditingTabName]) 33 | 34 | const className = useMemo(() => 35 | clsx( 36 | tab.id === currentTabId ? undefined : 'opacity-70', 37 | 'select-none', 38 | ), [tab, currentTabId]) 39 | 40 | return ( 41 | { 44 | updateCurrentTabId(tab.id) 45 | }} 46 | > 47 | {isEditingTabName 48 | ? ( 49 | { 55 | setTabs((tabs) => { 56 | const newTabs = [...tabs] 57 | newTabs[index] = { ...tabs[index], name: e.target.value } 58 | return newTabs 59 | }) 60 | }} 61 | onKeyDown={(e) => { 62 | if (e.key === 'Enter') { 63 | finishEditingTabName() 64 | } 65 | }} 66 | onBlur={() => { 67 | finishEditingTabName() 68 | }} 69 | ref={(el) => el?.focus()} 70 | /> 71 | ) 72 | : ( 73 | { 75 | setIsEditingTabName(true) 76 | }} 77 | > 78 | {tab.name} 79 | 80 | )} 81 | 82 | { 86 | // will trigger tab onClick and set this to current tab otherwise 87 | e.stopPropagation() 88 | 89 | const newTabs = [...tabs] 90 | newTabs.splice(index, 1) 91 | if (newTabs.length === 0) { 92 | newTabs.push(createNewDefaultTab()) 93 | } 94 | 95 | // if current tab was closed, this should be -1 (index not found) 96 | const newCurrentTabIndex = newTabs.findIndex((tab) => tab.id === currentTabId) 97 | 98 | if (newCurrentTabIndex === -1) { 99 | updateCurrentTabId(newTabs[0].id) 100 | } 101 | 102 | setTabs(() => newTabs) 103 | }} 104 | > 105 | 106 | 107 | 108 | ) 109 | } 110 | -------------------------------------------------------------------------------- /apps/next/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # next-app 2 | 3 | ## 1.0.4 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies []: 8 | - trpc-playground@1.0.4 9 | 10 | ## 1.0.3 11 | 12 | ### Patch Changes 13 | 14 | - Updated dependencies [[`88a477b`](https://github.com/sachinraja/trpc-playground/commit/88a477b3e4ab1736e7fe298346cd311dc4fd9ea7)]: 15 | - trpc-playground@1.0.3 16 | 17 | ## 1.0.2 18 | 19 | ### Patch Changes 20 | 21 | - Updated dependencies [[`838a7b5`](https://github.com/sachinraja/trpc-playground/commit/838a7b5ebff49aab13497703266c985ba2bb5c1f)]: 22 | - trpc-playground@1.0.2 23 | 24 | ## 1.0.1 25 | 26 | ### Patch Changes 27 | 28 | - Updated dependencies []: 29 | - trpc-playground@1.0.1 30 | 31 | ## 1.0.0 32 | 33 | ### Major Changes 34 | 35 | - [`d226d7b`](https://github.com/sachinraja/trpc-playground/commit/d226d7b5829b79cce8bee3f70d6635f2f76fd796) Thanks [@sachinraja](https://github.com/sachinraja)! - update deps, fix bugs 36 | 37 | ### Patch Changes 38 | 39 | - Updated dependencies [[`d226d7b`](https://github.com/sachinraja/trpc-playground/commit/d226d7b5829b79cce8bee3f70d6635f2f76fd796)]: 40 | - trpc-playground@1.0.0 41 | 42 | ## 1.0.0-next.0 43 | 44 | ### Major Changes 45 | 46 | - [`413fca6`](https://github.com/sachinraja/trpc-playground/commit/413fca6c4c4cb50b690d3fb76ea1b43a713275ef) Thanks [@sachinraja](https://github.com/sachinraja)! - support tRPC v10 47 | 48 | ### Patch Changes 49 | 50 | - Updated dependencies [[`413fca6`](https://github.com/sachinraja/trpc-playground/commit/413fca6c4c4cb50b690d3fb76ea1b43a713275ef)]: 51 | - trpc-playground@1.0.0-next.0 52 | 53 | ## null 54 | 55 | ### Patch Changes 56 | 57 | - Updated dependencies [[`a9b1f29`](https://github.com/sachinraja/trpc-playground/commit/a9b1f297309d4346b5e58a7df4aafed3567920ef)]: 58 | - trpc-playground@0.4.3 59 | 60 | ## null 61 | 62 | ### Patch Changes 63 | 64 | - Updated dependencies [[`ed8a74e`](https://github.com/sachinraja/trpc-playground/commit/ed8a74e248aa8a16210595d40be9e05c96962603)]: 65 | - trpc-playground@0.4.2 66 | 67 | ## null 68 | 69 | ### Patch Changes 70 | 71 | - Updated dependencies [[`8fb105e`](https://github.com/sachinraja/trpc-playground/commit/8fb105ecf80b3ded6c12706ab0e16ef0ba51f7a6)]: 72 | - trpc-playground@0.4.1 73 | 74 | ## null 75 | 76 | ### Patch Changes 77 | 78 | - Updated dependencies [[`732d401`](https://github.com/sachinraja/trpc-playground/commit/732d401f804bc24feb9905eb2161f8311c0919c3)]: 79 | - trpc-playground@0.4.0 80 | 81 | ## null 82 | 83 | ### Patch Changes 84 | 85 | - Updated dependencies []: 86 | - trpc-playground@0.3.3 87 | 88 | ## null 89 | 90 | ### Patch Changes 91 | 92 | - Updated dependencies []: 93 | - trpc-playground@0.3.2 94 | 95 | ## null 96 | 97 | ### Patch Changes 98 | 99 | - Updated dependencies []: 100 | - trpc-playground@0.3.1 101 | 102 | ## null 103 | 104 | ### Patch Changes 105 | 106 | - Updated dependencies []: 107 | - trpc-playground@0.3.0 108 | 109 | ## null 110 | 111 | ### Patch Changes 112 | 113 | - Updated dependencies [[`c2f5e54`](https://github.com/sachinraja/trpc-playground/commit/c2f5e543056786b10ec1ebf59f32567a102de611)]: 114 | - trpc-playground@0.2.0 115 | 116 | ## null 117 | 118 | ### Patch Changes 119 | 120 | - Updated dependencies [[`78948da`](https://github.com/sachinraja/trpc-playground/commit/78948daca6df5ad0df71900f3874e739481d0287)]: 121 | - trpc-playground@0.1.6 122 | 123 | ## null 124 | 125 | ### Patch Changes 126 | 127 | - Updated dependencies [[`1b2f93e`](https://github.com/sachinraja/trpc-playground/commit/1b2f93e780c3bddbf17d09c2a8f14e74e85b3fcb)]: 128 | - trpc-playground@0.1.5 129 | 130 | ## null 131 | 132 | ### Patch Changes 133 | 134 | - trpc-playground@0.1.4 135 | 136 | ## null 137 | 138 | ### Patch Changes 139 | 140 | - Updated dependencies [50bba50] 141 | - trpc-playground@0.1.3 142 | 143 | ## null 144 | 145 | ### Patch Changes 146 | 147 | - Updated dependencies [80169fd] 148 | - trpc-playground@0.1.2 149 | 150 | ## null 151 | 152 | ### Patch Changes 153 | 154 | - trpc-playground@0.1.1 155 | 156 | ## null 157 | 158 | ### Patch Changes 159 | 160 | - Updated dependencies [aa288c7] 161 | - trpc-playground@0.1.0 162 | -------------------------------------------------------------------------------- /apps/express/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # express-app 2 | 3 | ## 1.0.4 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies []: 8 | - trpc-playground@1.0.4 9 | 10 | ## 1.0.3 11 | 12 | ### Patch Changes 13 | 14 | - Updated dependencies [[`88a477b`](https://github.com/sachinraja/trpc-playground/commit/88a477b3e4ab1736e7fe298346cd311dc4fd9ea7)]: 15 | - trpc-playground@1.0.3 16 | 17 | ## 1.0.2 18 | 19 | ### Patch Changes 20 | 21 | - Updated dependencies [[`838a7b5`](https://github.com/sachinraja/trpc-playground/commit/838a7b5ebff49aab13497703266c985ba2bb5c1f)]: 22 | - trpc-playground@1.0.2 23 | 24 | ## 1.0.1 25 | 26 | ### Patch Changes 27 | 28 | - Updated dependencies []: 29 | - trpc-playground@1.0.1 30 | 31 | ## 1.0.0 32 | 33 | ### Major Changes 34 | 35 | - [`d226d7b`](https://github.com/sachinraja/trpc-playground/commit/d226d7b5829b79cce8bee3f70d6635f2f76fd796) Thanks [@sachinraja](https://github.com/sachinraja)! - update deps, fix bugs 36 | 37 | ### Patch Changes 38 | 39 | - Updated dependencies [[`d226d7b`](https://github.com/sachinraja/trpc-playground/commit/d226d7b5829b79cce8bee3f70d6635f2f76fd796)]: 40 | - trpc-playground@1.0.0 41 | 42 | ## 1.0.0-next.0 43 | 44 | ### Major Changes 45 | 46 | - [`413fca6`](https://github.com/sachinraja/trpc-playground/commit/413fca6c4c4cb50b690d3fb76ea1b43a713275ef) Thanks [@sachinraja](https://github.com/sachinraja)! - support tRPC v10 47 | 48 | ### Patch Changes 49 | 50 | - Updated dependencies [[`413fca6`](https://github.com/sachinraja/trpc-playground/commit/413fca6c4c4cb50b690d3fb76ea1b43a713275ef)]: 51 | - trpc-playground@1.0.0-next.0 52 | 53 | ## null 54 | 55 | ### Patch Changes 56 | 57 | - Updated dependencies [[`a9b1f29`](https://github.com/sachinraja/trpc-playground/commit/a9b1f297309d4346b5e58a7df4aafed3567920ef)]: 58 | - trpc-playground@0.4.3 59 | 60 | ## null 61 | 62 | ### Patch Changes 63 | 64 | - Updated dependencies [[`ed8a74e`](https://github.com/sachinraja/trpc-playground/commit/ed8a74e248aa8a16210595d40be9e05c96962603)]: 65 | - trpc-playground@0.4.2 66 | 67 | ## null 68 | 69 | ### Patch Changes 70 | 71 | - Updated dependencies [[`8fb105e`](https://github.com/sachinraja/trpc-playground/commit/8fb105ecf80b3ded6c12706ab0e16ef0ba51f7a6)]: 72 | - trpc-playground@0.4.1 73 | 74 | ## null 75 | 76 | ### Patch Changes 77 | 78 | - Updated dependencies [[`732d401`](https://github.com/sachinraja/trpc-playground/commit/732d401f804bc24feb9905eb2161f8311c0919c3)]: 79 | - trpc-playground@0.4.0 80 | 81 | ## null 82 | 83 | ### Patch Changes 84 | 85 | - Updated dependencies []: 86 | - trpc-playground@0.3.3 87 | 88 | ## null 89 | 90 | ### Patch Changes 91 | 92 | - Updated dependencies []: 93 | - trpc-playground@0.3.2 94 | 95 | ## null 96 | 97 | ### Patch Changes 98 | 99 | - Updated dependencies []: 100 | - trpc-playground@0.3.1 101 | 102 | ## null 103 | 104 | ### Patch Changes 105 | 106 | - Updated dependencies []: 107 | - trpc-playground@0.3.0 108 | 109 | ## null 110 | 111 | ### Patch Changes 112 | 113 | - Updated dependencies [[`c2f5e54`](https://github.com/sachinraja/trpc-playground/commit/c2f5e543056786b10ec1ebf59f32567a102de611)]: 114 | - trpc-playground@0.2.0 115 | 116 | ## null 117 | 118 | ### Patch Changes 119 | 120 | - Updated dependencies [[`78948da`](https://github.com/sachinraja/trpc-playground/commit/78948daca6df5ad0df71900f3874e739481d0287)]: 121 | - trpc-playground@0.1.6 122 | 123 | ## null 124 | 125 | ### Patch Changes 126 | 127 | - Updated dependencies [[`1b2f93e`](https://github.com/sachinraja/trpc-playground/commit/1b2f93e780c3bddbf17d09c2a8f14e74e85b3fcb)]: 128 | - trpc-playground@0.1.5 129 | 130 | ## null 131 | 132 | ### Patch Changes 133 | 134 | - trpc-playground@0.1.4 135 | 136 | ## null 137 | 138 | ### Patch Changes 139 | 140 | - Updated dependencies [50bba50] 141 | - trpc-playground@0.1.3 142 | 143 | ## null 144 | 145 | ### Patch Changes 146 | 147 | - Updated dependencies [80169fd] 148 | - trpc-playground@0.1.2 149 | 150 | ## null 151 | 152 | ### Patch Changes 153 | 154 | - trpc-playground@0.1.1 155 | 156 | ## null 157 | 158 | ### Patch Changes 159 | 160 | - Updated dependencies [aa288c7] 161 | - trpc-playground@0.1.0 162 | -------------------------------------------------------------------------------- /packages/components/src/components/settings.tsx: -------------------------------------------------------------------------------- 1 | import { PlusIcon, XMarkIcon } from '@heroicons/react/20/solid' 2 | import { useAtom } from 'jotai' 3 | import { SidebarOverlay } from './sidebarOverlay' 4 | import { headersAtom } from './tab/store' 5 | import { Headers as HeadersType } from './tab/types' 6 | 7 | type SettingsProps = { 8 | hide: () => void 9 | } 10 | 11 | export const Settings = ({ hide }: SettingsProps) => { 12 | return ( 13 | 14 | 15 | 16 | Settings 17 | 18 | 19 | 20 | 25 | 26 | 27 | 28 | 29 | Global headers 30 | 31 | 32 | 33 | 34 | ) 35 | } 36 | 37 | const Headers = () => { 38 | const [headers, setHeaders] = useAtom(headersAtom) 39 | 40 | return ( 41 | 42 | 43 | {Object.entries(headers).map(([name, value]) => ( 44 | 49 | ))} 50 | 51 | { 55 | const newHeaderName = prompt('Enter name for new header')?.trim() 56 | if (!newHeaderName) return 57 | 58 | if (newHeaderName in headers) { 59 | return alert(`Header with the name '${newHeaderName}' already exists`) 60 | } 61 | 62 | setHeaders((headers: HeadersType) => { 63 | const newHeaders = { ...headers } 64 | newHeaders[newHeaderName] = '' 65 | return newHeaders 66 | }) 67 | }} 68 | > 69 | 70 | 71 | 72 | ) 73 | } 74 | 75 | interface HeaderProps { 76 | name: string 77 | value: string 78 | } 79 | 80 | const Header: React.FC = ({ name, value }) => { 81 | const [headers, setHeaders] = useAtom(headersAtom) 82 | 83 | return ( 84 | 85 | { 92 | const newName = prompt('Rename header', name)?.trim() 93 | if (newName == null || newName === name || !newName) return 94 | 95 | if (newName in headers) return alert(`'${newName}' already exists`) 96 | 97 | setHeaders((headers) => { 98 | const newHeaders = { ...headers } 99 | newHeaders[newName] = value 100 | delete newHeaders[name] 101 | 102 | return newHeaders 103 | }) 104 | }} 105 | /> 106 | { 112 | setHeaders((headers) => { 113 | headers[name] = e.currentTarget.value 114 | return headers 115 | }) 116 | }} 117 | /> 118 | { 120 | setHeaders((headers) => { 121 | const newHeaders = { ...headers } 122 | delete newHeaders[name] 123 | return newHeaders 124 | }) 125 | }} 126 | title='Remove Header' 127 | > 128 | 133 | 134 | 135 | ) 136 | } 137 | -------------------------------------------------------------------------------- /packages/components/src/components/queryBuilder/index.tsx: -------------------------------------------------------------------------------- 1 | import { ChevronUpIcon } from '@heroicons/react/24/solid' 2 | import { atom, useAtom, WritableAtom } from 'jotai' 3 | import { useCallback } from 'preact/hooks' 4 | import { Resizable } from 're-resizable' 5 | import { typesAtom } from '../provider' 6 | import { editorAtom, queryBuilderOpened } from '../tab/store' 7 | import { getDefaultOperation } from './getDefaultOperation' 8 | 9 | const generatedAtom = atom(null) 10 | const baseOperationNameAtom = atom(null) 11 | // Updates `baseOperationNameAtom` and `generatedAtom` 12 | const operationNameAtom: WritableAtom = atom( 13 | get => get(baseOperationNameAtom), 14 | (get, set, update) => { 15 | set(baseOperationNameAtom, update) 16 | 17 | const types = get(typesAtom) 18 | if (!types) return 19 | 20 | const defaultOp = getDefaultOperation({ types, operationName: update }) 21 | if (defaultOp?.value) { 22 | set(generatedAtom, defaultOp.value) 23 | } 24 | }, 25 | ) 26 | 27 | export const QueryBuilder = () => { 28 | const [types] = useAtom(typesAtom) 29 | const [queryBuilderOpen, setQueryBuilderOpened] = useAtom(queryBuilderOpened) 30 | const [generated] = useAtom(generatedAtom) 31 | const [operationName, setOperationName] = useAtom(operationNameAtom) 32 | const [editorView] = useAtom(editorAtom) 33 | 34 | // On insert btn click 35 | const insertGenerated = useCallback( 36 | () => { 37 | if (!operationName || !editorView) return 38 | const gen = getDefaultOperation({ operationName, types }) 39 | if (!gen) return 40 | 41 | const { value: generated, inputLength } = gen 42 | const cursor = editorView.state.selection.asSingle().ranges[0] 43 | 44 | editorView.focus() 45 | // Add generated query to current line in editor 46 | editorView.dispatch({ 47 | changes: { 48 | from: cursor.from, 49 | to: cursor.to, 50 | insert: generated, 51 | }, 52 | }) 53 | 54 | // Select generated default input so user can instantly remove/edit 55 | if (cursor.from + generated.length - inputLength !== cursor.from + generated.length) { 56 | editorView.dispatch({ 57 | selection: { 58 | anchor: cursor.from + generated.length - inputLength - 1, 59 | head: cursor.from + generated.length - 1, 60 | }, 61 | }) 62 | } 63 | }, 64 | [editorView, operationName, types], 65 | ) 66 | 67 | if (!types) return null 68 | 69 | return ( 70 | setQueryBuilderOpened(true)} 75 | minWidth='100%' 76 | > 77 | 78 | setQueryBuilderOpened(open => !open)} 81 | > 82 | Query builder 83 | 84 | {queryBuilderOpen 85 | ? 86 | : } 87 | 88 | 89 | {queryBuilderOpen && ( 90 | 91 | Operations 92 | 93 | 94 | {Object.keys({ ...types.queries, ...types.mutations }).sort().map((opName) => ( 95 | setOperationName(opName)} 100 | > 101 | {opName} 102 | 103 | ))} 104 | 105 | 106 | 115 | Insert 116 | 117 | {generated} 118 | 119 | 120 | 121 | )} 122 | 123 | 124 | ) 125 | } 126 | -------------------------------------------------------------------------------- /apps/vite/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # vite-app 2 | 3 | ## 1.0.4 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [[`1d7112c`](https://github.com/sachinraja/trpc-playground/commit/1d7112cc2987d0daccb611becd3661a9fad8123c)]: 8 | - @trpc-playground/components@1.0.2 9 | - trpc-playground@1.0.4 10 | 11 | ## 1.0.3 12 | 13 | ### Patch Changes 14 | 15 | - Updated dependencies [[`88a477b`](https://github.com/sachinraja/trpc-playground/commit/88a477b3e4ab1736e7fe298346cd311dc4fd9ea7)]: 16 | - trpc-playground@1.0.3 17 | 18 | ## 1.0.2 19 | 20 | ### Patch Changes 21 | 22 | - Updated dependencies [[`838a7b5`](https://github.com/sachinraja/trpc-playground/commit/838a7b5ebff49aab13497703266c985ba2bb5c1f)]: 23 | - trpc-playground@1.0.2 24 | 25 | ## 1.0.1 26 | 27 | ### Patch Changes 28 | 29 | - Updated dependencies []: 30 | - trpc-playground@1.0.1 31 | - @trpc-playground/components@1.0.1 32 | 33 | ## 1.0.0 34 | 35 | ### Major Changes 36 | 37 | - [`d226d7b`](https://github.com/sachinraja/trpc-playground/commit/d226d7b5829b79cce8bee3f70d6635f2f76fd796) Thanks [@sachinraja](https://github.com/sachinraja)! - update deps, fix bugs 38 | 39 | ### Patch Changes 40 | 41 | - Updated dependencies [[`d226d7b`](https://github.com/sachinraja/trpc-playground/commit/d226d7b5829b79cce8bee3f70d6635f2f76fd796)]: 42 | - @trpc-playground/components@1.0.0 43 | - trpc-playground@1.0.0 44 | 45 | ## 1.0.0-next.0 46 | 47 | ### Major Changes 48 | 49 | - [`413fca6`](https://github.com/sachinraja/trpc-playground/commit/413fca6c4c4cb50b690d3fb76ea1b43a713275ef) Thanks [@sachinraja](https://github.com/sachinraja)! - support tRPC v10 50 | 51 | ### Patch Changes 52 | 53 | - Updated dependencies [[`413fca6`](https://github.com/sachinraja/trpc-playground/commit/413fca6c4c4cb50b690d3fb76ea1b43a713275ef)]: 54 | - @trpc-playground/components@1.0.0-next.0 55 | - trpc-playground@1.0.0-next.0 56 | 57 | ## null 58 | 59 | ### Patch Changes 60 | 61 | - Updated dependencies [[`a9b1f29`](https://github.com/sachinraja/trpc-playground/commit/a9b1f297309d4346b5e58a7df4aafed3567920ef)]: 62 | - trpc-playground@0.4.3 63 | 64 | ## null 65 | 66 | ### Patch Changes 67 | 68 | - Updated dependencies [[`ed8a74e`](https://github.com/sachinraja/trpc-playground/commit/ed8a74e248aa8a16210595d40be9e05c96962603)]: 69 | - trpc-playground@0.4.2 70 | 71 | ## null 72 | 73 | ### Patch Changes 74 | 75 | - Updated dependencies [[`8fb105e`](https://github.com/sachinraja/trpc-playground/commit/8fb105ecf80b3ded6c12706ab0e16ef0ba51f7a6)]: 76 | - trpc-playground@0.4.1 77 | 78 | ## null 79 | 80 | ### Patch Changes 81 | 82 | - Updated dependencies [[`732d401`](https://github.com/sachinraja/trpc-playground/commit/732d401f804bc24feb9905eb2161f8311c0919c3)]: 83 | - trpc-playground@0.4.0 84 | 85 | ## null 86 | 87 | ### Patch Changes 88 | 89 | - Updated dependencies [[`dd904cf`](https://github.com/sachinraja/trpc-playground/commit/dd904cfe853a61e4aeb68a31250b101598794dea)]: 90 | - @trpc-playground/components@0.2.3 91 | - trpc-playground@0.3.3 92 | 93 | ## null 94 | 95 | ### Patch Changes 96 | 97 | - Updated dependencies [[`3eb67eb`](https://github.com/sachinraja/trpc-playground/commit/3eb67eb100e96d3f804ac34976f26888df923a37)]: 98 | - @trpc-playground/components@0.2.2 99 | - trpc-playground@0.3.2 100 | 101 | ## null 102 | 103 | ### Patch Changes 104 | 105 | - Updated dependencies [[`e7620f3`](https://github.com/sachinraja/trpc-playground/commit/e7620f3238dd1ceea4264bc227a5a4217b42ea89)]: 106 | - @trpc-playground/components@0.2.1 107 | - trpc-playground@0.3.1 108 | 109 | ## null 110 | 111 | ### Patch Changes 112 | 113 | - Updated dependencies []: 114 | - trpc-playground@0.3.0 115 | 116 | ## null 117 | 118 | ### Patch Changes 119 | 120 | - Updated dependencies [[`c2f5e54`](https://github.com/sachinraja/trpc-playground/commit/c2f5e543056786b10ec1ebf59f32567a102de611)]: 121 | - @trpc-playground/components@0.2.0 122 | - trpc-playground@0.2.0 123 | 124 | ## null 125 | 126 | ### Patch Changes 127 | 128 | - Updated dependencies [[`78948da`](https://github.com/sachinraja/trpc-playground/commit/78948daca6df5ad0df71900f3874e739481d0287)]: 129 | - trpc-playground@0.1.6 130 | 131 | ## null 132 | 133 | ### Patch Changes 134 | 135 | - Updated dependencies [[`1b2f93e`](https://github.com/sachinraja/trpc-playground/commit/1b2f93e780c3bddbf17d09c2a8f14e74e85b3fcb)]: 136 | - @trpc-playground/components@0.1.2 137 | - trpc-playground@0.1.5 138 | 139 | ## null 140 | 141 | ### Patch Changes 142 | 143 | - Updated dependencies [2b46455] 144 | - @trpc-playground/components@0.1.1 145 | - trpc-playground@0.1.4 146 | 147 | ## null 148 | 149 | ### Patch Changes 150 | 151 | - Updated dependencies [50bba50] 152 | - trpc-playground@0.1.3 153 | 154 | ## null 155 | 156 | ### Patch Changes 157 | 158 | - Updated dependencies [80169fd] 159 | - trpc-playground@0.1.2 160 | 161 | ## null 162 | 163 | ### Patch Changes 164 | 165 | - trpc-playground@0.1.1 166 | 167 | ## null 168 | 169 | ### Patch Changes 170 | 171 | - Updated dependencies [aa288c7] 172 | - @trpc-playground/components@0.1.0 173 | - trpc-playground@0.1.0 174 | -------------------------------------------------------------------------------- /packages/trpc-playground/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # trpc-playground 2 | 3 | ## 1.0.4 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [[`1d7112c`](https://github.com/sachinraja/trpc-playground/commit/1d7112cc2987d0daccb611becd3661a9fad8123c)]: 8 | - @trpc-playground/html@1.0.4 9 | 10 | ## 1.0.3 11 | 12 | ### Patch Changes 13 | 14 | - [`88a477b`](https://github.com/sachinraja/trpc-playground/commit/88a477b3e4ab1736e7fe298346cd311dc4fd9ea7) Thanks [@sachinraja](https://github.com/sachinraja)! - remove console.log 15 | 16 | ## 1.0.2 17 | 18 | ### Patch Changes 19 | 20 | - [`838a7b5`](https://github.com/sachinraja/trpc-playground/commit/838a7b5ebff49aab13497703266c985ba2bb5c1f) Thanks [@sachinraja](https://github.com/sachinraja)! - support interop mode 21 | 22 | ## 1.0.1 23 | 24 | ### Patch Changes 25 | 26 | - Updated dependencies [[`012cb1e`](https://github.com/sachinraja/trpc-playground/commit/012cb1e3888e69ddc76abf55492b2d3441989fa8)]: 27 | - @trpc-playground/html@1.0.1 28 | 29 | ## 1.0.0 30 | 31 | ### Major Changes 32 | 33 | - [`d226d7b`](https://github.com/sachinraja/trpc-playground/commit/d226d7b5829b79cce8bee3f70d6635f2f76fd796) Thanks [@sachinraja](https://github.com/sachinraja)! - update deps, fix bugs 34 | 35 | ### Patch Changes 36 | 37 | - Updated dependencies [[`d226d7b`](https://github.com/sachinraja/trpc-playground/commit/d226d7b5829b79cce8bee3f70d6635f2f76fd796)]: 38 | - @trpc-playground/html@1.0.0 39 | - @trpc-playground/types@1.0.0 40 | 41 | ## 1.0.0-next.0 42 | 43 | ### Major Changes 44 | 45 | - [`413fca6`](https://github.com/sachinraja/trpc-playground/commit/413fca6c4c4cb50b690d3fb76ea1b43a713275ef) Thanks [@sachinraja](https://github.com/sachinraja)! - support tRPC v10 46 | 47 | ### Patch Changes 48 | 49 | - Updated dependencies [[`413fca6`](https://github.com/sachinraja/trpc-playground/commit/413fca6c4c4cb50b690d3fb76ea1b43a713275ef)]: 50 | - @trpc-playground/types@1.0.0-next.0 51 | - @trpc-playground/html@1.0.0-next.0 52 | 53 | ## 0.4.3 54 | 55 | ### Patch Changes 56 | 57 | - [`a9b1f29`](https://github.com/sachinraja/trpc-playground/commit/a9b1f297309d4346b5e58a7df4aafed3567920ef) Thanks [@sachinraja](https://github.com/sachinraja)! - update uttp 58 | 59 | ## 0.4.2 60 | 61 | ### Patch Changes 62 | 63 | - [`ed8a74e`](https://github.com/sachinraja/trpc-playground/commit/ed8a74e248aa8a16210595d40be9e05c96962603) Thanks [@sachinraja](https://github.com/sachinraja)! - fix body parsing (again) 64 | 65 | ## 0.4.1 66 | 67 | ### Patch Changes 68 | 69 | - [`8fb105e`](https://github.com/sachinraja/trpc-playground/commit/8fb105ecf80b3ded6c12706ab0e16ef0ba51f7a6) Thanks [@sachinraja](https://github.com/sachinraja)! - fix body parsing 70 | 71 | ## 0.4.0 72 | 73 | ### Minor Changes 74 | 75 | - [`732d401`](https://github.com/sachinraja/trpc-playground/commit/732d401f804bc24feb9905eb2161f8311c0919c3) Thanks [@sachinraja](https://github.com/sachinraja)! - support Fastify, Fetch, h3, Koa, and AWS Lambda 76 | 77 | ## 0.3.3 78 | 79 | ### Patch Changes 80 | 81 | - Updated dependencies [[`dd904cf`](https://github.com/sachinraja/trpc-playground/commit/dd904cfe853a61e4aeb68a31250b101598794dea)]: 82 | - @trpc-playground/html@0.3.3 83 | 84 | ## 0.3.2 85 | 86 | ### Patch Changes 87 | 88 | - Updated dependencies [[`3eb67eb`](https://github.com/sachinraja/trpc-playground/commit/3eb67eb100e96d3f804ac34976f26888df923a37)]: 89 | - @trpc-playground/html@0.3.2 90 | 91 | ## 0.3.1 92 | 93 | ### Patch Changes 94 | 95 | - Updated dependencies [[`e7620f3`](https://github.com/sachinraja/trpc-playground/commit/e7620f3238dd1ceea4264bc227a5a4217b42ea89)]: 96 | - @trpc-playground/html@0.3.1 97 | 98 | ## 0.3.0 99 | 100 | ### Patch Changes 101 | 102 | - Updated dependencies [[`5553920`](https://github.com/sachinraja/trpc-playground/commit/5553920db2bd15da8249d19826a9b7a1ecf1791f)]: 103 | - @trpc-playground/html@0.3.0 104 | 105 | ## 0.2.0 106 | 107 | ### Minor Changes 108 | 109 | - [`c2f5e54`](https://github.com/sachinraja/trpc-playground/commit/c2f5e543056786b10ec1ebf59f32567a102de611) Thanks [@sachinraja](https://github.com/sachinraja)! - add interactive UI 110 | 111 | ### Patch Changes 112 | 113 | - Updated dependencies [[`c2f5e54`](https://github.com/sachinraja/trpc-playground/commit/c2f5e543056786b10ec1ebf59f32567a102de611)]: 114 | - @trpc-playground/types@0.2.0 115 | - @trpc-playground/html@0.1.5 116 | 117 | ## 0.1.6 118 | 119 | ### Patch Changes 120 | 121 | - [`78948da`](https://github.com/sachinraja/trpc-playground/commit/78948daca6df5ad0df71900f3874e739481d0287) Thanks [@sachinraja](https://github.com/sachinraja)! - fix lodash import 122 | 123 | ## 0.1.5 124 | 125 | ### Patch Changes 126 | 127 | - [`1b2f93e`](https://github.com/sachinraja/trpc-playground/commit/1b2f93e780c3bddbf17d09c2a8f14e74e85b3fcb) Thanks [@sachinraja](https://github.com/sachinraja)! - add superjson transformer option 128 | 129 | - Updated dependencies [[`1b2f93e`](https://github.com/sachinraja/trpc-playground/commit/1b2f93e780c3bddbf17d09c2a8f14e74e85b3fcb)]: 130 | - @trpc-playground/html@0.1.5 131 | - @trpc-playground/types@0.1.1 132 | 133 | ## 0.1.4 134 | 135 | ### Patch Changes 136 | 137 | - Updated dependencies [2b46455] 138 | - @trpc-playground/html@0.1.4 139 | 140 | ## 0.1.3 141 | 142 | ### Patch Changes 143 | 144 | - 50bba50: remove `console.log` 145 | 146 | ## 0.1.2 147 | 148 | ### Patch Changes 149 | 150 | - 80169fd: fix types for procedures without `input` 151 | 152 | ## 0.1.1 153 | 154 | ### Patch Changes 155 | 156 | - Updated dependencies [4629f11] 157 | - @trpc-playground/html@0.1.1 158 | 159 | ## 0.1.0 160 | 161 | ### Minor Changes 162 | 163 | - aa288c7: first release 164 | 165 | ### Patch Changes 166 | 167 | - Updated dependencies [aa288c7] 168 | - @trpc-playground/html@0.1.0 169 | - @trpc-playground/types@0.1.0 170 | -------------------------------------------------------------------------------- /packages/trpc-playground/README.md: -------------------------------------------------------------------------------- 1 | # tRPC Playground 2 | 3 | Playground for running tRPC queries in the browser. Backed by CodeMirror and the TypeScript language server to provide you with the same fully-typed experience. 4 | 5 | https://user-images.githubusercontent.com/58836760/150659582-ff844a0f-e1b8-4cb4-8d89-0d53a8669db5.mp4 6 | 7 | ## Installation 8 | 9 | ```sh 10 | npm install trpc-playground 11 | ``` 12 | 13 | ## Handlers 14 | 15 | tRPC Playground provides handlers that serve the playground HTML page and handle playground-related requests such as getting types from the router. 16 | 17 | 18 | Next.js 19 | 20 | Next.js App Router 21 | 22 | ```ts 23 | // src/app/api/trpc-playground/route.ts 24 | import { appRouter } from '@/server' 25 | import { fetchHandler } from 'trpc-playground/handlers/fetch' 26 | 27 | const setupHandler = fetchHandler({ 28 | router: appRouter, 29 | trpcApiEndpoint: '/api/trpc', 30 | playgroundEndpoint: '/api/trpc-playground', 31 | }) 32 | 33 | const handler = async (req: Request) => { 34 | const playgroundHandler = await setupHandler 35 | return await playgroundHandler(req) 36 | } 37 | 38 | export { handler as GET, handler as POST } 39 | ``` 40 | 41 | Next.js Pages Router 42 | 43 | [Example](https://github.com/sachinraja/trpc-playground/tree/main/apps/next) 44 | 45 | ```ts 46 | // pages/api/trpc-playground.ts 47 | import { NextApiHandler } from 'next' 48 | import { appRouter } from 'server/routers/_app' 49 | import { nextHandler } from 'trpc-playground/handlers/next' 50 | 51 | const setupHandler = nextHandler({ 52 | router: appRouter, 53 | // tRPC api path, pages/api/trpc/[trpc].ts in this case 54 | trpcApiEndpoint: '/api/trpc', 55 | playgroundEndpoint: '/api/trpc-playground', 56 | // uncomment this if you're using superjson 57 | // request: { 58 | // superjson: true, 59 | // }, 60 | }) 61 | 62 | const handler: NextApiHandler = async (req, res) => { 63 | const playgroundHandler = await setupHandler 64 | await playgroundHandler(req, res) 65 | } 66 | 67 | export default handler 68 | ``` 69 | 70 | 71 | 72 | 73 | Express 74 | 75 | [Example](https://github.com/sachinraja/trpc-playground/tree/main/apps/express) 76 | 77 | ```ts 78 | // server.ts 79 | import * as trpcExpress from '@trpc/server/adapters/express' 80 | import express from 'express' 81 | import { expressHandler } from 'trpc-playground/handlers/express' 82 | import { appRouter } from './router' 83 | 84 | const runApp = async () => { 85 | const app = express() 86 | 87 | const trpcApiEndpoint = '/api/trpc' 88 | const playgroundEndpoint = '/api/trpc-playground' 89 | 90 | app.use( 91 | trpcApiEndpoint, 92 | trpcExpress.createExpressMiddleware({ 93 | router: appRouter, 94 | }), 95 | ) 96 | 97 | app.use( 98 | playgroundEndpoint, 99 | await expressHandler({ 100 | trpcApiEndpoint, 101 | playgroundEndpoint, 102 | router: appRouter, 103 | // uncomment this if you're using superjson 104 | // request: { 105 | // superjson: true, 106 | // }, 107 | }), 108 | ) 109 | 110 | app.listen(3000, () => { 111 | console.log('listening at http://localhost:3000') 112 | }) 113 | } 114 | 115 | runApp() 116 | ``` 117 | 118 | 119 | 120 | 121 | trpc-nuxt 122 | 123 | [trpc-nuxt](https://github.com/wobsoriano/trpc-nuxt) 124 | 125 | ```ts 126 | // server/api/trpc-playground.ts 127 | 128 | import { h3Handler } from 'trpc-playground/handlers/h3' 129 | import { appRouter } from '~~/server/trpc/routers' 130 | 131 | export default defineLazyEventHandler(async () => { 132 | const setupHandler = await h3Handler({ 133 | router: appRouter, 134 | trpcApiEndpoint: '/api/trpc', 135 | playgroundEndpoint: '/api/trpc-playground', 136 | // uncomment this if you're using superjson 137 | // request: { 138 | // superjson: true, 139 | // }, 140 | }) 141 | 142 | return defineEventHandler(setupHandler) 143 | }) 144 | ``` 145 | 146 | 147 | 148 | tRPC Playground also supports Fastify, Fetch, h3, Koa, and AWS Lambda. You can import them using this format: `trpc-playground/handlers/{framework}`. 149 | 150 | ## Settings 151 | 152 | For all configuration options, see [the API docs](https://paka.dev/npm/@trpc-playground/types@0.1.1/api#be2d1ce7878179d). 153 | 154 | ## Writing Queries 155 | 156 | In the playground, writing queries is meant to mimic the experience of writing queries in a tRPC client as closely as possible. You can even write TS and your code will be transformed to JS before it is run. 157 | 158 | tRPC Playground queries follow the syntax of the [proxy vanilla client](https://trpc.io/docs/vanilla): 159 | 160 | ```ts 161 | await trpc.path.query(input) 162 | 163 | // example 164 | await trpc.getUser.query({ id: 4 }) 165 | ``` 166 | 167 | For mutations: 168 | 169 | ```ts 170 | await trpc.path.mutate(input) 171 | 172 | // example 173 | await trpc.createUser.mutate({ name: 'Bob' }) 174 | ``` 175 | 176 | When using the `Run all queries` button in the center of the editor, you can write any code and it will just work: 177 | 178 | ```ts 179 | const name: string = 'John' 180 | 181 | // run queries one at a time 182 | await trpc.getGreeting.query({ name }) 183 | await trpc.getFarewell.query({ name }) 184 | 185 | // batch queries 186 | await Promise.all([ 187 | trpc.getGreeting.query({ name }), 188 | trpc.getFarewell.query({ name }), 189 | ]) 190 | ``` 191 | 192 | Queries can be run individually by pressing on the button to the left of them or by pressing `Alt + Q` when your cursor in the editor is on the query. Note that any variables you pass to the query will not be defined when running queries individually. 193 | 194 | You can use the return value of one query and pass it to the next: 195 | 196 | ```ts 197 | const { sum } = await trpc.addNums.query({ a: 1, b: 2 }) 198 | await trpc.subtractNums.query({ a: sum, b: -7 }) 199 | ``` 200 | 201 | ## Types 202 | 203 | tRPC Playground resolves the types for your queries based on the `input` schema in your router. The default resolver is [`zod-to-ts`](https://github.com/sachinraja/zod-to-ts), which should work out of the box for the most part. However, there are [a few special cases that it may not handle correctly](https://github.com/sachinraja/zod-to-ts#special-cases) such as `z.lazy()` and `z.nativeEnum()`, so read those docs for more information on how to handle these cases if you have any issues with them. 204 | -------------------------------------------------------------------------------- /packages/query-extension/src/line-numbers.ts: -------------------------------------------------------------------------------- 1 | import { EditorSelection, Extension } from '@codemirror/state' 2 | import { EditorView, gutter, GutterMarker } from '@codemirror/view' 3 | import { isCursorInRange } from './find-cursor' 4 | import { Query } from './find-queries' 5 | import { OnErrorFacet, OnExecuteFacet, queryStateField } from './state' 6 | 7 | const SVG_NAMESPACE = 'http://www.w3.org/2000/svg' 8 | 9 | /** 10 | * A GutterMarker that shows line numbers 11 | */ 12 | class LineNumberMarker extends GutterMarker { 13 | private number: number 14 | public elementClass: string 15 | 16 | constructor(number: number) { 17 | super() 18 | 19 | this.number = number 20 | this.elementClass = 'cm-lineNumbers' 21 | } 22 | 23 | eq(other: LineNumberMarker) { 24 | return this.number === other.number 25 | } 26 | 27 | toDOM() { 28 | const widget = document.createElement('div') 29 | widget.classList.add('cm-gutterElement') 30 | widget.textContent = `${this.number}` 31 | return widget 32 | } 33 | } 34 | 35 | /** 36 | * A GutterMarker that shows a "run query" button 37 | */ 38 | class RunQueryMarker extends GutterMarker { 39 | private number: number 40 | private active: boolean 41 | public elementClass: string 42 | 43 | constructor(number: number, active: boolean) { 44 | super() 45 | 46 | this.number = number 47 | this.active = active 48 | this.elementClass = 'cm-lineNumbers' 49 | } 50 | 51 | eq(other: RunQueryMarker) { 52 | return this.number === other.number && this.active === other.active 53 | } 54 | 55 | toDOM() { 56 | const runButton = document.createElement('button') 57 | // Feathericons: play-circle 58 | const svg = document.createElementNS(SVG_NAMESPACE, 'svg') 59 | svg.setAttribute('xmlns', SVG_NAMESPACE) 60 | svg.setAttribute('viewBox', '0 0 24 24') 61 | svg.setAttribute('fill', 'none') 62 | 63 | const circle = document.createElementNS(SVG_NAMESPACE, 'path') 64 | circle.setAttribute('fill-rule', 'evenodd') 65 | circle.setAttribute('clip-rule', 'evenodd') 66 | circle.setAttribute( 67 | 'd', 68 | 'M1 12C1 5.92487 5.92487 1 12 1C18.0751 1 23 5.92487 23 12C23 18.0751 18.0751 23 12 23C5.92487 23 1 18.0751 1 12Z', 69 | ) 70 | circle.setAttribute('fill', 'currentColor') 71 | svg.appendChild(circle) 72 | 73 | // Play button 74 | const path = document.createElementNS(SVG_NAMESPACE, 'path') 75 | path.setAttribute('fill-rule', 'evenodd') 76 | path.setAttribute('clip-rule', 'evenodd') 77 | path.setAttribute( 78 | 'd', 79 | 'M9.52814 7.11833C9.8533 6.94431 10.2478 6.96338 10.5547 7.16795L16.5547 11.168C16.8329 11.3534 17 11.6656 17 12C17 12.3344 16.8329 12.6466 16.5547 12.8321L10.5547 16.8321C10.2478 17.0366 9.8533 17.0557 9.52814 16.8817C9.20298 16.7077 9 16.3688 9 16V8C9 7.63121 9.20298 7.29235 9.52814 7.11833Z', 80 | ) 81 | path.setAttribute('fill', 'white') 82 | svg.appendChild(path) 83 | 84 | runButton.appendChild(svg) 85 | runButton.classList.add('cm-gutterElement') 86 | runButton.classList.add('cm-queryRunButton') 87 | if (this.active) { 88 | runButton.classList.add('active') 89 | } 90 | 91 | return runButton 92 | } 93 | } 94 | 95 | export function lineNumbers(): Extension { 96 | return [ 97 | gutter({ 98 | lineMarker: (view, _line) => { 99 | const line = view.state.doc.lineAt(_line.from) 100 | 101 | // Assume this line should have a line number 102 | let marker: LineNumberMarker | RunQueryMarker = new LineNumberMarker( 103 | line.number, 104 | ) 105 | 106 | view.state 107 | .field(queryStateField) 108 | .between(line.from, line.to, (from, to) => { 109 | // If this is the first line of a query, change the line number to a button 110 | const queryLineStart = view.state.doc.lineAt(from) 111 | if (queryLineStart.number === line.number) { 112 | marker = new RunQueryMarker( 113 | queryLineStart.number, 114 | isCursorInRange(view.state, line.from, to), // If the cursor is inside a query, change the button to `active` 115 | ) 116 | } 117 | }) 118 | 119 | return marker 120 | }, 121 | domEventHandlers: { 122 | click: (view, line, event) => { 123 | const targetElement = event.target as HTMLDivElement 124 | const targetParentElement = targetElement.parentNode as HTMLDivElement 125 | if ( 126 | !targetElement?.classList?.contains('cm-queryRunButton') 127 | && targetParentElement?.classList?.contains('cm-lineNumbers') 128 | ) { 129 | // Clicking on a line number should not execute the query 130 | return false 131 | } 132 | 133 | // Make cursor jump to this line 134 | if (!isCursorInRange(view.state, line.from, line.to)) { 135 | view.dispatch({ 136 | selection: EditorSelection.single(line.from, line.from), 137 | }) 138 | } 139 | 140 | const onExecute = view.state.facet(OnExecuteFacet) 141 | const onError = view.state.facet(OnErrorFacet) 142 | if (!onExecute) { 143 | return false 144 | } 145 | 146 | let query: Query | null = null 147 | let initArgs: Promise 148 | view.state 149 | .field(queryStateField) 150 | .between(line.from, line.to, (from, to, q) => { 151 | initArgs = q.init() 152 | query = q.query 153 | return false 154 | }) 155 | 156 | if (!query) { 157 | return false 158 | } 159 | 160 | initArgs!.then(() => onExecute(query!)).catch((e) => { 161 | if (onError) return onError(e) 162 | throw e 163 | }) 164 | return true 165 | }, 166 | }, 167 | }), 168 | 169 | // Gutter line marker styles 170 | EditorView.baseTheme({ 171 | '.cm-gutter': { 172 | backgroundColor: 'var(--primary-color)', 173 | }, 174 | '.cm-lineNumbers': { 175 | display: 'flex', 176 | '& .cm-gutterElement': { 177 | padding: '0 8px 0 0', 178 | }, 179 | }, 180 | '.cm-gutterElement': { 181 | userSelect: 'none', 182 | backgroundColor: 'var(--primary-color)', 183 | }, 184 | '.cm-queryRunButton': { 185 | cursor: 'pointer', 186 | width: '24px', 187 | height: '24', 188 | backgroundColor: 'var(--primary-color)', 189 | color: '#E2E8F0', /* blueGray-200 */ 190 | 191 | '&:hover': { 192 | color: '#16A34A', /* green-600 */ 193 | }, 194 | '&.active': { 195 | color: '#22C55E', /* green-500 */ 196 | 197 | '&:hover': { 198 | color: '#16A34A', /* green-600 */ 199 | }, 200 | }, 201 | }, 202 | }), 203 | ] 204 | } 205 | -------------------------------------------------------------------------------- /packages/trpc-playground/src/get-default-input.ts: -------------------------------------------------------------------------------- 1 | import { ProcedureSchemas } from '@trpc-playground/types' 2 | import { 3 | z, 4 | ZodArrayDef, 5 | ZodEnumDef, 6 | ZodFirstPartyTypeKind, 7 | ZodIntersectionDef, 8 | ZodLiteralDef, 9 | ZodMapDef, 10 | ZodNativeEnumDef, 11 | ZodNullableDef, 12 | ZodObjectDef, 13 | ZodOptionalDef, 14 | ZodPromiseDef, 15 | ZodRecordDef, 16 | ZodSetDef, 17 | ZodTupleDef, 18 | ZodUnionDef, 19 | } from 'zod' 20 | import { createTypeAlias, printNode, zodToTs } from 'zod-to-ts' 21 | import { getInputFromInputParsers, Procedures, ProcedureTypes } from './zod-resolve-types' 22 | 23 | export const getProcedureSchemas = (procedures: Procedures) => { 24 | const procedureSchemas: ProcedureSchemas = { queries: {}, mutations: {} } 25 | const procedureTypes: ProcedureTypes = { queries: {}, mutations: {} } 26 | 27 | Object.entries(procedures) 28 | .filter(([, { _def }]) => _def.query || _def.mutation) 29 | .forEach(([procedureName, procedure]) => { 30 | const inputParser = getInputFromInputParsers(procedure._def.inputs) 31 | if (typeof inputParser === 'function') { 32 | return z.any() 33 | } 34 | 35 | const defaultInputValue = inputParser ? getDefaultForDef(inputParser._def) : '' 36 | 37 | let procedureType = '' 38 | let docsType = '' 39 | 40 | if (inputParser) { 41 | const { node } = zodToTs(inputParser) 42 | procedureType = `input: ${printNode(node)}` 43 | 44 | docsType = printNode(createTypeAlias(node, 'input', inputParser.description)) 45 | } 46 | 47 | const procedureDefaults = { 48 | inputLength: defaultInputValue.length, 49 | value: `await trpc.${procedureName}.${procedure._def.query ? 'query' : 'mutate'}(${defaultInputValue})`, 50 | } 51 | 52 | const procedureObject = procedure._def.query ? procedureSchemas.queries : procedureSchemas.mutations 53 | const typeProcedureObject = procedure._def.query ? procedureTypes.queries : procedureTypes.mutations 54 | procedureObject[procedureName] = { default: procedureDefaults, type: docsType } 55 | typeProcedureObject[procedureName] = procedureType 56 | }) 57 | 58 | return { schemas: procedureSchemas, types: procedureTypes } 59 | } 60 | 61 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 62 | const getDefaultForDef = (def: any): string => { 63 | if (!def) return '' 64 | 65 | switch (def.typeName) { 66 | case ZodFirstPartyTypeKind.ZodString: 67 | return defaultString() 68 | case ZodFirstPartyTypeKind.ZodDate: 69 | return defaultDate() 70 | case ZodFirstPartyTypeKind.ZodNumber: 71 | return defaultNumber() 72 | case ZodFirstPartyTypeKind.ZodBigInt: 73 | return defaultBigInt() 74 | case ZodFirstPartyTypeKind.ZodBoolean: 75 | return defaultBoolean() 76 | case ZodFirstPartyTypeKind.ZodUndefined: 77 | return defaultUndefined() 78 | case ZodFirstPartyTypeKind.ZodNull: 79 | return defaultNull() 80 | case ZodFirstPartyTypeKind.ZodObject: 81 | return defaultObject(def) 82 | case ZodFirstPartyTypeKind.ZodArray: 83 | return defaultArray(def) 84 | case ZodFirstPartyTypeKind.ZodTuple: 85 | return defaultTuple(def) 86 | case ZodFirstPartyTypeKind.ZodRecord: 87 | return defaultRecord(def) 88 | case ZodFirstPartyTypeKind.ZodLiteral: 89 | return defaultLiteral(def) 90 | case ZodFirstPartyTypeKind.ZodNullable: 91 | return defaultNullable(def) 92 | case ZodFirstPartyTypeKind.ZodOptional: 93 | return defaultOptional(def) 94 | case ZodFirstPartyTypeKind.ZodIntersection: 95 | return defaultIntersection(def) 96 | case ZodFirstPartyTypeKind.ZodEnum: 97 | return defaultEnum(def) 98 | case ZodFirstPartyTypeKind.ZodNativeEnum: 99 | return defaultNativeEnum(def) 100 | case ZodFirstPartyTypeKind.ZodMap: 101 | return defaultMap(def) 102 | case ZodFirstPartyTypeKind.ZodSet: 103 | return defaultSet(def) 104 | case ZodFirstPartyTypeKind.ZodPromise: 105 | return defaultPromise(def) 106 | case ZodFirstPartyTypeKind.ZodNaN: 107 | return 'NaN' 108 | case ZodFirstPartyTypeKind.ZodDiscriminatedUnion: 109 | case ZodFirstPartyTypeKind.ZodUnion: 110 | return defaultUnion(def) 111 | default: 112 | return '' 113 | } 114 | } 115 | 116 | const defaultString = () => { 117 | return `""` 118 | } 119 | 120 | const defaultDate = () => { 121 | return `new Date()` 122 | } 123 | 124 | const defaultNumber = () => { 125 | return `0` 126 | } 127 | 128 | const defaultBigInt = () => { 129 | return `BigInt(0)` 130 | } 131 | 132 | const defaultBoolean = () => { 133 | return `false` 134 | } 135 | 136 | const defaultUndefined = () => { 137 | return `undefined` 138 | } 139 | 140 | const defaultNull = () => { 141 | return `null` 142 | } 143 | 144 | const defaultObject = (def: ZodObjectDef) => { 145 | let ret = `{ ` 146 | 147 | const entries = Object.entries(def.shape()) 148 | entries.forEach(([name, propDef], idx) => { 149 | ret += `${name}: ${getDefaultForDef(propDef._def)}` 150 | if (idx !== entries.length - 1) ret += `, ` 151 | else ret += ` ` 152 | }) 153 | ret += `}` 154 | 155 | return ret 156 | } 157 | 158 | const defaultArray = (def: ZodArrayDef) => { 159 | return `[${getDefaultForDef(def.type._def)}]` 160 | } 161 | 162 | const defaultTuple = (def: ZodTupleDef) => { 163 | let ret = `[` 164 | for (let i = 0; i < def.items.length; i++) { 165 | const item = def.items[i] 166 | ret += `${getDefaultForDef(item._def)}` 167 | if (i !== def.items.length - 1) ret += `` 168 | } 169 | 170 | return ret 171 | } 172 | 173 | const defaultRecord = (_def: ZodRecordDef) => { 174 | return `{ ${getDefaultForDef(_def.keyType._def)}: ${getDefaultForDef(_def.valueType._def)} }` 175 | } 176 | 177 | const defaultLiteral = (def: ZodLiteralDef) => { 178 | return typeof def.value === 'string' ? `"${def.value}"` : `${def.value}` 179 | } 180 | 181 | const defaultNullable = (def: ZodNullableDef) => { 182 | return getDefaultForDef(def.innerType._def) 183 | } 184 | 185 | const defaultOptional = (def: ZodOptionalDef) => { 186 | return getDefaultForDef(def.innerType._def) ?? `undefined` 187 | } 188 | 189 | const defaultEnum = (def: ZodEnumDef) => { 190 | return `"${def.values[0]}"` 191 | } 192 | 193 | const defaultUnion = (def: ZodUnionDef) => { 194 | const options = def.options instanceof Map ? Array.from(def.options.values()) : def.options 195 | if (options.length === 0) return '' 196 | return getDefaultForDef(options[0]._def) 197 | } 198 | 199 | // Does not work correctly 200 | const defaultIntersection = (def: ZodIntersectionDef) => { 201 | return getDefaultForDef(def.right._def) 202 | } 203 | 204 | // don't know if this is the best solution 205 | const defaultNativeEnum = (def: ZodNativeEnumDef) => { 206 | const val = Object.values(def.values)[Object.values(def.values).length - 1] 207 | if (val) { 208 | return typeof val === 'string' ? `"${val}"` : `${val}` 209 | } 210 | 211 | return '' 212 | } 213 | 214 | const defaultMap = (_def: ZodMapDef) => { 215 | return `new Map([[${getDefaultForDef(_def.keyType._def)}, ${getDefaultForDef(_def.valueType._def)}]])` 216 | } 217 | 218 | const defaultSet = (_def: ZodSetDef) => { 219 | return `new Set([${getDefaultForDef(_def.valueType._def)}])` 220 | } 221 | 222 | const defaultPromise = (def: ZodPromiseDef) => { 223 | return `Promise.resolve(${getDefaultForDef(def.type._def)})` 224 | } 225 | -------------------------------------------------------------------------------- /packages/components/src/components/editor.tsx: -------------------------------------------------------------------------------- 1 | import { javascript } from '@codemirror/lang-javascript' 2 | import { EditorState } from '@codemirror/state' 3 | import { EditorView } from '@codemirror/view' 4 | import { PlayIcon } from '@heroicons/react/24/solid' 5 | import { state as queryExtensionState } from '@trpc-playground/query-extension' 6 | import { injectTypes, setDiagnostics } from '@trpc-playground/typescript-extension' 7 | import { printObject } from '@trpc-playground/utils' 8 | import { atom, useAtom } from 'jotai' 9 | import { memo } from 'preact/compat' 10 | import { useCallback, useEffect, useLayoutEffect, useMemo } from 'preact/hooks' 11 | import CodeMirror, { CodeMirrorProps } from 'rodemirror' 12 | import { baseExtension, tsExtension } from '../editor/extensions' 13 | import { transformAndRunQueries } from '../editor/transform-and-run-queries' 14 | import { makePlaygroundRequest } from '../utils/playground-request' 15 | import { configAtom, trpcClientAtom, typesAtom } from './provider' 16 | import { QueryBuilder } from './queryBuilder' 17 | import { 18 | currentTabAtom, 19 | editorAtom, 20 | previousTabAtom, 21 | previousTabIdAtom, 22 | tabsAtom, 23 | updateCurrentTabIdAtom, 24 | } from './tab/store' 25 | 26 | const MemoizedCodeMirror = memo((props: CodeMirrorProps) => ) 27 | 28 | const responseValueAtom = atom(printObject({ foo: 'bar' })) 29 | 30 | export const Editor = () => { 31 | const [editorView, setEditorView] = useAtom(editorAtom) 32 | const [, setTypes] = useAtom(typesAtom) 33 | const [, setTabs] = useAtom(tabsAtom) 34 | const [previousTab] = useAtom(previousTabAtom) 35 | const [previousTabId] = useAtom(previousTabIdAtom) 36 | const [currentTabId] = useAtom(updateCurrentTabIdAtom) 37 | const [currentTab] = useAtom(currentTabAtom) 38 | const [responseValue, setResponseValue] = useAtom(responseValueAtom) 39 | const [trpcClient] = useAtom(trpcClientAtom) 40 | const [config] = useAtom(configAtom) 41 | 42 | const refreshTypes = useCallback(async () => { 43 | if (!editorView) return 44 | 45 | try { 46 | const response = await makePlaygroundRequest('getRouterSchema', { 47 | playgroundEndpoint: config.playgroundEndpoint, 48 | }) 49 | setTypes(response) 50 | 51 | editorView.dispatch(injectTypes({ 52 | '/index.d.ts': response.tsTypes, 53 | })) 54 | // server might be restarting so ignore fetch errors 55 | // eslint-disable-next-line no-empty 56 | } catch (error) {} 57 | }, [editorView]) 58 | 59 | const extensions = useMemo(() => [ 60 | tsExtension, 61 | queryExtensionState({ 62 | async onExecute(query) { 63 | const firstArg = query.args[0] 64 | if (typeof firstArg !== 'string') return 65 | 66 | // force type to satisfy trpc args because it cannot infer types from router 67 | const args = [firstArg, query.args[1]] as unknown as [string, undefined] 68 | try { 69 | if (query.operation === 'query') { 70 | const response = await trpcClient.query(...args) 71 | return setResponseValue(printObject(response)) 72 | } else if (query.operation === 'mutate') { 73 | const response = await trpcClient.mutation(...args) 74 | return setResponseValue(printObject(response)) 75 | } 76 | } catch (e) { 77 | return setResponseValue(printObject(e)) 78 | } 79 | }, 80 | onError(error) { 81 | setResponseValue(printObject(error)) 82 | }, 83 | }), 84 | ], [trpcClient, setResponseValue]) 85 | 86 | const responseEditorExtensions = useMemo(() => [ 87 | baseExtension, 88 | javascript(), 89 | EditorState.readOnly.of(true), 90 | EditorView.theme({ 91 | '.cm-line': { 92 | marginLeft: '30px', 93 | }, 94 | }), 95 | ], []) 96 | 97 | useLayoutEffect(() => { 98 | if (!editorView || !previousTab || (previousTabId === currentTabId)) return 99 | 100 | setTabs((tabs) => { 101 | const newTabs = [...tabs] 102 | const previousTabIndex = newTabs.findIndex((tab) => tab.id === previousTabId) 103 | newTabs[previousTabIndex] = { ...newTabs[previousTabIndex], doc: editorView.state.doc.toString() } 104 | 105 | return newTabs 106 | }) 107 | }, [editorView, previousTabId, currentTabId, currentTab, setTabs]) 108 | 109 | useEffect(() => { 110 | const onBeforeUnload = () => { 111 | if (!editorView) return 112 | 113 | setTabs((tabs) => { 114 | const newTabs = [...tabs] 115 | const currentTabIndex = newTabs.findIndex(tab => tab.id === currentTabId) 116 | newTabs[currentTabIndex] = { ...newTabs[currentTabIndex], doc: editorView.state.doc.toString() } 117 | 118 | return newTabs 119 | }) 120 | setEditorView(null) 121 | } 122 | 123 | window.addEventListener('beforeunload', onBeforeUnload) 124 | 125 | return () => window.removeEventListener('beforeunload', onBeforeUnload) 126 | }, [editorView, setTabs, currentTabId]) 127 | 128 | useEffect(() => { 129 | if (!editorView || !currentTab) return 130 | 131 | editorView.dispatch({ 132 | changes: { 133 | from: 0, 134 | to: editorView.state.doc.length, 135 | insert: currentTab.doc, 136 | }, 137 | }) 138 | }, [editorView, currentTabId]) 139 | 140 | useEffect(() => { 141 | refreshTypes() 142 | 143 | // no need to refresh types anymore 144 | if (!config.polling.enable || !editorView) return 145 | 146 | const refreshTypesTimeoutMs = config.polling.interval 147 | 148 | let refreshTypesTimeoutId = setTimeout(async function recursiveRefreshTypes() { 149 | await refreshTypes() 150 | // refresh ts linter diagnostics after types are refreshed 151 | editorView.dispatch(await setDiagnostics(editorView.state)) 152 | 153 | refreshTypesTimeoutId = setTimeout(recursiveRefreshTypes, refreshTypesTimeoutMs) 154 | }, refreshTypesTimeoutMs) 155 | 156 | return () => clearTimeout(refreshTypesTimeoutId) 157 | }, [editorView, refreshTypes]) 158 | 159 | return ( 160 | 161 | 162 | 163 | { 167 | if (!editorView) return 168 | 169 | const responseObjectValue = await transformAndRunQueries({ 170 | code: editorView.state.doc.toString(), 171 | trpcClient, 172 | }) 173 | 174 | setResponseValue(responseObjectValue) 175 | }} 176 | > 177 | 182 | 183 | 184 | 185 | 186 | 187 | setEditorView(editorView)} 190 | elementProps={{ className: 'bg-primary flex-1' }} 191 | /> 192 | 193 | 194 | 195 | 200 | 201 | 202 | ) 203 | } 204 | -------------------------------------------------------------------------------- /packages/typescript-extension/src/index.ts: -------------------------------------------------------------------------------- 1 | import { autocompletion, completeFromList, CompletionContext, CompletionResult } from '@codemirror/autocomplete' 2 | import { javascript } from '@codemirror/lang-javascript' 3 | import { Diagnostic, linter, setDiagnostics as cmSetDiagnostics } from '@codemirror/lint' 4 | import { EditorState, Extension, StateEffect, StateField, TransactionSpec } from '@codemirror/state' 5 | import { EditorView, hoverTooltip, Tooltip } from '@codemirror/view' 6 | import throttle from 'lodash/throttle' 7 | import { DiagnosticCategory, displayPartsToString, flattenDiagnosticMessageText } from 'typescript' 8 | import { onChangeCallback } from './change-callback' 9 | import { FileMap, TypescriptProject } from './project' 10 | 11 | export { TypescriptProject } 12 | export type { FileMap } 13 | 14 | /** 15 | * This file exports an extension that makes Typescript language services work. This includes: 16 | * 17 | * 1. A StateField that holds an instance of a `TypescriptProject` (used to communicate with tsserver) 18 | * 2. A StateField that stores ranges for lint diagostics (used to cancel hover tooltips if a lint diagnistic is also present at the position) 19 | * 3. A `javascript` extension, that provides syntax highlighting and other simple JS features. 20 | * 4. An `autocomplete` extension that provides tsserver-backed completions, powered by the `completionSource` function 21 | * 5. A `linter` extension that provides tsserver-backed type errors, powered by the `lintDiagnostics` function 22 | * 6. A `hoverTooltip` extension that provides tsserver-backed type information on hover, powered by the `hoverTooltip` function 23 | * 7. An `updateListener` (facet) extension, that ensures that the editor's view is kept in sync with tsserver's view of the file 24 | * 8. A StateEffect that lets a consumer inject custom types into the `TypescriptProject` 25 | * 26 | * The "correct" way to read this file is from bottom to top. 27 | */ 28 | 29 | /** 30 | * A State field that represents the Typescript project that is currently "open" in the EditorView 31 | */ 32 | const tsStateField = StateField.define({ 33 | create(state) { 34 | return new TypescriptProject(state.sliceDoc(0)) 35 | }, 36 | 37 | update(ts, transaction) { 38 | // For all transactions that run, this state field's value will only "change" if a `injectTypesEffect` StateEffect is attached to the transaction 39 | transaction.effects.forEach(e => { 40 | if (e.is(injectTypesEffect)) { 41 | ts.injectTypes(e.value) 42 | } 43 | }) 44 | 45 | return ts 46 | }, 47 | 48 | compare() { 49 | // There must never be two instances of this state field 50 | return true 51 | }, 52 | }) 53 | 54 | /** 55 | * A CompletionSource that returns completions to show at the current cursor position (via tsserver) 56 | */ 57 | const completionSource = async ( 58 | ctx: CompletionContext, 59 | ): Promise => { 60 | const { state, pos } = ctx 61 | const ts = state.field(tsStateField) 62 | 63 | try { 64 | const completions = (await ts.lang()).getCompletionsAtPosition( 65 | ts.entrypoint, 66 | pos, 67 | {}, 68 | ) 69 | if (!completions) { 70 | return null 71 | } 72 | 73 | return completeFromList( 74 | completions.entries.map((c) => ({ 75 | type: c.kind, 76 | label: c.name, 77 | boost: 1 / Number(c.sortText), 78 | })), 79 | )(ctx) 80 | } catch (e) { 81 | return null 82 | } 83 | } 84 | 85 | /** 86 | * A LintSource that returns lint diagnostics across the current editor view (via tsserver) 87 | */ 88 | const lintDiagnostics = async (state: EditorState): Promise => { 89 | const ts = state.field(tsStateField) 90 | const diagnostics = (await ts.lang()).getSemanticDiagnostics(ts.entrypoint) 91 | 92 | return diagnostics 93 | .filter(d => d.start !== undefined && d.length !== undefined) 94 | .map(d => { 95 | let severity: 'info' | 'warning' | 'error' = 'info' 96 | if (d.category === DiagnosticCategory.Error) { 97 | severity = 'error' 98 | } else if (d.category === DiagnosticCategory.Warning) { 99 | severity = 'warning' 100 | } 101 | 102 | return { 103 | from: d.start!, // `!` is fine because of the `.filter()` before the `.map()` 104 | to: d.start! + d.length!, // `!` is fine because of the `.filter()` before the `.map()` 105 | severity, 106 | message: flattenDiagnosticMessageText(d.messageText, '\n', 0), 107 | } 108 | }) 109 | } 110 | 111 | /** 112 | * A HoverTooltipSource that returns a Tooltip to show at a given cursor position (via tsserver) 113 | */ 114 | const hoverTooltipSource = async ( 115 | state: EditorState, 116 | pos: number, 117 | ): Promise => { 118 | const ts = state.field(tsStateField) 119 | 120 | const quickInfo = (await ts.lang()).getQuickInfoAtPosition( 121 | ts.entrypoint, 122 | pos, 123 | ) 124 | if (!quickInfo) { 125 | return null 126 | } 127 | 128 | return { 129 | pos, 130 | create() { 131 | const dom = document.createElement('div') 132 | dom.setAttribute('class', 'cm-quickinfo-tooltip') 133 | dom.textContent = displayPartsToString(quickInfo.displayParts) 134 | + (quickInfo.documentation?.length 135 | ? '\n' + displayPartsToString(quickInfo.documentation) 136 | : '') 137 | 138 | return { 139 | dom, 140 | } 141 | }, 142 | above: false, // HACK: This makes it so lint errors show up on TOP of this, so BOTH quickInfo and lint tooltips don't show up at the same time 143 | } 144 | } 145 | 146 | /** 147 | * A TransactionSpec that can be dispatched to add new types to the underlying tsserver instance 148 | */ 149 | const injectTypesEffect = StateEffect.define() 150 | export function injectTypes(types: FileMap): TransactionSpec { 151 | return { 152 | effects: [injectTypesEffect.of(types)], 153 | } 154 | } 155 | 156 | /** 157 | * A TransactionSpec that can be dispatched to force re-calculation of lint diagnostics 158 | */ 159 | export async function setDiagnostics( 160 | state: EditorState, 161 | ): Promise { 162 | const diagnostics = await lintDiagnostics(state) 163 | return cmSetDiagnostics(state, diagnostics) 164 | } 165 | 166 | /** 167 | * A (throttled) function that updates the view of the currently open "file" on TSServer 168 | */ 169 | const updateTSFileThrottled = throttle((code: string, view: EditorView) => { 170 | const ts = view.state.field(tsStateField) 171 | 172 | // Don't `await` because we do not want to block 173 | ts.env().then(env => env.updateFile(ts.entrypoint, code || ' ')) // tsserver deletes the file if the text content is empty; we can't let that happen 174 | }, 100) 175 | 176 | // Export a function that will build & return an Extension 177 | export function typescript(): Extension { 178 | return [ 179 | tsStateField, 180 | javascript({ typescript: true, jsx: false }), 181 | autocompletion({ 182 | activateOnTyping: true, 183 | maxRenderedOptions: 30, 184 | override: [completionSource], 185 | }), 186 | linter(view => lintDiagnostics(view.state)), 187 | hoverTooltip((view, pos) => hoverTooltipSource(view.state, pos), { 188 | hideOnChange: true, 189 | }), 190 | EditorView.updateListener.of(({ view, docChanged }) => { 191 | // We're not doing this in the `onChangeCallback` extension because we do not want TS file updates to be debounced (we want them throttled) 192 | 193 | if (docChanged) { 194 | // Update tsserver's view of this file 195 | updateTSFileThrottled(view.state.sliceDoc(0), view) 196 | } 197 | }), 198 | onChangeCallback(async (_code, view) => { 199 | // No need to debounce here because this callback is already debounced 200 | 201 | // Re-compute lint diagnostics via tsserver 202 | view.dispatch(await setDiagnostics(view.state)) 203 | }), 204 | ] 205 | } 206 | 207 | export { tsTheme } from './theme' 208 | -------------------------------------------------------------------------------- /packages/typescript-extension/src/theme.ts: -------------------------------------------------------------------------------- 1 | import { Extension } from '@codemirror/state' 2 | import { EditorView } from '@codemirror/view' 3 | 4 | export const tsTheme: Extension = [ 5 | EditorView.theme({ 6 | // Autocomplete 7 | '.cm-tooltip.cm-tooltip-autocomplete > ul': { 8 | minWidth: '250px', 9 | }, 10 | '.cm-tooltip.cm-tooltip-autocomplete > ul > li': { 11 | display: 'flex', 12 | alignItems: 'center', 13 | padding: '2px', 14 | }, 15 | '.cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected]': {}, 16 | '.cm-completionLabel': {}, // Unmatched text 17 | '.cm-completionMatchedText': { 18 | textDecoration: 'none', 19 | fontWeight: 600, 20 | color: '#00B4D4', 21 | }, 22 | '.cm-completionDetail': { 23 | fontStyle: 'initial', 24 | color: '#ABABAB', 25 | marginLeft: '2rem', 26 | }, // Text to the right of tooltip 27 | '.cm-completionInfo': {}, // "Additional" text that shows up in a panel on the right of the tolltip 28 | 29 | '.cm-completionIcon': { 30 | padding: '0', 31 | marginRight: '4px', 32 | width: '16px', 33 | height: '16px', 34 | backgroundRepeat: 'no-repeat', 35 | // 'snippet' icon from https://code.visualstudio.com/docs/editor/intellisense#_types-of-completions 36 | // acts as a fallback icon 37 | backgroundImage: 38 | "url(\"data:image/svg+xml,%3Csvg viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M2.5 1L2 1.5V13H3V2H14V13H15V1.5L14.5 1H2.5ZM2 15V14H3V15H2ZM5 14.0001H4V15.0001H5V14.0001ZM6 14.0001H7V15.0001H6V14.0001ZM9 14.0001H8V15.0001H9V14.0001ZM10 14.0001H11V15.0001H10V14.0001ZM15 15.0001V14.0001H14V15.0001H15ZM12 14.0001H13V15.0001H12V14.0001Z' fill='%23424242' /%3E%3C/svg%3E\")", 39 | '&:after': { 40 | content: "' '", 41 | }, 42 | 43 | '&.cm-completionIcon-var, &.cm-completionIcon-let, &.cm-completionIcon-const': { 44 | // 'variable' icon from https://code.visualstudio.com/docs/editor/intellisense#_types-of-completions 45 | backgroundImage: 46 | "url(\"data:image/svg+xml,%3Csvg viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M1 4C1 3.72386 1.21614 3.5 1.48276 3.5H3.89655C4.16317 3.5 4.37931 3.72386 4.37931 4C4.37931 4.27614 4.16317 4.5 3.89655 4.5H1.96552V11.5H3.89655C4.16317 11.5 4.37931 11.7239 4.37931 12C4.37931 12.2761 4.16317 12.5 3.89655 12.5H1.48276C1.21614 12.5 1 12.2761 1 12V4ZM8.97252 4.57125C8.83764 4.48744 8.6718 4.47693 8.52807 4.54309L4.18324 6.5431C4.04909 6.60485 3.95163 6.72512 3.91384 6.86732C3.90234 6.91044 3.89668 6.95446 3.89655 6.9982L3.89655 7V9.5C3.89655 9.67563 3.98552 9.83839 4.13093 9.92875L6.53533 11.4229C6.60993 11.4718 6.69836 11.5001 6.79318 11.5001C6.86852 11.5001 6.93983 11.4823 7.00337 11.4504L11.334 9.45691C11.5083 9.37666 11.6207 9.1976 11.6207 9V6.51396C11.6227 6.44159 11.6094 6.36764 11.5792 6.29706C11.5404 6.20686 11.4791 6.13438 11.4052 6.0836C11.399 6.07935 11.3927 6.07523 11.3863 6.07125L8.97252 4.57125ZM10.0932 6.43389L8.69092 5.56245L5.42408 7.06623L6.8264 7.93767L10.0932 6.43389ZM4.86207 7.88317V9.21691L6.31042 10.117V8.78322L4.86207 7.88317ZM7.27594 10.2306L10.6552 8.67506V7.26954L7.27594 8.82506V10.2306ZM14.5172 12.5C14.7839 12.5 15 12.2761 15 12L15 4C15 3.72386 14.7839 3.5 14.5172 3.5H12.1034C11.8368 3.5 11.6207 3.72386 11.6207 4C11.6207 4.27614 11.8368 4.5 12.1034 4.5L14.0345 4.5L14.0345 11.5L12.1034 11.5C11.8368 11.5 11.6207 11.7239 11.6207 12C11.6207 12.2761 11.8368 12.5 12.1034 12.5H14.5172Z' fill='%23805AD5' /%3E%3C/svg%3E\")", 47 | '&:after': { 48 | content: "' '", 49 | }, 50 | }, 51 | 52 | '&.cm-completionIcon-function, &.cm-completionIcon-method': { 53 | // 'method' icon from https://code.visualstudio.com/docs/editor/intellisense#_types-of-completions 54 | backgroundImage: 55 | "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8 1C7.7033 1 7.41182 1.08255 7.15479 1.23938L7.15385 1.23995L2.84793 3.84401L2.84615 3.8451C2.58915 4.00214 2.37568 4.22795 2.22716 4.49987C2.07864 4.77179 2.0003 5.08077 2 5.39485V10.6056C2.0003 10.9197 2.07864 11.2282 2.22716 11.5001C2.37568 11.772 2.58915 11.9979 2.84615 12.1549L2.84794 12.156L7.15385 14.76L7.15466 14.7605C7.41173 14.9174 7.70325 15 8 15C8.29675 15 8.58828 14.9174 8.84534 14.7605L8.84615 14.76L13.1521 12.156L13.1538 12.1549C13.4109 11.9979 13.6243 11.772 13.7728 11.5001C13.9214 11.2282 13.9997 10.9192 14 10.6051V5.39435C13.9997 5.08027 13.9214 4.77179 13.7728 4.49987C13.6243 4.22795 13.4109 4.00214 13.1538 3.8451L8.84615 1.23995L8.84521 1.23938C8.58818 1.08255 8.2967 1 8 1ZM7.61538 2.086C7.73232 2.01455 7.86497 1.97693 8 1.97693C8.13503 1.97693 8.26768 2.01455 8.38461 2.086L12.6931 4.69163C12.6992 4.69534 12.7052 4.69914 12.7111 4.70302L7.99996 7.42924L3.28893 4.70303C3.29512 4.69898 3.30138 4.69502 3.30769 4.69115L7.61361 2.08709L7.61538 2.086ZM2.92308 5.64668V10.6049C2.92325 10.7476 2.95886 10.8877 3.02633 11.0112C3.0937 11.1346 3.19047 11.237 3.30698 11.3084L7.53857 13.8675V8.31761L2.92308 5.64668ZM8.46165 13.8674L12.6923 11.3089C12.8088 11.2375 12.9063 11.1346 12.9737 11.0112C13.0412 10.8876 13.0768 10.7474 13.0769 10.6046V5.6467L8.46165 8.31743V13.8674Z' fill='%23DD6B20' /%3E%3C/svg%3E\")", 56 | '&:after': { 57 | content: "' '", 58 | }, 59 | }, 60 | '&.cm-completionIcon-property, &.cm-completionIcon-getter': { 61 | // 'field' icon from https://code.visualstudio.com/docs/editor/intellisense#_types-of-completions 62 | backgroundImage: 63 | "url(\"data:image/svg+xml,%3Csvg viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M1 6.39443L1.55279 5.5L8.55279 2H9.44721L14.4472 4.5L15 5.39443V9.89443L14.4472 10.7889L7.44721 14.2889H6.55279L1.55279 11.7889L1 10.8944V6.39443ZM6.5 13.1444L2 10.8944V7.17094L6.5 9.21639V13.1444ZM7.5 13.1444L14 9.89443V6.17954L7.5 9.21287V13.1444ZM9 2.89443L2.33728 6.22579L6.99725 8.34396L13.6706 5.22973L9 2.89443Z' fill='%23805AD5' /%3E%3C/svg%3E\")", 64 | '&:after': { 65 | content: "' '", 66 | }, 67 | }, 68 | 69 | '&.cm-completionIcon-enum, &.cm-completionIcon-enum-member, &.cm-completionIcon-string': { 70 | // 'constant' icon from https://code.visualstudio.com/docs/editor/intellisense#_types-of-completions 71 | backgroundImage: 72 | "url(\"data:image/svg+xml,%3Csvg viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M4.00024 6H12.0002V7H4.00024V6ZM12.0002 9H4.00024V10H12.0002V9Z' fill='%23424242' /%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M1.00024 4L2.00024 3H14.0002L15.0002 4V12L14.0002 13H2.00024L1.00024 12V4ZM2.00024 4V12H14.0002V4H2.00024Z' fill='%23424242' /%3E%3C/svg%3E\")", 73 | '&:after': { 74 | content: "' '", 75 | }, 76 | }, 77 | '&.cm-completionIcon-keyword': { 78 | // 'keyword' icon from https://code.visualstudio.com/docs/editor/intellisense#_types-of-completions 79 | backgroundImage: 80 | "url(\"data:image/svg+xml,%3Csvg viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M15 4H10V3H15V4ZM14 7H12V8H14V7ZM10 7H1V8H10V7ZM12 13H1V14H12V13ZM7 10H1V11H7V10ZM15 10H10V11H15V10ZM8 2V5H1V2H8ZM7 3H2V4H7V3Z' fill='%23424242' /%3E%3C/svg%3E\")", 81 | '&:after': { 82 | content: "' '", 83 | }, 84 | }, 85 | '&.cm-completionIcon-class, &.cm-completionIcon-interface, &.cm-completionIcon-alias': { 86 | // 'class' icon from https://code.visualstudio.com/docs/editor/intellisense#_types-of-completions 87 | backgroundImage: 88 | "url(\"data:image/svg+xml,%3Csvg viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M3.35356 6.64642L2.06066 5.35353L5.35356 2.06065L6.64645 3.35354L3.35356 6.64642ZM5 1L1 4.99998V5.70708L3 7.70707H3.70711L4.85355 6.56063V12.3535L5.35355 12.8535H10.0097V13.3741L11.343 14.7074H12.0501L14.7168 12.0407V11.3336L13.3835 10.0003H12.6763L10.8231 11.8535H5.85355V7.89355H10.0097V8.37401L11.343 9.70734H12.0501L14.7168 7.04068V6.33357L13.3835 5.00024H12.6763L10.863 6.81356H5.85355V5.56064L7.70711 3.70709V2.99999L5.70711 1H5ZM11.0703 8.02046L11.6966 8.64668L13.6561 6.68713L13.0299 6.0609L11.0703 8.02046ZM11.0703 13.0205L11.6966 13.6467L13.6561 11.6872L13.0299 11.061L11.0703 13.0205Z' fill='%23DD6B20' /%3E%3C/svg%3E\")", 89 | '&:after': { 90 | content: "' '", 91 | }, 92 | }, 93 | }, 94 | 95 | // Diagnostics (Lint issues) & Quickinfo (Hover tooltips) 96 | '.cm-diagnostic, .cm-quickinfo-tooltip': { 97 | padding: '0.5rem', 98 | }, 99 | }), 100 | ] 101 | --------------------------------------------------------------------------------
{config.trpcApiEndpoint}
47 | {def.type} 48 |
{tab.name}
Query builder