diff --git a/discord/README.md b/discord/README.md new file mode 100644 index 0000000000000000000000000000000000000000..82265e8f2159da431311275b12d9adc3cff62f89 --- /dev/null +++ b/discord/README.md @@ -0,0 +1,6 @@ +- Express server +- Vanilla TRPCClient in node + +--- + +Created by [@alexdotjs](https://twitter.com/alexdotjs). diff --git a/discord/package.json b/discord/package.json new file mode 100644 index 0000000000000000000000000000000000000000..4cd1c47f6267eab9dca68f420eddcd04047f1ffa --- /dev/null +++ b/discord/package.json @@ -0,0 +1,41 @@ +{ + "name": "@examples/express-server", + "version": "10.12.0", + "private": true, + "scripts": { + "dev:server": "tsx watch src/server", + "dev:client": "wait-port 2021 && tsx watch src/client", + "dev": "run-p dev:* --print-label", + "build": "tsc", + "lint": "eslint --ext \".js,.ts,.tsx\" --report-unused-disable-directives src", + "start": "pnpm dev", + "test-dev": "start-server-and-test 'tsx src/server' 2021 'tsx src/client'", + "test-start": "start-server-and-test 'node dist/server' 2021 'node dist/client'" + }, + "dependencies": { + "@trpc/client": "^10.12.0", + "@trpc/react-query": "^10.12.0", + "@trpc/server": "^10.12.0", + "@types/node-fetch": "^2.5.11", + "express": "^4.17.1", + "node-fetch": "^2.6.1", + "zod": "^3.0.0" + }, + "alias": { + "scheduler/tracing": "../../node_modules/scheduler/tracing-profiling" + }, + "devDependencies": { + "@types/express": "^4.17.12", + "@types/node": "^18.7.20", + "@types/react": "^18.0.9", + "eslint": "^8.30.0", + "npm-run-all": "^4.1.5", + "start-server-and-test": "^1.12.0", + "tsx": "^3.9.0", + "typescript": "^4.8.3", + "wait-port": "^1.0.1" + }, + "publishConfig": { + "access": "restricted" + } +} diff --git a/discord/src/server.ts b/discord/src/server.ts new file mode 100644 index 0000000000000000000000000000000000000000..1a484b5113d4fb016b94fa4362e63145c48eb996 --- /dev/null +++ b/discord/src/server.ts @@ -0,0 +1,133 @@ +import { TRPCError, inferAsyncReturnType, initTRPC } from '@trpc/server'; +import * as trpcExpress from '@trpc/server/adapters/express'; +import { EventEmitter } from 'events'; +import express from 'express'; +import { z } from 'zod'; + +const createContext = ({ + req, + res, +}: trpcExpress.CreateExpressContextOptions) => { + const getUser = () => { + if (req.headers.authorization !== 'secret') { + return null; + } + return { + name: 'alex', + }; + }; + + return { + req, + res, + user: getUser(), + }; +}; +type Context = inferAsyncReturnType<typeof createContext>; + +const t = initTRPC.context<Context>().create(); + +const router = t.router; +const publicProcedure = t.procedure; + +// --------- create procedures etc + +let id = 0; + +const ee = new EventEmitter(); +const db = { + posts: [ + { + id: ++id, + title: 'hello', + }, + ], + messages: [createMessage('initial message')], +}; +function createMessage(text: string) { + const msg = { + id: ++id, + text, + createdAt: Date.now(), + updatedAt: Date.now(), + }; + ee.emit('newMessage', msg); + return msg; +} + +const postRouter = router({ + createPost: t.procedure + .input(z.object({ title: z.string() })) + .mutation(({ input }) => { + const post = { + id: ++id, + ...input, + }; + db.posts.push(post); + return post; + }), + listPosts: publicProcedure.query(() => db.posts), +}); + +const messageRouter = router({ + addMessage: publicProcedure.input(z.string()).mutation(({ input }) => { + const msg = createMessage(input); + db.messages.push(msg); + + return msg; + }), + listMessages: publicProcedure.query(() => db.messages), +}); + +// root router to call +const appRouter = router({ + // merge predefined routers + post: postRouter, + message: messageRouter, + // or individual procedures + hello: publicProcedure.input(z.string().nullish()).query(({ input, ctx }) => { + return `hello ${input ?? ctx.user?.name ?? 'world'}`; + }), + // or inline a router + admin: router({ + secret: publicProcedure.query(({ ctx }) => { + if (!ctx.user) { + throw new TRPCError({ code: 'UNAUTHORIZED' }); + } + if (ctx.user?.name !== 'alex') { + throw new TRPCError({ code: 'FORBIDDEN' }); + } + return { + secret: 'sauce', + }; + }), + }), +}); + +export type AppRouter = typeof appRouter; + +async function main() { + // express implementation + const app = express(); + + app.use((req, _res, next) => { + // request logger + console.log('⬅️ ', req.method, req.path, req.body ?? req.query); + + next(); + }); + + app.use( + '/trpc', + trpcExpress.createExpressMiddleware({ + router: appRouter, + createContext, + }), + ); + app.get('/', (_req, res) => res.send('hello')); + app.listen(2021, () => { + console.log('listening on port 2021'); + }); +} + +main(); diff --git a/discord/tsconfig.json b/discord/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..428f2bb550af9de9d5161437382446efe95cbcf6 --- /dev/null +++ b/discord/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "lib": ["es5", "es6", "es7", "esnext", "dom"], + "target": "es5", + "module": "commonjs", + "declaration": true, + // "moduleResolution": "node", + "outDir": "./dist", + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "esModuleInterop": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "sourceMap": true, + "resolveJsonModule": true, + /* Strict Type-Checking Options */ + "strict": true /* Enable all strict type-checking options. */, + "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, + "strictNullChecks": true /* Enable strict null checks. */, + "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, + "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, + // "strictPropertyInitialization": false, + /* Additional Checks */ + "noUnusedLocals": true /* Report errors on unused locals. */, + "noUnusedParameters": true /* Report errors on unused parameters. */, + "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, + "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */ + }, + "include": ["src"], + "exclude": [ + "node_modules" + // "**/__tests__/" + ] +}