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 +48 -1
- package/dist/index.js +69 -34
- package/dist/index.js.map +1 -1
- package/dist/utils.js +14 -1
- package/dist/utils.js.map +1 -1
- package/package.json +7 -5
- package/src/index.ts +81 -39
- package/src/types.ts +14 -4
- package/src/utils.ts +11 -0
- package/test/index.spec.ts +152 -61
- package/types/index.d.ts +5 -6
- package/types/types.d.ts +11 -3
- package/types/utils.d.ts +2 -0
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
|
|
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
|
|
25
|
-
* @param opts
|
|
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
|
|
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:
|
|
78
|
+
req: request,
|
|
79
|
+
res: response,
|
|
59
80
|
});
|
|
60
81
|
};
|
|
61
|
-
const
|
|
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:
|
|
93
|
+
req: internalReqObj,
|
|
74
94
|
error: bodyResult.ok ? null : bodyResult.error,
|
|
75
95
|
});
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,yCAIsB;AAStB,mCAAoD;AACpD,oDAA4B;AAC5B,0CAAwB;AAExB;;;;GAIG;AACH,SAAgB,wBAAwB,CACtC,MAAoB,EACpB,UAAkB,EAClB,IAA8C;IAE9C,MAAM,gBAAgB,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,qBAAqB;IAErE,MAAM,OAAO,GAAG,KAAK,EAAE,GAAiB,EAAE,GAAgB,EAAE,EAAE;QAC5D,MAAM,MAAM,GAAG,GAAG,CAAC,SAAS,EAAE,CAAC,WAAW,EAAE,CAAC;QAC7C,IAAI,MAAM,KAAK,KAAK,IAAI,MAAM,KAAK,MAAM,EAAE;YACzC,oDAAoD;YACpD,8CAA8C;YAC9C,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YACnB,OAAO;SACR;QAED,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;QACtD,MAAM,KAAK,GAAG,IAAI,eAAe,CAAC,kBAAkB,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;QAEtE,MAAM,OAAO,GAA2B,EAAE,CAAC;QAC3C,GAAG,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;YACzB,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACvB,CAAC,CAAC,CAAC;QAEH,yDAAyD;QACzD,wEAAwE;QACxE,MAAM,OAAO,GAA6B;YACxC,OAAO;YACP,MAAM;YACN,KAAK;YACL,IAAI;YACJ,UAAU,EAAE,IAAA,mBAAW,EAAC,OAAO,CAAC;SACjC,CAAC;QAEF,MAAM,WAAW,GAAG;YAClB,OAAO,EAAE,IAAI,GAAG,EAAkB;YAClC,OAAO,EAAE,EAAc;YACvB,MAAM,EAAE,CAAC;SACV,CAAC;QAEF,MAAM,QAAQ,GAA8B;YAC1C,SAAS,EAAE,CACT,IAAY,EACZ,KAAa,EACb,IAA6B,EAC7B,EAAE;gBACF,MAAM,UAAU,GAAG,gBAAM,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,6CAA6C;gBACrG,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACvC,CAAC;YACD,SAAS,EAAE,CAAC,MAAc,EAAE,EAAE;gBAC5B,WAAW,CAAC,MAAM,GAAG,MAAM,CAAC;YAC9B,CAAC;YACD,SAAS,EAAE,CAAC,GAAW,EAAE,KAAa,EAAE,EAAE;gBACxC,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YACtC,CAAC;SACF,CAAC;QAEF,MAAM,UAAU,GAAG,MAAM,IAAA,oBAAY,EAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAEnD,8BAA8B;QAE9B,MAAM,aAAa,GAAG,KAAK,UAAU,CAAC;YAGpC,2BAA2B;YAC3B,OAAO,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;gBAChC,GAAG,EAAE,MAAM;gBACX,GAAG,EAAE,OAAO;gBACZ,GAAG,EAAE,QAAQ;aACd,CAAC,CAAC;QACL,CAAC,CAAC;QAEF,MAAM,cAAc,GAAgB;YAClC,MAAM;YACN,OAAO;YACP,KAAK;YACL,IAAI,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;SAClD,CAAC;QAEF,sDAAsD;QACtD,MAAM,MAAM,GAAG,MAAM,IAAA,4BAAmB,EAAC;YACvC,IAAI;YACJ,aAAa;YACb,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,GAAG,EAAE,cAAc;YACnB,KAAK,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK;SAC/C,CAAC,CAAC;QAEH,oCAAoC;QACpC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE;YACZ,IAAI,WAAW,CAAC,MAAM,IAAI,CAAC,EAAE;gBAC3B,GAAG,CAAC,WAAW,CAAC,WAAW,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;aAChD;iBAAM,IAAI,QAAQ,IAAI,MAAM,EAAE;gBAC7B,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;aAC3C;iBAAM;gBACL,kDAAkD;gBAClD,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;gBAErC,gDAAgD;gBAChD,aAAa;gBACb,UAAU;aACX;YAED,IAAI,IAAI,CAAC,SAAS;gBAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YAE7C,kBAAkB;YAClB,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;gBACpC,GAAG,CAAC,WAAW,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;YACvC,CAAC,CAAC,CAAC;YAEH,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;gBACzC,GAAG,CAAC,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAC9B,CAAC,CAAC,CAAC;YAEH,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE;gBAC/D,IAAI,OAAO,KAAK,KAAK,WAAW,EAAE;oBAChC,SAAS;iBACV;gBACD,mDAAmD;gBACnD,2BAA2B;gBAC3B,IAAI,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;oBAAE,SAAS;gBAE3C,kEAAkE;gBAClE,oEAAoE;gBACpE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;oBACtB,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;wBACvB,GAAG,CAAC,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;oBAC/B,CAAC,CAAC,CAAC;;oBACA,GAAG,CAAC,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;aAClC;YAED,uBAAuB;YACvB,IAAI,MAAM,CAAC,IAAI;gBAAE,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACxC,GAAG,CAAC,GAAG,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,CAAC,GAAG,CAAC,UAAU,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;AACzC,CAAC;AA1ID,4DA0IC"}
|
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":"
|
|
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.
|
|
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
|
|
7
|
+
import { CookieSerializeOptions } from 'cookie';
|
|
8
|
+
import type { HttpRequest, HttpResponse, TemplatedApp } from 'uWebSockets.js';
|
|
8
9
|
import {
|
|
9
|
-
|
|
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
|
|
19
|
-
* @param opts
|
|
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:
|
|
24
|
+
uWsApp: TemplatedApp,
|
|
23
25
|
pathPrefix: string,
|
|
24
|
-
opts:
|
|
26
|
+
opts: UWebSocketsCreateHandlerOptions<TRouter>
|
|
25
27
|
) {
|
|
26
28
|
const prefixTrimLength = pathPrefix.length + 1; // remove /* from url
|
|
27
29
|
|
|
28
|
-
const handler = async (res:
|
|
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
|
|
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:
|
|
90
|
+
req: request,
|
|
91
|
+
res: response,
|
|
65
92
|
});
|
|
66
93
|
};
|
|
67
94
|
|
|
68
|
-
const
|
|
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:
|
|
107
|
+
req: internalReqObj,
|
|
82
108
|
error: bodyResult.ok ? null : bodyResult.error,
|
|
83
109
|
});
|
|
84
110
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
res.
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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<
|
package/test/index.spec.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
83
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
|
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
|
|
3
|
-
import {
|
|
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
|
|
9
|
-
* @param opts
|
|
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:
|
|
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
|
-
|
|
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;
|