trpc-uwebsockets 0.8.3 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,6 +15,7 @@ Import needed packages
15
15
  ```typescript
16
16
  import { App } from 'uWebSockets.js';
17
17
  import * as trpc from '@trpc/server';
18
+ import z from 'zod';
18
19
  ```
19
20
 
20
21
  Define tRPC context and router
@@ -54,7 +55,7 @@ const router = trpc.router<Context>().query('hello', {
54
55
  });
55
56
  ```
56
57
 
57
- Initialize uWebsockets server and attach tTRP router
58
+ Initialize uWebsockets server and attach tRPC router
58
59
 
59
60
  ```typescript
60
61
  const app = App();
@@ -64,6 +65,12 @@ createUWebSocketsHandler(app, '/trpc', {
64
65
  createContext,
65
66
  });
66
67
 
68
+ /* dont crash on unknown request */
69
+ app.any('/*', (res) => {
70
+ res.writeStatus('404 NOT FOUND');
71
+ res.end();
72
+ });
73
+
67
74
  app.listen('0.0.0.0', 8000, () => {
68
75
  console.log('Server listening on http://localhost:8000');
69
76
  });
@@ -71,6 +78,34 @@ app.listen('0.0.0.0', 8000, () => {
71
78
 
72
79
  # API
73
80
 
81
+ Create uWebSockets handler options
82
+
83
+ ```typescript
84
+ function createUWebSocketsHandler<TRouter extends AnyRouter>(
85
+ /* instance of the uWebSockets server */
86
+ uWsApp: TemplatedApp,
87
+ /* Path to trpc without trailing slash (ex: "/trpc") */
88
+ pathPrefix: string,
89
+ /* Handler options */
90
+ opts: UWebSocketsCreateHandlerOptions<TRouter>
91
+ );
92
+ ```
93
+
94
+ Handler options
95
+
96
+ ```typescript
97
+ type UWebSocketsCreateHandlerOptions<TRouter extends AnyRouter> = {
98
+ /* trpc router */
99
+ router: TRouter;
100
+ /* optional create context */
101
+ createContext?: (
102
+ opts: UWebSocketsCreateContextOptions
103
+ ) => Promise<inferRouterContext<TRouter>> | inferRouterContext<TRouter>;
104
+ /* optional pre-request handler. Useful for dealing with CORS */
105
+ onRequest?: (res: HttpResponse, req: HttpRequest) => void;
106
+ };
107
+ ```
108
+
74
109
  Create context options
75
110
 
76
111
  ```typescript
@@ -81,6 +116,13 @@ type UWebSocketsCreateContextOptions = {
81
116
  method: 'POST' | 'GET';
82
117
  query: URLSearchParams;
83
118
  path: string;
119
+ getCookies: (opts?: CookieParseOptions) => Record<string, string>;
120
+ };
121
+ /* */
122
+ res: {
123
+ setStatus(status: number): void;
124
+ setHeader(key: string, value: string): void;
125
+ setCookie(key: string, value: string, opts?: CookieSerializeOptions): void;
84
126
  };
85
127
  /* instance of the uWebSockets server */
86
128
  uWs: TemplatedApp;
@@ -98,3 +140,8 @@ or
98
140
  ```bash
99
141
  yarn test:watch
100
142
  ```
143
+
144
+ # Todo
145
+
146
+ - [ ] Expose batching options (like express adapter)
147
+ - [ ] Implement onError handling (like express adapter)
package/dist/index.js CHANGED
@@ -13,16 +13,19 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (functi
13
13
  var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
16
19
  Object.defineProperty(exports, "__esModule", { value: true });
17
20
  exports.createUWebSocketsHandler = void 0;
18
21
  const server_1 = require("@trpc/server");
19
22
  const utils_1 = require("./utils");
23
+ const cookie_1 = __importDefault(require("cookie"));
20
24
  __exportStar(require("./types"), exports);
21
25
  /**
22
- *
23
26
  * @param uWsApp uWebsockets server instance
24
- * @param pathPrefix The path to endpoint without trailing slash (ex: "/trpc")
25
- * @param opts router and createContext functions
27
+ * @param pathPrefix The path to trpc without trailing slash (ex: "/trpc")
28
+ * @param opts handler options
26
29
  */
27
30
  function createUWebSocketsHandler(uWsApp, pathPrefix, opts) {
28
31
  const prefixTrimLength = pathPrefix.length + 1; // remove /* from url
@@ -42,64 +45,96 @@ function createUWebSocketsHandler(uWsApp, pathPrefix, opts) {
42
45
  });
43
46
  // new request object needs to be created, because socket
44
47
  // can only be accessed synchronously, after await it cannot be accessed
45
- const requestObj = {
48
+ const request = {
46
49
  headers,
47
50
  method,
48
51
  query,
49
52
  path,
53
+ getCookies: (0, utils_1.getCookieFn)(headers),
54
+ };
55
+ const resOverride = {
56
+ headers: new Map(),
57
+ cookies: [],
58
+ status: 0,
59
+ };
60
+ const response = {
61
+ setCookie: (name, value, opts) => {
62
+ const serialized = cookie_1.default.serialize(name, value, opts); //.substring(12); //remove the "Set-Cookie: "
63
+ resOverride.cookies.push(serialized);
64
+ },
65
+ setStatus: (status) => {
66
+ resOverride.status = status;
67
+ },
68
+ setHeader: (key, value) => {
69
+ resOverride.headers.set(key, value);
70
+ },
50
71
  };
51
72
  const bodyResult = await (0, utils_1.readPostBody)(method, res);
52
73
  // req is no longer available!
53
74
  const createContext = async function _() {
54
75
  //res could be proxied here
55
76
  return await opts.createContext?.({
56
- // res,
57
77
  uWs: uWsApp,
58
- req: requestObj,
78
+ req: request,
79
+ res: response,
59
80
  });
60
81
  };
61
- const fakeReqObject = {
82
+ const internalReqObj = {
62
83
  method,
63
84
  headers,
64
85
  query,
65
86
  body: bodyResult.ok ? bodyResult.data : undefined,
66
87
  };
67
88
  // TODO batching, onError options need implementation.
68
- // responseMeta is not applicable?
69
89
  const result = await (0, server_1.resolveHTTPResponse)({
70
90
  path,
71
91
  createContext,
72
92
  router: opts.router,
73
- req: fakeReqObject,
93
+ req: internalReqObj,
74
94
  error: bodyResult.ok ? null : bodyResult.error,
75
95
  });
76
- if ('status' in result) {
77
- res.writeStatus(result.status.toString()); //temp
78
- }
79
- else {
80
- // assume something went bad, should never happen?
81
- // there is no way to know from res object that something was send to the socket
82
- // can proxy it to detect res calls during createContext and resolve
83
- // then will need to exit from here
84
- res.cork(() => {
85
- res.writeStatus('500 INTERNAL SERVER ERROR');
86
- res.end();
87
- });
88
- return;
89
- }
90
- for (const [key, value] of Object.entries(result.headers ?? {})) {
91
- if (typeof value === 'undefined') {
92
- continue;
96
+ // user returned already, do nothing
97
+ res.cork(() => {
98
+ if (resOverride.status != 0) {
99
+ res.writeStatus(resOverride.status.toString());
93
100
  }
94
- // FIX not sure why it could be an array. This code path is not tested
95
- if (Array.isArray(value))
96
- value.forEach((header) => {
97
- res.writeHeader(key, header);
98
- });
99
- else
101
+ else if ('status' in result) {
102
+ res.writeStatus(result.status.toString());
103
+ }
104
+ else {
105
+ // assume something went bad, should never happen?
106
+ throw new Error('No status to send');
107
+ // res.writeStatus('500 INTERNAL SERVER ERROR');
108
+ // res.end();
109
+ // return;
110
+ }
111
+ if (opts.onRequest)
112
+ opts.onRequest(res, req);
113
+ //send all cookies
114
+ resOverride.cookies.forEach((value) => {
115
+ res.writeHeader('Set-Cookie', value);
116
+ });
117
+ resOverride.headers.forEach((value, key) => {
100
118
  res.writeHeader(key, value);
101
- }
102
- res.cork(() => {
119
+ });
120
+ for (const [key, value] of Object.entries(result.headers ?? {})) {
121
+ if (typeof value === 'undefined') {
122
+ continue;
123
+ }
124
+ // make sure to never override user defined headers
125
+ // cookies are an exception
126
+ if (resOverride.headers.has(key))
127
+ continue;
128
+ // not sure why it could be an array. This code path is not tested
129
+ // maybe its duplicates for the same key? like multiple "Set-Cookie"
130
+ if (Array.isArray(value))
131
+ value.forEach((header) => {
132
+ res.writeHeader(key, header);
133
+ });
134
+ else
135
+ res.writeHeader(key, value);
136
+ }
137
+ //now send user headers
103
138
  if (result.body)
104
139
  res.write(result.body);
105
140
  res.end();
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/utils.js CHANGED
@@ -1,7 +1,20 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.readPostBody = void 0;
6
+ exports.readPostBody = exports.getCookieFn = void 0;
4
7
  const server_1 = require("@trpc/server");
8
+ const cookie_1 = __importDefault(require("cookie"));
9
+ /*
10
+ cookie: 'cookie1=abc; cookie2=d.e'
11
+ */
12
+ const getCookieFn = (headers) => (opts) => {
13
+ if (!('cookie' in headers))
14
+ return {};
15
+ return cookie_1.default.parse(headers.cookie, opts);
16
+ };
17
+ exports.getCookieFn = getCookieFn;
5
18
  function readPostBody(method, res) {
6
19
  return new Promise((resolve) => {
7
20
  if (method == 'GET') {
package/dist/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;AAAA,yCAAyC;AAGzC,SAAgB,YAAY,CAAC,MAAc,EAAE,GAAiB;IAC5D,OAAO,IAAI,OAAO,CAEhB,CAAC,OAAO,EAAE,EAAE;QACZ,IAAI,MAAM,IAAI,KAAK,EAAE;YACnB,yBAAyB;YACzB,OAAO,CAAC;gBACN,EAAE,EAAE,IAAI;gBACR,IAAI,EAAE,SAAS;aAChB,CAAC,CAAC;SACJ;QAED,IAAI,MAAc,CAAC;QACnB,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE;YACxB,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAE9B,IAAI,MAAM,EAAE;gBACV,IAAI,MAAM,EAAE;oBACV,sCAAsC;oBACtC,OAAO,CAAC;wBACN,EAAE,EAAE,IAAI;wBACR,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE,kBAAkB;qBAC5C,CAAC,CAAC;iBACJ;qBAAM;oBACL,mCAAmC;oBACnC,OAAO,CAAC;wBACN,EAAE,EAAE,IAAI;wBACR,IAAI,EAAE,KAAK,CAAC,QAAQ,EAAE;qBACvB,CAAC,CAAC;iBACJ;aACF;iBAAM;gBACL,IAAI,MAAM,EAAE;oBACV,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;iBACzC;qBAAM;oBACL,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;iBACjC;aACF;QACH,CAAC,CAAC,CAAC;QAEH,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;YACjB,OAAO,CAAC;gBACN,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,IAAI,kBAAS,CAAC,EAAE,IAAI,EAAE,uBAAuB,EAAE,CAAC;aACxD,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AA9CD,oCA8CC"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;;;;AAAA,yCAAyC;AAEzC,oDAAoD;AACpD;;EAEE;AAEK,MAAM,WAAW,GACtB,CAAC,OAA+B,EAAE,EAAE,CAAC,CAAC,IAAyB,EAAE,EAAE;IACjE,IAAI,CAAC,CAAC,QAAQ,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,CAAC;IAEtC,OAAO,gBAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAC5C,CAAC,CAAC;AALS,QAAA,WAAW,eAKpB;AAEJ,SAAgB,YAAY,CAAC,MAAc,EAAE,GAAiB;IAC5D,OAAO,IAAI,OAAO,CAEhB,CAAC,OAAO,EAAE,EAAE;QACZ,IAAI,MAAM,IAAI,KAAK,EAAE;YACnB,yBAAyB;YACzB,OAAO,CAAC;gBACN,EAAE,EAAE,IAAI;gBACR,IAAI,EAAE,SAAS;aAChB,CAAC,CAAC;SACJ;QAED,IAAI,MAAc,CAAC;QACnB,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE;YACxB,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAE9B,IAAI,MAAM,EAAE;gBACV,IAAI,MAAM,EAAE;oBACV,sCAAsC;oBACtC,OAAO,CAAC;wBACN,EAAE,EAAE,IAAI;wBACR,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE,kBAAkB;qBAC5C,CAAC,CAAC;iBACJ;qBAAM;oBACL,mCAAmC;oBACnC,OAAO,CAAC;wBACN,EAAE,EAAE,IAAI;wBACR,IAAI,EAAE,KAAK,CAAC,QAAQ,EAAE;qBACvB,CAAC,CAAC;iBACJ;aACF;iBAAM;gBACL,IAAI,MAAM,EAAE;oBACV,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;iBACzC;qBAAM;oBACL,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;iBACjC;aACF;QACH,CAAC,CAAC,CAAC;QAEH,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;YACjB,OAAO,CAAC;gBACN,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,IAAI,kBAAS,CAAC,EAAE,IAAI,EAAE,uBAAuB,EAAE,CAAC;aACxD,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AA9CD,oCA8CC"}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "private": false,
3
3
  "name": "trpc-uwebsockets",
4
4
  "description": "tRPC adapter for uWebSockets.js server",
5
- "version": "0.8.3",
5
+ "version": "0.9.2",
6
6
  "main": "./dist/index.js",
7
7
  "type": "commonjs",
8
8
  "types": "./types/index.d.ts",
@@ -25,28 +25,30 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@trpc/server": "^9.26.2",
28
+ "cookie": "^0.5.0",
28
29
  "uWebSockets.js": "uNetworking/uWebSockets.js#v20.10.0"
29
30
  },
30
31
  "devDependencies": {
31
- "@types/jest": "^28.1.6",
32
- "babel-jest": "^28.1.3",
33
- "jest": "^28.1.3",
34
- "node-fetch": "2",
35
32
  "@rollup/plugin-typescript": "^8.3.3",
36
33
  "@swc/core": "^1.2.215",
37
34
  "@swc/jest": "^0.2.21",
38
35
  "@trpc/client": "^9.26.2",
36
+ "@types/cookie": "^0.5.1",
37
+ "@types/jest": "^28.1.6",
39
38
  "@types/node": "^18.0.6",
40
39
  "@types/webrtc": "^0.0.32",
41
40
  "@typescript-eslint/eslint-plugin": "^5.30.6",
42
41
  "@typescript-eslint/parser": "^5.30.6",
43
42
  "abort-controller": "^3.0.0",
43
+ "babel-jest": "^28.1.3",
44
44
  "eslint": "^8.19.0",
45
45
  "eslint-config-prettier": "^8.5.0",
46
46
  "eslint-plugin-jest": "^26.6.0",
47
47
  "eslint-plugin-prettier": "^4.2.1",
48
+ "jest": "^28.1.3",
48
49
  "jest-environment-jsdom": "^27.1.0",
49
50
  "jest-environment-node": "^28.1.3",
51
+ "node-fetch": "2",
50
52
  "prettier": "^2.7.1",
51
53
  "rimraf": "^3.0.2",
52
54
  "rollup": "^2.77.0",
package/src/index.ts CHANGED
@@ -4,28 +4,30 @@ import {
4
4
  resolveHTTPResponse,
5
5
  } from '@trpc/server';
6
6
  import { HTTPRequest } from '@trpc/server/dist/declarations/src/http/internals/types';
7
- import type * as uWs from 'uWebSockets.js';
7
+ import { CookieSerializeOptions } from 'cookie';
8
+ import type { HttpRequest, HttpResponse, TemplatedApp } from 'uWebSockets.js';
8
9
  import {
9
- UWebSocketsRegisterEndpointOptions,
10
+ UWebSocketsCreateHandlerOptions,
10
11
  UWebSocketsRequestObject,
12
+ UWebSocketsResponseObject,
11
13
  } from './types';
12
- import { readPostBody } from './utils';
14
+ import { getCookieFn, readPostBody } from './utils';
15
+ import cookie from 'cookie';
13
16
  export * from './types';
14
17
 
15
18
  /**
16
- *
17
19
  * @param uWsApp uWebsockets server instance
18
- * @param pathPrefix The path to endpoint without trailing slash (ex: "/trpc")
19
- * @param opts router and createContext functions
20
+ * @param pathPrefix The path to trpc without trailing slash (ex: "/trpc")
21
+ * @param opts handler options
20
22
  */
21
23
  export function createUWebSocketsHandler<TRouter extends AnyRouter>(
22
- uWsApp: uWs.TemplatedApp,
24
+ uWsApp: TemplatedApp,
23
25
  pathPrefix: string,
24
- opts: UWebSocketsRegisterEndpointOptions<TRouter>
26
+ opts: UWebSocketsCreateHandlerOptions<TRouter>
25
27
  ) {
26
28
  const prefixTrimLength = pathPrefix.length + 1; // remove /* from url
27
29
 
28
- const handler = async (res: uWs.HttpResponse, req: uWs.HttpRequest) => {
30
+ const handler = async (res: HttpResponse, req: HttpRequest) => {
29
31
  const method = req.getMethod().toUpperCase();
30
32
  if (method !== 'GET' && method !== 'POST') {
31
33
  // handle only get and post requests, while the rest
@@ -33,6 +35,7 @@ export function createUWebSocketsHandler<TRouter extends AnyRouter>(
33
35
  req.setYield(true);
34
36
  return;
35
37
  }
38
+
36
39
  const path = req.getUrl().substring(prefixTrimLength);
37
40
  const query = new URLSearchParams(decodeURIComponent(req.getQuery()));
38
41
 
@@ -43,11 +46,35 @@ export function createUWebSocketsHandler<TRouter extends AnyRouter>(
43
46
 
44
47
  // new request object needs to be created, because socket
45
48
  // can only be accessed synchronously, after await it cannot be accessed
46
- const requestObj: UWebSocketsRequestObject = {
49
+ const request: UWebSocketsRequestObject = {
47
50
  headers,
48
51
  method,
49
52
  query,
50
53
  path,
54
+ getCookies: getCookieFn(headers),
55
+ };
56
+
57
+ const resOverride = {
58
+ headers: new Map<string, string>(),
59
+ cookies: [] as string[],
60
+ status: 0,
61
+ };
62
+
63
+ const response: UWebSocketsResponseObject = {
64
+ setCookie: (
65
+ name: string,
66
+ value: string,
67
+ opts?: CookieSerializeOptions
68
+ ) => {
69
+ const serialized = cookie.serialize(name, value, opts); //.substring(12); //remove the "Set-Cookie: "
70
+ resOverride.cookies.push(serialized);
71
+ },
72
+ setStatus: (status: number) => {
73
+ resOverride.status = status;
74
+ },
75
+ setHeader: (key: string, value: string) => {
76
+ resOverride.headers.set(key, value);
77
+ },
51
78
  };
52
79
 
53
80
  const bodyResult = await readPostBody(method, res);
@@ -59,13 +86,13 @@ export function createUWebSocketsHandler<TRouter extends AnyRouter>(
59
86
  > {
60
87
  //res could be proxied here
61
88
  return await opts.createContext?.({
62
- // res,
63
89
  uWs: uWsApp,
64
- req: requestObj,
90
+ req: request,
91
+ res: response,
65
92
  });
66
93
  };
67
94
 
68
- const fakeReqObject: HTTPRequest = {
95
+ const internalReqObj: HTTPRequest = {
69
96
  method,
70
97
  headers,
71
98
  query,
@@ -73,43 +100,58 @@ export function createUWebSocketsHandler<TRouter extends AnyRouter>(
73
100
  };
74
101
 
75
102
  // TODO batching, onError options need implementation.
76
- // responseMeta is not applicable?
77
103
  const result = await resolveHTTPResponse({
78
104
  path,
79
105
  createContext,
80
106
  router: opts.router,
81
- req: fakeReqObject,
107
+ req: internalReqObj,
82
108
  error: bodyResult.ok ? null : bodyResult.error,
83
109
  });
84
110
 
85
- if ('status' in result) {
86
- res.writeStatus(result.status.toString()); //temp
87
- } else {
88
- // assume something went bad, should never happen?
89
- // there is no way to know from res object that something was send to the socket
90
- // can proxy it to detect res calls during createContext and resolve
91
- // then will need to exit from here
92
-
93
- res.cork(() => {
94
- res.writeStatus('500 INTERNAL SERVER ERROR');
95
- res.end();
111
+ // user returned already, do nothing
112
+ res.cork(() => {
113
+ if (resOverride.status != 0) {
114
+ res.writeStatus(resOverride.status.toString());
115
+ } else if ('status' in result) {
116
+ res.writeStatus(result.status.toString());
117
+ } else {
118
+ // assume something went bad, should never happen?
119
+ throw new Error('No status to send');
120
+
121
+ // res.writeStatus('500 INTERNAL SERVER ERROR');
122
+ // res.end();
123
+ // return;
124
+ }
125
+
126
+ if (opts.onRequest) opts.onRequest(res, req);
127
+
128
+ //send all cookies
129
+ resOverride.cookies.forEach((value) => {
130
+ res.writeHeader('Set-Cookie', value);
96
131
  });
97
- return;
98
- }
99
132
 
100
- for (const [key, value] of Object.entries(result.headers ?? {})) {
101
- if (typeof value === 'undefined') {
102
- continue;
133
+ resOverride.headers.forEach((value, key) => {
134
+ res.writeHeader(key, value);
135
+ });
136
+
137
+ for (const [key, value] of Object.entries(result.headers ?? {})) {
138
+ if (typeof value === 'undefined') {
139
+ continue;
140
+ }
141
+ // make sure to never override user defined headers
142
+ // cookies are an exception
143
+ if (resOverride.headers.has(key)) continue;
144
+
145
+ // not sure why it could be an array. This code path is not tested
146
+ // maybe its duplicates for the same key? like multiple "Set-Cookie"
147
+ if (Array.isArray(value))
148
+ value.forEach((header) => {
149
+ res.writeHeader(key, header);
150
+ });
151
+ else res.writeHeader(key, value);
103
152
  }
104
- // FIX not sure why it could be an array. This code path is not tested
105
- if (Array.isArray(value))
106
- value.forEach((header) => {
107
- res.writeHeader(key, header);
108
- });
109
- else res.writeHeader(key, value);
110
- }
111
153
 
112
- res.cork(() => {
154
+ //now send user headers
113
155
  if (result.body) res.write(result.body);
114
156
  res.end();
115
157
  });
package/src/types.ts CHANGED
@@ -1,11 +1,16 @@
1
1
  import { AnyRouter, inferRouterContext } from '@trpc/server';
2
- import { HttpResponse, TemplatedApp } from 'uWebSockets.js';
2
+ import { HttpRequest, HttpResponse, TemplatedApp } from 'uWebSockets.js';
3
+ import { CookieParseOptions, CookieSerializeOptions } from 'cookie';
3
4
 
4
- export type UWebSocketsRegisterEndpointOptions<TRouter extends AnyRouter> = {
5
+ export type UWebSocketsCreateHandlerOptions<TRouter extends AnyRouter> = {
6
+ /* trpc router */
5
7
  router: TRouter;
8
+ /* optional create context */
6
9
  createContext?: (
7
10
  opts: UWebSocketsCreateContextOptions
8
11
  ) => Promise<inferRouterContext<TRouter>> | inferRouterContext<TRouter>;
12
+ /* optional pre-request handler. Useful for dealing with CORS */
13
+ onRequest?: (res: HttpResponse, req: HttpRequest) => void;
9
14
  };
10
15
 
11
16
  export type UWebSocketsRequestObject = {
@@ -13,13 +18,18 @@ export type UWebSocketsRequestObject = {
13
18
  method: 'POST' | 'GET';
14
19
  query: URLSearchParams;
15
20
  path: string;
21
+ getCookies: (opts?: CookieParseOptions) => Record<string, string>;
16
22
  };
17
23
 
18
24
  // if this to be used, it needs to be proxied
19
- export type UWebSocketsResponseObject = HttpResponse;
25
+ export type UWebSocketsResponseObject = {
26
+ setCookie(key: string, value: string, opts?: CookieSerializeOptions): void;
27
+ setStatus(status: number): void;
28
+ setHeader(key: string, value: string): void;
29
+ };
20
30
 
21
31
  export type UWebSocketsCreateContextOptions = {
22
32
  req: UWebSocketsRequestObject;
23
33
  uWs: TemplatedApp;
24
- // res: UWebSocketsResponseObject;
34
+ res: UWebSocketsResponseObject;
25
35
  };
package/src/utils.ts CHANGED
@@ -1,5 +1,16 @@
1
1
  import { TRPCError } from '@trpc/server';
2
2
  import { HttpResponse } from 'uWebSockets.js';
3
+ import cookie, { CookieParseOptions } from 'cookie';
4
+ /*
5
+ cookie: 'cookie1=abc; cookie2=d.e'
6
+ */
7
+
8
+ export const getCookieFn =
9
+ (headers: Record<string, string>) => (opts?: CookieParseOptions) => {
10
+ if (!('cookie' in headers)) return {};
11
+
12
+ return cookie.parse(headers.cookie, opts);
13
+ };
3
14
 
4
15
  export function readPostBody(method: string, res: HttpResponse) {
5
16
  return new Promise<
@@ -1,38 +1,17 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import AbortController from 'abort-controller';
3
3
  import fetch from 'node-fetch';
4
- import { z } from 'zod';
5
4
  import { UWebSocketsCreateContextOptions } from '../src/types';
6
5
  import uWs from 'uWebSockets.js';
7
-
6
+ import z from 'zod';
8
7
  import * as trpc from '@trpc/server';
9
- import { TRPCError } from '@trpc/server';
8
+ import { inferAsyncReturnType, TRPCError } from '@trpc/server';
10
9
  import { createUWebSocketsHandler } from '../src/index';
11
- import { createTRPCClient } from '@trpc/client';
12
-
13
- const testPort = 8732;
10
+ import { createTRPCClient, HTTPHeaders } from '@trpc/client';
14
11
 
15
- type Context = {
16
- user: {
17
- name: string;
18
- } | null;
19
- };
20
- async function startServer() {
21
- const createContext = (_opts: UWebSocketsCreateContextOptions): Context => {
22
- const getUser = () => {
23
- if (_opts.req.headers.authorization === 'meow') {
24
- return {
25
- name: 'KATT',
26
- };
27
- }
28
- return null;
29
- };
30
-
31
- return {
32
- user: getUser(),
33
- };
34
- };
12
+ const testPort = 8799;
35
13
 
14
+ function makeRouter() {
36
15
  const router = trpc
37
16
  .router<Context>()
38
17
  .query('hello', {
@@ -65,8 +44,58 @@ async function startServer() {
65
44
  user: ctx.user,
66
45
  };
67
46
  },
47
+ })
48
+ .query('cookie-monster', {
49
+ resolve({ input, ctx }) {
50
+ const cookies = ctx.req.getCookies();
51
+
52
+ const combined = cookies['cookie1'] + cookies['cookie2'];
53
+ ctx.res.setCookie('one', 'nom');
54
+ ctx.res.setCookie('two', 'nom nom');
55
+ ctx.res.setHeader('x-spooked', 'true');
56
+ ctx.res.setStatus(201);
57
+ return {
58
+ combined,
59
+ user: ctx.user,
60
+ };
61
+ },
68
62
  });
69
63
 
64
+ return router;
65
+ }
66
+ export type Router = ReturnType<typeof makeRouter>;
67
+
68
+ function makeContext() {
69
+ const createContext = ({
70
+ req,
71
+ res,
72
+ uWs,
73
+ }: UWebSocketsCreateContextOptions) => {
74
+ const getUser = () => {
75
+ if (req.headers.authorization === 'meow') {
76
+ return {
77
+ name: 'KATT',
78
+ };
79
+ }
80
+ if (req.getCookies()?.user === 'romanzy')
81
+ return {
82
+ name: 'romanzy',
83
+ };
84
+ return null;
85
+ };
86
+
87
+ return {
88
+ req,
89
+ res,
90
+ uWs,
91
+ user: getUser(),
92
+ };
93
+ };
94
+
95
+ return createContext;
96
+ }
97
+ export type Context = inferAsyncReturnType<ReturnType<typeof makeContext>>;
98
+ async function startServer() {
70
99
  const app = uWs.App();
71
100
 
72
101
  // Handle CORS
@@ -76,51 +105,67 @@ async function startServer() {
76
105
  res.end();
77
106
  });
78
107
 
108
+ app.get('/', (res) => {
109
+ res.writeStatus('200 OK');
110
+
111
+ res.end();
112
+ });
113
+
79
114
  // need to register everything on the app object,
80
115
  // as uWebSockets does not have middleware
81
116
  createUWebSocketsHandler(app, '/trpc', {
82
- router,
83
- createContext,
117
+ onRequest: (res, req) => {
118
+ // allows for prerequest handling
119
+ res.writeHeader('Access-Control-Allow-Origin', '*');
120
+ },
121
+ router: makeRouter(),
122
+ createContext: makeContext(),
123
+ });
124
+
125
+ app.put('/trpc/put', (res) => {
126
+ res.writeStatus('204');
127
+ res.end();
128
+ });
129
+
130
+ app.any('/*', (res) => {
131
+ res.writeStatus('404 NOT FOUND');
132
+ res.end();
84
133
  });
85
134
 
86
- const { server, socket } = await new Promise<{
87
- server: uWs.TemplatedApp;
135
+ const { socket } = await new Promise<{
88
136
  socket: uWs.us_listen_socket;
89
137
  }>((resolve) => {
90
138
  app.listen('0.0.0.0', testPort, (socket) => {
91
139
  resolve({
92
- server: app,
93
140
  socket,
94
141
  });
95
142
  });
96
143
  });
97
144
 
98
- const client = createTRPCClient<typeof router>({
99
- url: `http://localhost:${testPort}/trpc`,
100
-
101
- AbortController: AbortController as any,
102
- fetch: fetch as any,
103
- headers: {
104
- authorization: 'meow',
105
- },
106
- });
107
-
108
145
  return {
109
146
  close: () =>
110
147
  new Promise<void>((resolve, reject) => {
111
148
  try {
112
149
  uWs.us_listen_socket_close(socket);
150
+ resolve();
113
151
  } catch (error) {
114
152
  reject();
115
153
  }
116
- resolve();
117
154
  }),
118
- router,
119
- client,
120
155
  };
121
156
  }
122
157
 
123
- let t: trpc.inferAsyncReturnType<typeof startServer>;
158
+ function makeClient(headers) {
159
+ return createTRPCClient<Router>({
160
+ url: `http://localhost:${testPort}/trpc`,
161
+
162
+ AbortController: AbortController as any,
163
+ fetch: fetch as any,
164
+ headers,
165
+ });
166
+ }
167
+
168
+ let t!: trpc.inferAsyncReturnType<typeof startServer>;
124
169
  beforeEach(async () => {
125
170
  t = await startServer();
126
171
  });
@@ -129,8 +174,11 @@ afterEach(async () => {
129
174
  });
130
175
 
131
176
  test('simple query', async () => {
177
+ // t.client.runtime.headers = ()
178
+ const client = makeClient({});
179
+
132
180
  expect(
133
- await t.client.query('hello', {
181
+ await client.query('hello', {
134
182
  who: 'test',
135
183
  })
136
184
  ).toMatchInlineSnapshot(`
@@ -139,25 +187,16 @@ test('simple query', async () => {
139
187
  }
140
188
  `);
141
189
 
142
- // t.client.runtime.headers()
143
-
144
- expect(await t.client.query('hello')).toMatchInlineSnapshot(`
145
- Object {
146
- "text": "hello KATT",
147
- }
148
- `);
190
+ expect(client.query('error', null)).rejects.toThrowError('error as expected');
149
191
  });
150
192
 
151
- // Error status codes are correct
152
- test('error handling', async () => {
153
- expect(t.client.query('error', null)).rejects.toThrowError(
154
- 'error as expected'
155
- );
156
- });
193
+ test('mutation with header', async () => {
194
+ const client = makeClient({
195
+ authorization: 'meow',
196
+ });
157
197
 
158
- test('simple mutation', async () => {
159
198
  expect(
160
- await t.client.mutation('test', {
199
+ await client.mutation('test', {
161
200
  value: 'lala',
162
201
  })
163
202
  ).toMatchInlineSnapshot(`
@@ -169,3 +208,55 @@ test('simple mutation', async () => {
169
208
  }
170
209
  `);
171
210
  });
211
+
212
+ // Error status codes are correct
213
+ test('reads cookies', async () => {
214
+ const client = makeClient({
215
+ cookie: 'cookie1=abc; cookie2=d.e; user=romanzy',
216
+ });
217
+
218
+ expect(await client.query('cookie-monster')).toMatchInlineSnapshot(`
219
+ Object {
220
+ "combined": "abcd.e",
221
+ "user": Object {
222
+ "name": "romanzy",
223
+ },
224
+ }
225
+ `);
226
+ });
227
+
228
+ test('setting cookies and headers', async () => {
229
+ const monsterRes = await fetch(
230
+ `http://localhost:${testPort}/trpc/cookie-monster`
231
+ );
232
+ expect(monsterRes.status).toEqual(201);
233
+ expect(monsterRes.headers.get('set-cookie')).toEqual(
234
+ 'one=nom, two=nom%20nom'
235
+ );
236
+ expect(monsterRes.headers.get('x-spooked')).toEqual('true');
237
+ });
238
+
239
+ test('error handling', async () => {
240
+ const indexRes = await fetch(`http://localhost:${testPort}`);
241
+ expect(indexRes.status).toEqual(200);
242
+
243
+ const putRes = await fetch(`http://localhost:${testPort}/trpc/put`, {
244
+ method: 'PUT',
245
+ });
246
+ expect(putRes.status).toEqual(204);
247
+
248
+ const badInput = '{"who": "test';
249
+ const badRes = await fetch(
250
+ `http://localhost:${testPort}/trpc/hello?input=${badInput}`
251
+ );
252
+ expect(badRes.status).toEqual(400);
253
+
254
+ const badPath = await fetch(
255
+ `http://localhost:${testPort}/trpc/nonexisting?input=${badInput}`
256
+ );
257
+ expect(badPath.status).toEqual(400);
258
+
259
+ const uncaught = await fetch(`http://localhost:${testPort}/badurl`);
260
+
261
+ expect(uncaught.status).toEqual(404);
262
+ });
package/types/index.d.ts CHANGED
@@ -1,11 +1,10 @@
1
1
  import { AnyRouter } from '@trpc/server';
2
- import type * as uWs from 'uWebSockets.js';
3
- import { UWebSocketsRegisterEndpointOptions } from './types';
2
+ import type { TemplatedApp } from 'uWebSockets.js';
3
+ import { UWebSocketsCreateHandlerOptions } from './types';
4
4
  export * from './types';
5
5
  /**
6
- *
7
6
  * @param uWsApp uWebsockets server instance
8
- * @param pathPrefix The path to endpoint without trailing slash (ex: "/trpc")
9
- * @param opts router and createContext functions
7
+ * @param pathPrefix The path to trpc without trailing slash (ex: "/trpc")
8
+ * @param opts handler options
10
9
  */
11
- export declare function createUWebSocketsHandler<TRouter extends AnyRouter>(uWsApp: uWs.TemplatedApp, pathPrefix: string, opts: UWebSocketsRegisterEndpointOptions<TRouter>): void;
10
+ export declare function createUWebSocketsHandler<TRouter extends AnyRouter>(uWsApp: TemplatedApp, pathPrefix: string, opts: UWebSocketsCreateHandlerOptions<TRouter>): void;
package/types/types.d.ts CHANGED
@@ -1,17 +1,25 @@
1
1
  import { AnyRouter, inferRouterContext } from '@trpc/server';
2
- import { HttpResponse, TemplatedApp } from 'uWebSockets.js';
3
- export declare type UWebSocketsRegisterEndpointOptions<TRouter extends AnyRouter> = {
2
+ import { HttpRequest, HttpResponse, TemplatedApp } from 'uWebSockets.js';
3
+ import { CookieParseOptions, CookieSerializeOptions } from 'cookie';
4
+ export declare type UWebSocketsCreateHandlerOptions<TRouter extends AnyRouter> = {
4
5
  router: TRouter;
5
6
  createContext?: (opts: UWebSocketsCreateContextOptions) => Promise<inferRouterContext<TRouter>> | inferRouterContext<TRouter>;
7
+ onRequest?: (res: HttpResponse, req: HttpRequest) => void;
6
8
  };
7
9
  export declare type UWebSocketsRequestObject = {
8
10
  headers: Record<string, string>;
9
11
  method: 'POST' | 'GET';
10
12
  query: URLSearchParams;
11
13
  path: string;
14
+ getCookies: (opts?: CookieParseOptions) => Record<string, string>;
15
+ };
16
+ export declare type UWebSocketsResponseObject = {
17
+ setCookie(key: string, value: string, opts?: CookieSerializeOptions): void;
18
+ setStatus(status: number): void;
19
+ setHeader(key: string, value: string): void;
12
20
  };
13
- export declare type UWebSocketsResponseObject = HttpResponse;
14
21
  export declare type UWebSocketsCreateContextOptions = {
15
22
  req: UWebSocketsRequestObject;
16
23
  uWs: TemplatedApp;
24
+ res: UWebSocketsResponseObject;
17
25
  };
package/types/utils.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { TRPCError } from '@trpc/server';
2
2
  import { HttpResponse } from 'uWebSockets.js';
3
+ import { CookieParseOptions } from 'cookie';
4
+ export declare const getCookieFn: (headers: Record<string, string>) => (opts?: CookieParseOptions) => Record<string, string>;
3
5
  export declare function readPostBody(method: string, res: HttpResponse): Promise<{
4
6
  ok: true;
5
7
  data: unknown;