@trpc/server 11.8.2-canary.3 → 11.9.0
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/dist/adapters/fastify/index.cjs +1 -1
- package/dist/adapters/fastify/index.mjs +1 -1
- package/dist/adapters/next-app-dir.cjs +72 -72
- package/dist/adapters/next-app-dir.mjs +72 -72
- package/dist/adapters/next-app-dir.mjs.map +1 -1
- package/dist/adapters/ws.cjs +3 -2
- package/dist/adapters/ws.d.cts +33 -4
- package/dist/adapters/ws.d.cts.map +1 -1
- package/dist/adapters/ws.d.mts +33 -4
- package/dist/adapters/ws.d.mts.map +1 -1
- package/dist/adapters/ws.mjs +2 -2
- package/dist/{ws-Dl6Y2YNA.mjs → ws-DCeuK64x.mjs} +50 -11
- package/dist/ws-DCeuK64x.mjs.map +1 -0
- package/dist/{ws-B16sko8C.cjs → ws-DpjAOm3h.cjs} +54 -9
- package/package.json +3 -3
- package/src/adapters/ws.ts +53 -13
- package/src/adapters/wsEncoder.ts +35 -0
- package/dist/ws-Dl6Y2YNA.mjs.map +0 -1
package/dist/adapters/ws.cjs
CHANGED
|
@@ -10,8 +10,9 @@ require('../initTRPC--HrEu8sH.cjs');
|
|
|
10
10
|
require('../http-DXy3XyhL.cjs');
|
|
11
11
|
require('../node-http-D0T_XJ9C.cjs');
|
|
12
12
|
require('../observable-BVzLuBs6.cjs');
|
|
13
|
-
const require_ws = require('../ws-
|
|
13
|
+
const require_ws = require('../ws-DpjAOm3h.cjs');
|
|
14
14
|
|
|
15
15
|
exports.applyWSSHandler = require_ws.applyWSSHandler;
|
|
16
16
|
exports.getWSConnectionHandler = require_ws.getWSConnectionHandler;
|
|
17
|
-
exports.handleKeepAlive = require_ws.handleKeepAlive;
|
|
17
|
+
exports.handleKeepAlive = require_ws.handleKeepAlive;
|
|
18
|
+
exports.jsonEncoder = require_ws.jsonEncoder;
|
package/dist/adapters/ws.d.cts
CHANGED
|
@@ -5,8 +5,34 @@ import { NodeHTTPCreateContextFnOptions } from "../index.d--uNJFzUS.cjs";
|
|
|
5
5
|
import { IncomingMessage } from "http";
|
|
6
6
|
import ws from "ws";
|
|
7
7
|
|
|
8
|
-
//#region src/adapters/
|
|
8
|
+
//#region src/adapters/wsEncoder.d.ts
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Encoder for WebSocket wire format.
|
|
12
|
+
* Encodes outgoing messages and decodes incoming messages.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const customEncoder: Encoder = {
|
|
17
|
+
* encode: (data) => myFormat.stringify(data),
|
|
18
|
+
* decode: (data) => myFormat.parse(data),
|
|
19
|
+
* };
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
interface Encoder {
|
|
23
|
+
/** Encode data for transmission over the wire */
|
|
24
|
+
encode(data: unknown): string | Uint8Array;
|
|
25
|
+
/** Decode data received from the wire */
|
|
26
|
+
decode(data: string | ArrayBuffer | Uint8Array): unknown;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Default JSON encoder - used when no encoder is specified.
|
|
30
|
+
* This maintains backwards compatibility with existing behavior.
|
|
31
|
+
*/
|
|
32
|
+
declare const jsonEncoder: Encoder;
|
|
33
|
+
//# sourceMappingURL=wsEncoder.d.ts.map
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/adapters/ws.d.ts
|
|
10
36
|
/**
|
|
11
37
|
* @public
|
|
12
38
|
*/
|
|
@@ -45,6 +71,11 @@ type WSSHandlerOptions<TRouter extends AnyRouter> = WSConnectionHandlerOptions<T
|
|
|
45
71
|
* @default false
|
|
46
72
|
*/
|
|
47
73
|
dangerouslyDisablePong?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Custom encoder for wire encoding (e.g. custom binary formats)
|
|
76
|
+
* @default jsonEncoder
|
|
77
|
+
*/
|
|
78
|
+
experimental_encoder?: Encoder;
|
|
48
79
|
};
|
|
49
80
|
declare function getWSConnectionHandler<TRouter extends AnyRouter>(opts: WSSHandlerOptions<TRouter>): (client: ws.WebSocket, req: IncomingMessage) => void;
|
|
50
81
|
/**
|
|
@@ -54,8 +85,6 @@ declare function handleKeepAlive(client: ws.WebSocket, pingMs?: number, pongWait
|
|
|
54
85
|
declare function applyWSSHandler<TRouter extends AnyRouter>(opts: WSSHandlerOptions<TRouter>): {
|
|
55
86
|
broadcastReconnectNotification: () => void;
|
|
56
87
|
};
|
|
57
|
-
//# sourceMappingURL=ws.d.ts.map
|
|
58
|
-
|
|
59
88
|
//#endregion
|
|
60
|
-
export { CreateWSSContextFn, CreateWSSContextFnOptions, WSConnectionHandlerOptions, WSSHandlerOptions, applyWSSHandler, getWSConnectionHandler, handleKeepAlive };
|
|
89
|
+
export { CreateWSSContextFn, CreateWSSContextFnOptions, Encoder, WSConnectionHandlerOptions, WSSHandlerOptions, applyWSSHandler, getWSConnectionHandler, handleKeepAlive, jsonEncoder };
|
|
61
90
|
//# sourceMappingURL=ws.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws.d.cts","names":[],"sources":["../../src/adapters/ws.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ws.d.cts","names":[],"sources":["../../src/adapters/wsEncoder.ts","../../src/adapters/ws.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;AAYA;;;;AAIsC,UAJrB,OAAA,CAIqB;EAAU;EAOnC,MAAA,CAAA,IAAA,EAAA,OAWZ,CAAA,EAAA,MAXyB,GATQ,UAoBjC;;wBAlBuB,cAAc;;ACqCtC;;;;AAAwC,cD9B3B,WC8B2B,ED9Bd,OC8Bc;AAA8B;;;;ADzCtE;;AAEkC,KCuCtB,yBAAA,GAA4B,8BDvCN,CCwChC,eDxCgC,ECyChC,EAAA,CAAG,SDzC6B,CAAA;;;AAEc;AAOnC,KCsCD,kBDtCc,CAAA,gBCsCqB,SD3B9C,CAAA,GAAA,CAAA,IAAA,EC4BO,yBD5BP,EAAA,GC6BI,YD7BJ,CC6BiB,kBD7BjB,CC6BoC,OD7BpC,CAAA,CAAA;KC+BW,2CAA2C,aACrD,mBAAmB,SAAS,mBAC1B,sBACE,mBAAmB,UACnB,mBAAmB;;;AAhBzB;AAAqC,KAsBzB,iBAtByB,CAAA,gBAsBS,SAtBT,CAAA,GAuBnC,0BAvBmC,CAuBR,OAvBQ,CAAA,GAAA;EAAA,GACnC,EAuBO,EAAA,CAAG,eAvBV;EAAe,MACZ,CAAA,EAAA,MAAA;EAAS,SAF0B,CAAA,EAAA;IAA8B;AAQtE;;;IACQ,OAAA,EAAA,OAAA;IAC6B;;;AAApB;IAEL,MAAA,CAAA,EAAA,MAAA;IAA0B;;;;IACpC,UAAA,CAAA,EAAA,MAAA;EAAkB,CAAA;EAEY;;;;AADP;EAQb,sBAAiB,CAAA,EAAA,OAAA;EAAA;;;;EACD,oBAChB,CAAA,EA6Be,OA7Bf;CAAe;AA6BO,iBAGlB,sBAHkB,CAAA,gBAGqB,SAHrB,CAAA,CAAA,IAAA,EAI1B,iBAJ0B,CAIR,OAJQ,CAAA,CAAA,EAAA,CAAA,MAAA,EAUhB,EAAA,CAAG,SAVa,EAAA,GAAA,EAUG,eAVH,EAAA,GAAA,IAAA;AAGlC;;;AAC0B,iBAgdV,eAAA,CAhdU,MAAA,EAidhB,EAAA,CAAG,SAjda,EAAA,MAAA,CAAA,EAAA,MAAA,EAAA,UAAA,CAAA,EAAA,MAAA,CAAA,EAAA,IAAA;AAAlB,iBAsfQ,eAtfR,CAAA,gBAsfwC,SAtfxC,CAAA,CAAA,IAAA,EAufA,iBAvfA,CAufkB,OAvflB,CAAA,CAAA,EAAA;EAAiB,8BAMJ,EAAA,GAAA,GAAA,IAAA;CAAS"}
|
package/dist/adapters/ws.d.mts
CHANGED
|
@@ -5,8 +5,34 @@ import { NodeHTTPCreateContextFnOptions } from "../index.d-D7vhS0-R.mjs";
|
|
|
5
5
|
import { IncomingMessage } from "http";
|
|
6
6
|
import ws from "ws";
|
|
7
7
|
|
|
8
|
-
//#region src/adapters/
|
|
8
|
+
//#region src/adapters/wsEncoder.d.ts
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Encoder for WebSocket wire format.
|
|
12
|
+
* Encodes outgoing messages and decodes incoming messages.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const customEncoder: Encoder = {
|
|
17
|
+
* encode: (data) => myFormat.stringify(data),
|
|
18
|
+
* decode: (data) => myFormat.parse(data),
|
|
19
|
+
* };
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
interface Encoder {
|
|
23
|
+
/** Encode data for transmission over the wire */
|
|
24
|
+
encode(data: unknown): string | Uint8Array;
|
|
25
|
+
/** Decode data received from the wire */
|
|
26
|
+
decode(data: string | ArrayBuffer | Uint8Array): unknown;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Default JSON encoder - used when no encoder is specified.
|
|
30
|
+
* This maintains backwards compatibility with existing behavior.
|
|
31
|
+
*/
|
|
32
|
+
declare const jsonEncoder: Encoder;
|
|
33
|
+
//# sourceMappingURL=wsEncoder.d.ts.map
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/adapters/ws.d.ts
|
|
10
36
|
/**
|
|
11
37
|
* @public
|
|
12
38
|
*/
|
|
@@ -45,6 +71,11 @@ type WSSHandlerOptions<TRouter extends AnyRouter> = WSConnectionHandlerOptions<T
|
|
|
45
71
|
* @default false
|
|
46
72
|
*/
|
|
47
73
|
dangerouslyDisablePong?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Custom encoder for wire encoding (e.g. custom binary formats)
|
|
76
|
+
* @default jsonEncoder
|
|
77
|
+
*/
|
|
78
|
+
experimental_encoder?: Encoder;
|
|
48
79
|
};
|
|
49
80
|
declare function getWSConnectionHandler<TRouter extends AnyRouter>(opts: WSSHandlerOptions<TRouter>): (client: ws.WebSocket, req: IncomingMessage) => void;
|
|
50
81
|
/**
|
|
@@ -54,8 +85,6 @@ declare function handleKeepAlive(client: ws.WebSocket, pingMs?: number, pongWait
|
|
|
54
85
|
declare function applyWSSHandler<TRouter extends AnyRouter>(opts: WSSHandlerOptions<TRouter>): {
|
|
55
86
|
broadcastReconnectNotification: () => void;
|
|
56
87
|
};
|
|
57
|
-
//# sourceMappingURL=ws.d.ts.map
|
|
58
|
-
|
|
59
88
|
//#endregion
|
|
60
|
-
export { CreateWSSContextFn, CreateWSSContextFnOptions, WSConnectionHandlerOptions, WSSHandlerOptions, applyWSSHandler, getWSConnectionHandler, handleKeepAlive };
|
|
89
|
+
export { CreateWSSContextFn, CreateWSSContextFnOptions, Encoder, WSConnectionHandlerOptions, WSSHandlerOptions, applyWSSHandler, getWSConnectionHandler, handleKeepAlive, jsonEncoder };
|
|
61
90
|
//# sourceMappingURL=ws.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws.d.mts","names":[],"sources":["../../src/adapters/ws.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ws.d.mts","names":[],"sources":["../../src/adapters/wsEncoder.ts","../../src/adapters/ws.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;AAYA;;;;AAIsC,UAJrB,OAAA,CAIqB;EAAU;EAOnC,MAAA,CAAA,IAAA,EAAA,OAWZ,CAAA,EAAA,MAXyB,GATQ,UAoBjC;;wBAlBuB,cAAc;;ACqCtC;;;;AAAwC,cD9B3B,WC8B2B,ED9Bd,OC8Bc;AAA8B;;;;ADzCtE;;AAEkC,KCuCtB,yBAAA,GAA4B,8BDvCN,CCwChC,eDxCgC,ECyChC,EAAA,CAAG,SDzC6B,CAAA;;;AAEc;AAOnC,KCsCD,kBDtCc,CAAA,gBCsCqB,SD3B9C,CAAA,GAAA,CAAA,IAAA,EC4BO,yBD5BP,EAAA,GC6BI,YD7BJ,CC6BiB,kBD7BjB,CC6BoC,OD7BpC,CAAA,CAAA;KC+BW,2CAA2C,aACrD,mBAAmB,SAAS,mBAC1B,sBACE,mBAAmB,UACnB,mBAAmB;;;AAhBzB;AAAqC,KAsBzB,iBAtByB,CAAA,gBAsBS,SAtBT,CAAA,GAuBnC,0BAvBmC,CAuBR,OAvBQ,CAAA,GAAA;EAAA,GACnC,EAuBO,EAAA,CAAG,eAvBV;EAAe,MACZ,CAAA,EAAA,MAAA;EAAS,SAF0B,CAAA,EAAA;IAA8B;AAQtE;;;IACQ,OAAA,EAAA,OAAA;IAC6B;;;AAApB;IAEL,MAAA,CAAA,EAAA,MAAA;IAA0B;;;;IACpC,UAAA,CAAA,EAAA,MAAA;EAAkB,CAAA;EAEY;;;;AADP;EAQb,sBAAiB,CAAA,EAAA,OAAA;EAAA;;;;EACD,oBAChB,CAAA,EA6Be,OA7Bf;CAAe;AA6BO,iBAGlB,sBAHkB,CAAA,gBAGqB,SAHrB,CAAA,CAAA,IAAA,EAI1B,iBAJ0B,CAIR,OAJQ,CAAA,CAAA,EAAA,CAAA,MAAA,EAUhB,EAAA,CAAG,SAVa,EAAA,GAAA,EAUG,eAVH,EAAA,GAAA,IAAA;AAGlC;;;AAC0B,iBAgdV,eAAA,CAhdU,MAAA,EAidhB,EAAA,CAAG,SAjda,EAAA,MAAA,CAAA,EAAA,MAAA,EAAA,UAAA,CAAA,EAAA,MAAA,CAAA,EAAA,IAAA;AAAlB,iBAsfQ,eAtfR,CAAA,gBAsfwC,SAtfxC,CAAA,CAAA,IAAA,EAufA,iBAvfA,CAufkB,OAvflB,CAAA,CAAA,EAAA;EAAiB,8BAMJ,EAAA,GAAA,GAAA,IAAA;CAAS"}
|
package/dist/adapters/ws.mjs
CHANGED
|
@@ -10,6 +10,6 @@ import "../initTRPC-T5bbc89W.mjs";
|
|
|
10
10
|
import "../http-CWyjOa1l.mjs";
|
|
11
11
|
import "../node-http-pD5xpNfK.mjs";
|
|
12
12
|
import "../observable-CUiPknO-.mjs";
|
|
13
|
-
import { applyWSSHandler, getWSConnectionHandler, handleKeepAlive } from "../ws-
|
|
13
|
+
import { applyWSSHandler, getWSConnectionHandler, handleKeepAlive, jsonEncoder } from "../ws-DCeuK64x.mjs";
|
|
14
14
|
|
|
15
|
-
export { applyWSSHandler, getWSConnectionHandler, handleKeepAlive };
|
|
15
|
+
export { applyWSSHandler, getWSConnectionHandler, handleKeepAlive, jsonEncoder };
|
|
@@ -6,6 +6,20 @@ import { Unpromise, iteratorResource, parseConnectionParamsFromUnknown, require_
|
|
|
6
6
|
import { isObservable, observableToAsyncIterable } from "./observable-UMO3vUa_.mjs";
|
|
7
7
|
import { createURL } from "./node-http-pD5xpNfK.mjs";
|
|
8
8
|
|
|
9
|
+
//#region src/adapters/wsEncoder.ts
|
|
10
|
+
/**
|
|
11
|
+
* Default JSON encoder - used when no encoder is specified.
|
|
12
|
+
* This maintains backwards compatibility with existing behavior.
|
|
13
|
+
*/
|
|
14
|
+
const jsonEncoder = {
|
|
15
|
+
encode: (data) => JSON.stringify(data),
|
|
16
|
+
decode: (data) => {
|
|
17
|
+
if (typeof data !== "string") throw new Error("jsonEncoder received binary data. JSON uses text frames. Use a binary encoder for binary data.");
|
|
18
|
+
return JSON.parse(data);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
9
23
|
//#region src/adapters/ws.ts
|
|
10
24
|
var import_objectSpread2 = __toESM(require_objectSpread2(), 1);
|
|
11
25
|
var import_usingCtx = __toESM(require_usingCtx(), 1);
|
|
@@ -15,8 +29,10 @@ var import_usingCtx = __toESM(require_usingCtx(), 1);
|
|
|
15
29
|
*/
|
|
16
30
|
const WEBSOCKET_OPEN = 1;
|
|
17
31
|
function getWSConnectionHandler(opts) {
|
|
32
|
+
var _opts$experimental_en;
|
|
18
33
|
const { createContext, router } = opts;
|
|
19
34
|
const { transformer } = router._def._config;
|
|
35
|
+
const encoder = (_opts$experimental_en = opts.experimental_encoder) !== null && _opts$experimental_en !== void 0 ? _opts$experimental_en : jsonEncoder;
|
|
20
36
|
return (client, req) => {
|
|
21
37
|
var _opts$keepAlive;
|
|
22
38
|
const clientSubscriptions = /* @__PURE__ */ new Map();
|
|
@@ -26,7 +42,7 @@ function getWSConnectionHandler(opts) {
|
|
|
26
42
|
handleKeepAlive(client, pingMs, pongWaitMs);
|
|
27
43
|
}
|
|
28
44
|
function respond(untransformedJSON) {
|
|
29
|
-
client.send(
|
|
45
|
+
client.send(encoder.encode(transformTRPCResponse(router._def._config, untransformedJSON)));
|
|
30
46
|
}
|
|
31
47
|
async function createCtxPromise(getConnectionParams) {
|
|
32
48
|
try {
|
|
@@ -300,18 +316,39 @@ function getWSConnectionHandler(opts) {
|
|
|
300
316
|
});
|
|
301
317
|
});
|
|
302
318
|
}
|
|
303
|
-
client.on("message", (rawData) => {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
if (
|
|
319
|
+
client.on("message", (rawData, isBinary) => {
|
|
320
|
+
if (!isBinary) {
|
|
321
|
+
const msgStr = rawData.toString();
|
|
322
|
+
if (msgStr === "PONG") return;
|
|
323
|
+
if (msgStr === "PING") {
|
|
324
|
+
if (!opts.dangerouslyDisablePong) client.send("PONG");
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (!Buffer.isBuffer(rawData)) {
|
|
329
|
+
const error = new TRPCError({
|
|
330
|
+
code: "UNPROCESSABLE_CONTENT",
|
|
331
|
+
message: "Unexpected WebSocket message format"
|
|
332
|
+
});
|
|
333
|
+
respond({
|
|
334
|
+
id: null,
|
|
335
|
+
error: getErrorShape({
|
|
336
|
+
config: router._def._config,
|
|
337
|
+
error,
|
|
338
|
+
type: "unknown",
|
|
339
|
+
path: void 0,
|
|
340
|
+
input: void 0,
|
|
341
|
+
ctx
|
|
342
|
+
})
|
|
343
|
+
});
|
|
308
344
|
return;
|
|
309
345
|
}
|
|
346
|
+
const data = isBinary ? rawData : rawData.toString("utf8");
|
|
310
347
|
if (!ctxPromise) {
|
|
311
348
|
ctxPromise = createCtxPromise(() => {
|
|
312
349
|
let msg;
|
|
313
350
|
try {
|
|
314
|
-
msg =
|
|
351
|
+
msg = encoder.decode(data);
|
|
315
352
|
if (!isObject(msg)) throw new Error("Message was not an object");
|
|
316
353
|
} catch (cause) {
|
|
317
354
|
throw new TRPCError({
|
|
@@ -327,7 +364,7 @@ function getWSConnectionHandler(opts) {
|
|
|
327
364
|
}
|
|
328
365
|
const parsedMsgs = run(() => {
|
|
329
366
|
try {
|
|
330
|
-
const msgJSON =
|
|
367
|
+
const msgJSON = encoder.decode(data);
|
|
331
368
|
const msgs = Array.isArray(msgJSON) ? msgJSON : [msgJSON];
|
|
332
369
|
return msgs.map((raw) => parseTRPCMessage(raw, transformer));
|
|
333
370
|
} catch (cause) {
|
|
@@ -399,6 +436,8 @@ function handleKeepAlive(client, pingMs = 3e4, pongWaitMs = 5e3) {
|
|
|
399
436
|
schedulePing();
|
|
400
437
|
}
|
|
401
438
|
function applyWSSHandler(opts) {
|
|
439
|
+
var _opts$experimental_en2;
|
|
440
|
+
const encoder = (_opts$experimental_en2 = opts.experimental_encoder) !== null && _opts$experimental_en2 !== void 0 ? _opts$experimental_en2 : jsonEncoder;
|
|
402
441
|
const onConnection = getWSConnectionHandler(opts);
|
|
403
442
|
opts.wss.on("connection", (client, req) => {
|
|
404
443
|
var _req$url;
|
|
@@ -410,11 +449,11 @@ function applyWSSHandler(opts) {
|
|
|
410
449
|
id: null,
|
|
411
450
|
method: "reconnect"
|
|
412
451
|
};
|
|
413
|
-
const data =
|
|
452
|
+
const data = encoder.encode(response);
|
|
414
453
|
for (const client of opts.wss.clients) if (client.readyState === WEBSOCKET_OPEN) client.send(data);
|
|
415
454
|
} };
|
|
416
455
|
}
|
|
417
456
|
|
|
418
457
|
//#endregion
|
|
419
|
-
export { applyWSSHandler, getWSConnectionHandler, handleKeepAlive };
|
|
420
|
-
//# sourceMappingURL=ws-
|
|
458
|
+
export { applyWSSHandler, getWSConnectionHandler, handleKeepAlive, jsonEncoder };
|
|
459
|
+
//# sourceMappingURL=ws-DCeuK64x.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws-DCeuK64x.mjs","names":["jsonEncoder: Encoder","opts: WSSHandlerOptions<TRouter>","client: ws.WebSocket","req: IncomingMessage","untransformedJSON: TRPCResponseMessage","getConnectionParams: () => TRPCRequestInfo['connectionParams']","ctx: Context | undefined","msg: TRPCClientOutgoingMessage","abortController","next:\n | null\n | TRPCError\n | Awaited<\n typeof abortPromise | ReturnType<(typeof iterator)['next']>\n >","result: null | TRPCResultMessage<unknown>['result']","id","data: string | Uint8Array","msgJSON: unknown","msgs: unknown[]","timeout: NodeJS.Timeout | undefined","ping: NodeJS.Timeout | undefined","response: TRPCReconnectNotification"],"sources":["../src/adapters/wsEncoder.ts","../src/adapters/ws.ts"],"sourcesContent":["/**\n * Encoder for WebSocket wire format.\n * Encodes outgoing messages and decodes incoming messages.\n *\n * @example\n * ```ts\n * const customEncoder: Encoder = {\n * encode: (data) => myFormat.stringify(data),\n * decode: (data) => myFormat.parse(data),\n * };\n * ```\n */\nexport interface Encoder {\n /** Encode data for transmission over the wire */\n encode(data: unknown): string | Uint8Array;\n /** Decode data received from the wire */\n decode(data: string | ArrayBuffer | Uint8Array): unknown;\n}\n\n/**\n * Default JSON encoder - used when no encoder is specified.\n * This maintains backwards compatibility with existing behavior.\n */\nexport const jsonEncoder: Encoder = {\n encode: (data) => JSON.stringify(data),\n decode: (data) => {\n if (typeof data !== 'string') {\n throw new Error(\n 'jsonEncoder received binary data. JSON uses text frames. ' +\n 'Use a binary encoder for binary data.',\n );\n }\n return JSON.parse(data);\n },\n};\n","import type { IncomingMessage } from 'http';\nimport type ws from 'ws';\nimport type {\n AnyRouter,\n CreateContextCallback,\n inferRouterContext,\n} from '../@trpc/server';\nimport {\n callTRPCProcedure,\n getErrorShape,\n getTRPCErrorFromUnknown,\n transformTRPCResponse,\n TRPCError,\n} from '../@trpc/server';\nimport type { TRPCRequestInfo } from '../@trpc/server/http';\nimport { type BaseHandlerOptions } from '../@trpc/server/http';\nimport { parseTRPCMessage } from '../@trpc/server/rpc';\n// @trpc/server/rpc\nimport type {\n TRPCClientOutgoingMessage,\n TRPCConnectionParamsMessage,\n TRPCReconnectNotification,\n TRPCResponseMessage,\n TRPCResultMessage,\n} from '../@trpc/server/rpc';\nimport { parseConnectionParamsFromUnknown } from '../http';\nimport { isObservable, observableToAsyncIterable } from '../observable';\n// eslint-disable-next-line no-restricted-imports\nimport {\n isAsyncIterable,\n isObject,\n isTrackedEnvelope,\n run,\n type MaybePromise,\n} from '../unstable-core-do-not-import';\n// eslint-disable-next-line no-restricted-imports\nimport type { Result } from '../unstable-core-do-not-import';\n// eslint-disable-next-line no-restricted-imports\nimport { iteratorResource } from '../unstable-core-do-not-import/stream/utils/asyncIterable';\nimport { Unpromise } from '../vendor/unpromise';\nimport { createURL, type NodeHTTPCreateContextFnOptions } from './node-http';\nimport type { Encoder } from './wsEncoder';\nimport { jsonEncoder } from './wsEncoder';\n\n/**\n * Importing ws causes a build error\n * @see https://github.com/trpc/trpc/pull/5279\n */\nconst WEBSOCKET_OPEN = 1; /* ws.WebSocket.OPEN */\n\n/**\n * @public\n */\nexport type CreateWSSContextFnOptions = NodeHTTPCreateContextFnOptions<\n IncomingMessage,\n ws.WebSocket\n>;\n\n/**\n * @public\n */\nexport type CreateWSSContextFn<TRouter extends AnyRouter> = (\n opts: CreateWSSContextFnOptions,\n) => MaybePromise<inferRouterContext<TRouter>>;\n\nexport type WSConnectionHandlerOptions<TRouter extends AnyRouter> =\n BaseHandlerOptions<TRouter, IncomingMessage> &\n CreateContextCallback<\n inferRouterContext<TRouter>,\n CreateWSSContextFn<TRouter>\n >;\n\n/**\n * Web socket server handler\n */\nexport type WSSHandlerOptions<TRouter extends AnyRouter> =\n WSConnectionHandlerOptions<TRouter> & {\n wss: ws.WebSocketServer;\n prefix?: string;\n keepAlive?: {\n /**\n * Enable heartbeat messages\n * @default false\n */\n enabled: boolean;\n /**\n * Heartbeat interval in milliseconds\n * @default 30_000\n */\n pingMs?: number;\n /**\n * Terminate the WebSocket if no pong is received after this many milliseconds\n * @default 5_000\n */\n pongWaitMs?: number;\n };\n /**\n * Disable responding to ping messages from the client\n * **Not recommended** - this is mainly used for testing\n * @default false\n */\n dangerouslyDisablePong?: boolean;\n /**\n * Custom encoder for wire encoding (e.g. custom binary formats)\n * @default jsonEncoder\n */\n experimental_encoder?: Encoder;\n };\n\nexport function getWSConnectionHandler<TRouter extends AnyRouter>(\n opts: WSSHandlerOptions<TRouter>,\n) {\n const { createContext, router } = opts;\n const { transformer } = router._def._config;\n const encoder = opts.experimental_encoder ?? jsonEncoder;\n\n return (client: ws.WebSocket, req: IncomingMessage) => {\n type Context = inferRouterContext<TRouter>;\n type ContextResult = Result<Context>;\n\n const clientSubscriptions = new Map<number | string, AbortController>();\n const abortController = new AbortController();\n\n if (opts.keepAlive?.enabled) {\n const { pingMs, pongWaitMs } = opts.keepAlive;\n handleKeepAlive(client, pingMs, pongWaitMs);\n }\n\n function respond(untransformedJSON: TRPCResponseMessage) {\n client.send(\n encoder.encode(\n transformTRPCResponse(router._def._config, untransformedJSON),\n ),\n );\n }\n\n async function createCtxPromise(\n getConnectionParams: () => TRPCRequestInfo['connectionParams'],\n ): Promise<ContextResult> {\n try {\n return await run(async (): Promise<ContextResult> => {\n ctx = await createContext?.({\n req,\n res: client,\n info: {\n connectionParams: getConnectionParams(),\n calls: [],\n isBatchCall: false,\n accept: null,\n type: 'unknown',\n signal: abortController.signal,\n url: null,\n },\n });\n\n return {\n ok: true,\n value: ctx,\n };\n });\n } catch (cause) {\n const error = getTRPCErrorFromUnknown(cause);\n opts.onError?.({\n error,\n path: undefined,\n type: 'unknown',\n ctx,\n req,\n input: undefined,\n });\n respond({\n id: null,\n error: getErrorShape({\n config: router._def._config,\n error,\n type: 'unknown',\n path: undefined,\n input: undefined,\n ctx,\n }),\n });\n\n // close in next tick\n (globalThis.setImmediate ?? globalThis.setTimeout)(() => {\n client.close();\n });\n return {\n ok: false,\n error,\n };\n }\n }\n\n let ctx: Context | undefined = undefined;\n\n /**\n * promise for initializing the context\n *\n * - the context promise will be created immediately on connection if no connectionParams are expected\n * - if connection params are expected, they will be created once received\n */\n let ctxPromise =\n createURL(req).searchParams.get('connectionParams') === '1'\n ? null\n : createCtxPromise(() => null);\n\n function handleRequest(msg: TRPCClientOutgoingMessage) {\n const { id, jsonrpc } = msg;\n\n if (id === null) {\n const error = getTRPCErrorFromUnknown(\n new TRPCError({\n code: 'PARSE_ERROR',\n message: '`id` is required',\n }),\n );\n opts.onError?.({\n error,\n path: undefined,\n type: 'unknown',\n ctx,\n req,\n input: undefined,\n });\n respond({\n id,\n jsonrpc,\n error: getErrorShape({\n config: router._def._config,\n error,\n type: 'unknown',\n path: undefined,\n input: undefined,\n ctx,\n }),\n });\n return;\n }\n if (msg.method === 'subscription.stop') {\n clientSubscriptions.get(id)?.abort();\n return;\n }\n const { path, lastEventId } = msg.params;\n let { input } = msg.params;\n const type = msg.method;\n\n if (lastEventId !== undefined) {\n if (isObject(input)) {\n input = {\n ...input,\n lastEventId: lastEventId,\n };\n } else {\n input ??= {\n lastEventId: lastEventId,\n };\n }\n }\n run(async () => {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const res = await ctxPromise!; // asserts context has been set\n if (!res.ok) {\n throw res.error;\n }\n\n const abortController = new AbortController();\n const result = await callTRPCProcedure({\n router,\n path,\n getRawInput: async () => input,\n ctx,\n type,\n signal: abortController.signal,\n });\n\n const isIterableResult =\n isAsyncIterable(result) || isObservable(result);\n\n if (type !== 'subscription') {\n if (isIterableResult) {\n throw new TRPCError({\n code: 'UNSUPPORTED_MEDIA_TYPE',\n message: `Cannot return an async iterable or observable from a ${type} procedure with WebSockets`,\n });\n }\n // send the value as data if the method is not a subscription\n respond({\n id,\n jsonrpc,\n result: {\n type: 'data',\n data: result,\n },\n });\n return;\n }\n\n if (!isIterableResult) {\n throw new TRPCError({\n message: `Subscription ${path} did not return an observable or a AsyncGenerator`,\n code: 'INTERNAL_SERVER_ERROR',\n });\n }\n\n /* istanbul ignore next -- @preserve */\n if (client.readyState !== WEBSOCKET_OPEN) {\n // if the client got disconnected whilst initializing the subscription\n // no need to send stopped message if the client is disconnected\n\n return;\n }\n\n /* istanbul ignore next -- @preserve */\n if (clientSubscriptions.has(id)) {\n // duplicate request ids for client\n\n throw new TRPCError({\n message: `Duplicate id ${id}`,\n code: 'BAD_REQUEST',\n });\n }\n\n const iterable = isObservable(result)\n ? observableToAsyncIterable(result, abortController.signal)\n : result;\n\n run(async () => {\n await using iterator = iteratorResource(iterable);\n\n const abortPromise = new Promise<'abort'>((resolve) => {\n abortController.signal.onabort = () => resolve('abort');\n });\n // We need those declarations outside the loop for garbage collection reasons. If they\n // were declared inside, they would not be freed until the next value is present.\n let next:\n | null\n | TRPCError\n | Awaited<\n typeof abortPromise | ReturnType<(typeof iterator)['next']>\n >;\n let result: null | TRPCResultMessage<unknown>['result'];\n\n while (true) {\n next = await Unpromise.race([\n iterator.next().catch(getTRPCErrorFromUnknown),\n abortPromise,\n ]);\n\n if (next === 'abort') {\n await iterator.return?.();\n break;\n }\n if (next instanceof Error) {\n const error = getTRPCErrorFromUnknown(next);\n opts.onError?.({ error, path, type, ctx, req, input });\n respond({\n id,\n jsonrpc,\n error: getErrorShape({\n config: router._def._config,\n error,\n type,\n path,\n input,\n ctx,\n }),\n });\n break;\n }\n if (next.done) {\n break;\n }\n\n result = {\n type: 'data',\n data: next.value,\n };\n\n if (isTrackedEnvelope(next.value)) {\n const [id, data] = next.value;\n result.id = id;\n result.data = {\n id,\n data,\n };\n }\n\n respond({\n id,\n jsonrpc,\n result,\n });\n\n // free up references for garbage collection\n next = null;\n result = null;\n }\n\n respond({\n id,\n jsonrpc,\n result: {\n type: 'stopped',\n },\n });\n clientSubscriptions.delete(id);\n }).catch((cause) => {\n const error = getTRPCErrorFromUnknown(cause);\n opts.onError?.({ error, path, type, ctx, req, input });\n respond({\n id,\n jsonrpc,\n error: getErrorShape({\n config: router._def._config,\n error,\n type,\n path,\n input,\n ctx,\n }),\n });\n abortController.abort();\n });\n clientSubscriptions.set(id, abortController);\n\n respond({\n id,\n jsonrpc,\n result: {\n type: 'started',\n },\n });\n }).catch((cause) => {\n // procedure threw an error\n const error = getTRPCErrorFromUnknown(cause);\n opts.onError?.({ error, path, type, ctx, req, input });\n respond({\n id,\n jsonrpc,\n error: getErrorShape({\n config: router._def._config,\n error,\n type,\n path,\n input,\n ctx,\n }),\n });\n });\n }\n client.on('message', (rawData, isBinary) => {\n // Handle PING/PONG as text regardless of encoder\n if (!isBinary) {\n // eslint-disable-next-line @typescript-eslint/no-base-to-string\n const msgStr = rawData.toString();\n if (msgStr === 'PONG') {\n return;\n }\n if (msgStr === 'PING') {\n if (!opts.dangerouslyDisablePong) {\n client.send('PONG');\n }\n return;\n }\n }\n\n // Convert rawData to a format our encoder accepts\n // ws gives us Buffer for both text and binary frames\n if (!Buffer.isBuffer(rawData)) {\n const error = new TRPCError({\n code: 'UNPROCESSABLE_CONTENT',\n message: 'Unexpected WebSocket message format',\n });\n respond({\n id: null,\n error: getErrorShape({\n config: router._def._config,\n error,\n type: 'unknown',\n path: undefined,\n input: undefined,\n ctx,\n }),\n });\n return;\n }\n const data: string | Uint8Array = isBinary\n ? rawData\n : rawData.toString('utf8');\n\n if (!ctxPromise) {\n // If the ctxPromise wasn't created immediately, we're expecting the first message to be a TRPCConnectionParamsMessage\n ctxPromise = createCtxPromise(() => {\n let msg;\n try {\n msg = encoder.decode(data) as TRPCConnectionParamsMessage;\n\n if (!isObject(msg)) {\n throw new Error('Message was not an object');\n }\n } catch (cause) {\n throw new TRPCError({\n code: 'PARSE_ERROR',\n message: `Malformed TRPCConnectionParamsMessage`,\n cause,\n });\n }\n\n const connectionParams = parseConnectionParamsFromUnknown(msg.data);\n\n return connectionParams;\n });\n return;\n }\n\n const parsedMsgs = run(() => {\n try {\n const msgJSON: unknown = encoder.decode(data);\n const msgs: unknown[] = Array.isArray(msgJSON) ? msgJSON : [msgJSON];\n\n return msgs.map((raw) => parseTRPCMessage(raw, transformer));\n } catch (cause) {\n const error = new TRPCError({\n code: 'PARSE_ERROR',\n cause,\n });\n\n respond({\n id: null,\n error: getErrorShape({\n config: router._def._config,\n error,\n type: 'unknown',\n path: undefined,\n input: undefined,\n ctx,\n }),\n });\n\n return [];\n }\n });\n\n parsedMsgs.map(handleRequest);\n });\n\n // WebSocket errors should be handled, as otherwise unhandled exceptions will crash Node.js.\n // This line was introduced after the following error brought down production systems:\n // \"RangeError: Invalid WebSocket frame: RSV2 and RSV3 must be clear\"\n // Here is the relevant discussion: https://github.com/websockets/ws/issues/1354#issuecomment-774616962\n client.on('error', (cause) => {\n opts.onError?.({\n ctx,\n error: getTRPCErrorFromUnknown(cause),\n input: undefined,\n path: undefined,\n type: 'unknown',\n req,\n });\n });\n\n client.once('close', () => {\n for (const sub of clientSubscriptions.values()) {\n sub.abort();\n }\n clientSubscriptions.clear();\n abortController.abort();\n });\n };\n}\n\n/**\n * Handle WebSocket keep-alive messages\n */\nexport function handleKeepAlive(\n client: ws.WebSocket,\n pingMs = 30_000,\n pongWaitMs = 5_000,\n) {\n let timeout: NodeJS.Timeout | undefined = undefined;\n let ping: NodeJS.Timeout | undefined = undefined;\n\n const schedulePing = () => {\n const scheduleTimeout = () => {\n timeout = setTimeout(() => {\n client.terminate();\n }, pongWaitMs) as any;\n };\n ping = setTimeout(() => {\n client.send('PING');\n\n scheduleTimeout();\n }, pingMs) as any;\n };\n\n const onMessage = () => {\n clearTimeout(ping);\n clearTimeout(timeout);\n\n schedulePing();\n };\n\n client.on('message', onMessage);\n\n client.on('close', () => {\n clearTimeout(ping);\n clearTimeout(timeout);\n });\n\n schedulePing();\n}\n\nexport function applyWSSHandler<TRouter extends AnyRouter>(\n opts: WSSHandlerOptions<TRouter>,\n) {\n const encoder = opts.experimental_encoder ?? jsonEncoder;\n const onConnection = getWSConnectionHandler(opts);\n opts.wss.on('connection', (client, req) => {\n if (opts.prefix && !req.url?.startsWith(opts.prefix)) {\n return;\n }\n\n onConnection(client, req);\n });\n\n return {\n broadcastReconnectNotification: () => {\n const response: TRPCReconnectNotification = {\n id: null,\n method: 'reconnect',\n };\n const data = encoder.encode(response);\n for (const client of opts.wss.clients) {\n if (client.readyState === WEBSOCKET_OPEN) {\n client.send(data);\n }\n }\n },\n };\n}\n\nexport type { Encoder } from './wsEncoder';\nexport { jsonEncoder } from './wsEncoder';\n"],"mappings":"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"}
|
|
@@ -6,6 +6,20 @@ const require_resolveResponse = require('./resolveResponse-ByfQ6olt.cjs');
|
|
|
6
6
|
const require_observable = require('./observable-B1Nk6r1H.cjs');
|
|
7
7
|
const require_node_http = require('./node-http-D0T_XJ9C.cjs');
|
|
8
8
|
|
|
9
|
+
//#region src/adapters/wsEncoder.ts
|
|
10
|
+
/**
|
|
11
|
+
* Default JSON encoder - used when no encoder is specified.
|
|
12
|
+
* This maintains backwards compatibility with existing behavior.
|
|
13
|
+
*/
|
|
14
|
+
const jsonEncoder = {
|
|
15
|
+
encode: (data) => JSON.stringify(data),
|
|
16
|
+
decode: (data) => {
|
|
17
|
+
if (typeof data !== "string") throw new Error("jsonEncoder received binary data. JSON uses text frames. Use a binary encoder for binary data.");
|
|
18
|
+
return JSON.parse(data);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
9
23
|
//#region src/adapters/ws.ts
|
|
10
24
|
var import_objectSpread2 = require_getErrorShape.__toESM(require_getErrorShape.require_objectSpread2(), 1);
|
|
11
25
|
var import_usingCtx = require_getErrorShape.__toESM(require_resolveResponse.require_usingCtx(), 1);
|
|
@@ -15,8 +29,10 @@ var import_usingCtx = require_getErrorShape.__toESM(require_resolveResponse.requ
|
|
|
15
29
|
*/
|
|
16
30
|
const WEBSOCKET_OPEN = 1;
|
|
17
31
|
function getWSConnectionHandler(opts) {
|
|
32
|
+
var _opts$experimental_en;
|
|
18
33
|
const { createContext, router } = opts;
|
|
19
34
|
const { transformer } = router._def._config;
|
|
35
|
+
const encoder = (_opts$experimental_en = opts.experimental_encoder) !== null && _opts$experimental_en !== void 0 ? _opts$experimental_en : jsonEncoder;
|
|
20
36
|
return (client, req) => {
|
|
21
37
|
var _opts$keepAlive;
|
|
22
38
|
const clientSubscriptions = /* @__PURE__ */ new Map();
|
|
@@ -26,7 +42,7 @@ function getWSConnectionHandler(opts) {
|
|
|
26
42
|
handleKeepAlive(client, pingMs, pongWaitMs);
|
|
27
43
|
}
|
|
28
44
|
function respond(untransformedJSON) {
|
|
29
|
-
client.send(
|
|
45
|
+
client.send(encoder.encode(require_tracked.transformTRPCResponse(router._def._config, untransformedJSON)));
|
|
30
46
|
}
|
|
31
47
|
async function createCtxPromise(getConnectionParams) {
|
|
32
48
|
try {
|
|
@@ -300,18 +316,39 @@ function getWSConnectionHandler(opts) {
|
|
|
300
316
|
});
|
|
301
317
|
});
|
|
302
318
|
}
|
|
303
|
-
client.on("message", (rawData) => {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
if (
|
|
319
|
+
client.on("message", (rawData, isBinary) => {
|
|
320
|
+
if (!isBinary) {
|
|
321
|
+
const msgStr = rawData.toString();
|
|
322
|
+
if (msgStr === "PONG") return;
|
|
323
|
+
if (msgStr === "PING") {
|
|
324
|
+
if (!opts.dangerouslyDisablePong) client.send("PONG");
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (!Buffer.isBuffer(rawData)) {
|
|
329
|
+
const error = new require_tracked.TRPCError({
|
|
330
|
+
code: "UNPROCESSABLE_CONTENT",
|
|
331
|
+
message: "Unexpected WebSocket message format"
|
|
332
|
+
});
|
|
333
|
+
respond({
|
|
334
|
+
id: null,
|
|
335
|
+
error: require_getErrorShape.getErrorShape({
|
|
336
|
+
config: router._def._config,
|
|
337
|
+
error,
|
|
338
|
+
type: "unknown",
|
|
339
|
+
path: void 0,
|
|
340
|
+
input: void 0,
|
|
341
|
+
ctx
|
|
342
|
+
})
|
|
343
|
+
});
|
|
308
344
|
return;
|
|
309
345
|
}
|
|
346
|
+
const data = isBinary ? rawData : rawData.toString("utf8");
|
|
310
347
|
if (!ctxPromise) {
|
|
311
348
|
ctxPromise = createCtxPromise(() => {
|
|
312
349
|
let msg;
|
|
313
350
|
try {
|
|
314
|
-
msg =
|
|
351
|
+
msg = encoder.decode(data);
|
|
315
352
|
if (!require_codes.isObject(msg)) throw new Error("Message was not an object");
|
|
316
353
|
} catch (cause) {
|
|
317
354
|
throw new require_tracked.TRPCError({
|
|
@@ -327,7 +364,7 @@ function getWSConnectionHandler(opts) {
|
|
|
327
364
|
}
|
|
328
365
|
const parsedMsgs = require_codes.run(() => {
|
|
329
366
|
try {
|
|
330
|
-
const msgJSON =
|
|
367
|
+
const msgJSON = encoder.decode(data);
|
|
331
368
|
const msgs = Array.isArray(msgJSON) ? msgJSON : [msgJSON];
|
|
332
369
|
return msgs.map((raw) => require_parseTRPCMessage.parseTRPCMessage(raw, transformer));
|
|
333
370
|
} catch (cause) {
|
|
@@ -399,6 +436,8 @@ function handleKeepAlive(client, pingMs = 3e4, pongWaitMs = 5e3) {
|
|
|
399
436
|
schedulePing();
|
|
400
437
|
}
|
|
401
438
|
function applyWSSHandler(opts) {
|
|
439
|
+
var _opts$experimental_en2;
|
|
440
|
+
const encoder = (_opts$experimental_en2 = opts.experimental_encoder) !== null && _opts$experimental_en2 !== void 0 ? _opts$experimental_en2 : jsonEncoder;
|
|
402
441
|
const onConnection = getWSConnectionHandler(opts);
|
|
403
442
|
opts.wss.on("connection", (client, req) => {
|
|
404
443
|
var _req$url;
|
|
@@ -410,7 +449,7 @@ function applyWSSHandler(opts) {
|
|
|
410
449
|
id: null,
|
|
411
450
|
method: "reconnect"
|
|
412
451
|
};
|
|
413
|
-
const data =
|
|
452
|
+
const data = encoder.encode(response);
|
|
414
453
|
for (const client of opts.wss.clients) if (client.readyState === WEBSOCKET_OPEN) client.send(data);
|
|
415
454
|
} };
|
|
416
455
|
}
|
|
@@ -433,4 +472,10 @@ Object.defineProperty(exports, 'handleKeepAlive', {
|
|
|
433
472
|
get: function () {
|
|
434
473
|
return handleKeepAlive;
|
|
435
474
|
}
|
|
475
|
+
});
|
|
476
|
+
Object.defineProperty(exports, 'jsonEncoder', {
|
|
477
|
+
enumerable: true,
|
|
478
|
+
get: function () {
|
|
479
|
+
return jsonEncoder;
|
|
480
|
+
}
|
|
436
481
|
});
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@trpc/server",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "11.
|
|
5
|
+
"version": "11.9.0",
|
|
6
6
|
"description": "The tRPC server library",
|
|
7
7
|
"author": "KATT",
|
|
8
8
|
"license": "MIT",
|
|
@@ -194,7 +194,7 @@
|
|
|
194
194
|
},
|
|
195
195
|
"devDependencies": {
|
|
196
196
|
"@fastify/websocket": "^11.0.0",
|
|
197
|
-
"@oxc-project/runtime": "0.
|
|
197
|
+
"@oxc-project/runtime": "0.110.0",
|
|
198
198
|
"@tanstack/react-query": "^5.80.3",
|
|
199
199
|
"@types/aws-lambda": "^8.10.149",
|
|
200
200
|
"@types/express": "^5.0.0",
|
|
@@ -228,5 +228,5 @@
|
|
|
228
228
|
"peerDependencies": {
|
|
229
229
|
"typescript": ">=5.7.2"
|
|
230
230
|
},
|
|
231
|
-
"gitHead": "
|
|
231
|
+
"gitHead": "3beb5067de117bad47fcbd8ce4fbcd73817e872a"
|
|
232
232
|
}
|
package/src/adapters/ws.ts
CHANGED
|
@@ -39,6 +39,8 @@ import type { Result } from '../unstable-core-do-not-import';
|
|
|
39
39
|
import { iteratorResource } from '../unstable-core-do-not-import/stream/utils/asyncIterable';
|
|
40
40
|
import { Unpromise } from '../vendor/unpromise';
|
|
41
41
|
import { createURL, type NodeHTTPCreateContextFnOptions } from './node-http';
|
|
42
|
+
import type { Encoder } from './wsEncoder';
|
|
43
|
+
import { jsonEncoder } from './wsEncoder';
|
|
42
44
|
|
|
43
45
|
/**
|
|
44
46
|
* Importing ws causes a build error
|
|
@@ -98,6 +100,11 @@ export type WSSHandlerOptions<TRouter extends AnyRouter> =
|
|
|
98
100
|
* @default false
|
|
99
101
|
*/
|
|
100
102
|
dangerouslyDisablePong?: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Custom encoder for wire encoding (e.g. custom binary formats)
|
|
105
|
+
* @default jsonEncoder
|
|
106
|
+
*/
|
|
107
|
+
experimental_encoder?: Encoder;
|
|
101
108
|
};
|
|
102
109
|
|
|
103
110
|
export function getWSConnectionHandler<TRouter extends AnyRouter>(
|
|
@@ -105,6 +112,7 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
|
|
|
105
112
|
) {
|
|
106
113
|
const { createContext, router } = opts;
|
|
107
114
|
const { transformer } = router._def._config;
|
|
115
|
+
const encoder = opts.experimental_encoder ?? jsonEncoder;
|
|
108
116
|
|
|
109
117
|
return (client: ws.WebSocket, req: IncomingMessage) => {
|
|
110
118
|
type Context = inferRouterContext<TRouter>;
|
|
@@ -120,7 +128,7 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
|
|
|
120
128
|
|
|
121
129
|
function respond(untransformedJSON: TRPCResponseMessage) {
|
|
122
130
|
client.send(
|
|
123
|
-
|
|
131
|
+
encoder.encode(
|
|
124
132
|
transformTRPCResponse(router._def._config, untransformedJSON),
|
|
125
133
|
),
|
|
126
134
|
);
|
|
@@ -440,24 +448,52 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
|
|
|
440
448
|
});
|
|
441
449
|
});
|
|
442
450
|
}
|
|
443
|
-
client.on('message', (rawData) => {
|
|
444
|
-
//
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
451
|
+
client.on('message', (rawData, isBinary) => {
|
|
452
|
+
// Handle PING/PONG as text regardless of encoder
|
|
453
|
+
if (!isBinary) {
|
|
454
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
455
|
+
const msgStr = rawData.toString();
|
|
456
|
+
if (msgStr === 'PONG') {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (msgStr === 'PING') {
|
|
460
|
+
if (!opts.dangerouslyDisablePong) {
|
|
461
|
+
client.send('PONG');
|
|
462
|
+
}
|
|
463
|
+
return;
|
|
452
464
|
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Convert rawData to a format our encoder accepts
|
|
468
|
+
// ws gives us Buffer for both text and binary frames
|
|
469
|
+
if (!Buffer.isBuffer(rawData)) {
|
|
470
|
+
const error = new TRPCError({
|
|
471
|
+
code: 'UNPROCESSABLE_CONTENT',
|
|
472
|
+
message: 'Unexpected WebSocket message format',
|
|
473
|
+
});
|
|
474
|
+
respond({
|
|
475
|
+
id: null,
|
|
476
|
+
error: getErrorShape({
|
|
477
|
+
config: router._def._config,
|
|
478
|
+
error,
|
|
479
|
+
type: 'unknown',
|
|
480
|
+
path: undefined,
|
|
481
|
+
input: undefined,
|
|
482
|
+
ctx,
|
|
483
|
+
}),
|
|
484
|
+
});
|
|
453
485
|
return;
|
|
454
486
|
}
|
|
487
|
+
const data: string | Uint8Array = isBinary
|
|
488
|
+
? rawData
|
|
489
|
+
: rawData.toString('utf8');
|
|
490
|
+
|
|
455
491
|
if (!ctxPromise) {
|
|
456
492
|
// If the ctxPromise wasn't created immediately, we're expecting the first message to be a TRPCConnectionParamsMessage
|
|
457
493
|
ctxPromise = createCtxPromise(() => {
|
|
458
494
|
let msg;
|
|
459
495
|
try {
|
|
460
|
-
msg =
|
|
496
|
+
msg = encoder.decode(data) as TRPCConnectionParamsMessage;
|
|
461
497
|
|
|
462
498
|
if (!isObject(msg)) {
|
|
463
499
|
throw new Error('Message was not an object');
|
|
@@ -479,7 +515,7 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
|
|
|
479
515
|
|
|
480
516
|
const parsedMsgs = run(() => {
|
|
481
517
|
try {
|
|
482
|
-
const msgJSON: unknown =
|
|
518
|
+
const msgJSON: unknown = encoder.decode(data);
|
|
483
519
|
const msgs: unknown[] = Array.isArray(msgJSON) ? msgJSON : [msgJSON];
|
|
484
520
|
|
|
485
521
|
return msgs.map((raw) => parseTRPCMessage(raw, transformer));
|
|
@@ -577,6 +613,7 @@ export function handleKeepAlive(
|
|
|
577
613
|
export function applyWSSHandler<TRouter extends AnyRouter>(
|
|
578
614
|
opts: WSSHandlerOptions<TRouter>,
|
|
579
615
|
) {
|
|
616
|
+
const encoder = opts.experimental_encoder ?? jsonEncoder;
|
|
580
617
|
const onConnection = getWSConnectionHandler(opts);
|
|
581
618
|
opts.wss.on('connection', (client, req) => {
|
|
582
619
|
if (opts.prefix && !req.url?.startsWith(opts.prefix)) {
|
|
@@ -592,7 +629,7 @@ export function applyWSSHandler<TRouter extends AnyRouter>(
|
|
|
592
629
|
id: null,
|
|
593
630
|
method: 'reconnect',
|
|
594
631
|
};
|
|
595
|
-
const data =
|
|
632
|
+
const data = encoder.encode(response);
|
|
596
633
|
for (const client of opts.wss.clients) {
|
|
597
634
|
if (client.readyState === WEBSOCKET_OPEN) {
|
|
598
635
|
client.send(data);
|
|
@@ -601,3 +638,6 @@ export function applyWSSHandler<TRouter extends AnyRouter>(
|
|
|
601
638
|
},
|
|
602
639
|
};
|
|
603
640
|
}
|
|
641
|
+
|
|
642
|
+
export type { Encoder } from './wsEncoder';
|
|
643
|
+
export { jsonEncoder } from './wsEncoder';
|