@orpc/standard-server-peer 1.7.10 → 1.8.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/README.md +2 -1
- package/dist/index.d.mts +34 -4
- package/dist/index.d.ts +34 -4
- package/dist/index.mjs +247 -117
- package/package.json +3 -3
package/README.md
CHANGED
@@ -30,6 +30,7 @@
|
|
30
30
|
- **🔗 End-to-End Type Safety**: Ensure type-safe inputs, outputs, and errors from client to server.
|
31
31
|
- **📘 First-Class OpenAPI**: Built-in support that fully adheres to the OpenAPI standard.
|
32
32
|
- **📝 Contract-First Development**: Optionally define your API contract before implementation.
|
33
|
+
- **🔍 First-Class OpenTelemetry**: Seamlessly integrate with OpenTelemetry for observability.
|
33
34
|
- **⚙️ Framework Integrations**: Seamlessly integrate with TanStack Query (React, Vue, Solid, Svelte, Angular), Pinia Colada, and more.
|
34
35
|
- **🚀 Server Actions**: Fully compatible with React Server Actions on Next.js, TanStack Start, and other platforms.
|
35
36
|
- **🔠 Standard Schema Support**: Works out of the box with Zod, Valibot, ArkType, and other schema validators.
|
@@ -38,7 +39,6 @@
|
|
38
39
|
- **📡 SSE & Streaming**: Enjoy full type-safe support for SSE and streaming.
|
39
40
|
- **🌍 Multi-Runtime Support**: Fast and lightweight on Cloudflare, Deno, Bun, Node.js, and beyond.
|
40
41
|
- **🔌 Extendability**: Easily extend functionality with plugins, middleware, and interceptors.
|
41
|
-
- **🛡️ Reliability**: Well-tested, TypeScript-based, production-ready, and MIT licensed.
|
42
42
|
|
43
43
|
## Documentation
|
44
44
|
|
@@ -50,6 +50,7 @@ You can find the full documentation [here](https://orpc.unnoq.com).
|
|
50
50
|
- [@orpc/server](https://www.npmjs.com/package/@orpc/server): Build your API or implement API contract.
|
51
51
|
- [@orpc/client](https://www.npmjs.com/package/@orpc/client): Consume your API on the client with type-safety.
|
52
52
|
- [@orpc/openapi](https://www.npmjs.com/package/@orpc/openapi): Generate OpenAPI specs and handle OpenAPI requests.
|
53
|
+
- [@orpc/otel](https://www.npmjs.com/package/@orpc/otel): [OpenTelemetry](https://opentelemetry.io/) integration for observability.
|
53
54
|
- [@orpc/nest](https://www.npmjs.com/package/@orpc/nest): Deeply integrate oRPC with [NestJS](https://nestjs.com/).
|
54
55
|
- [@orpc/react](https://www.npmjs.com/package/@orpc/react): Utilities for integrating oRPC with React and React Server Actions.
|
55
56
|
- [@orpc/tanstack-query](https://www.npmjs.com/package/@orpc/tanstack-query): [TanStack Query](https://tanstack.com/query/latest) integration.
|
package/dist/index.d.mts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Promisable, AsyncIdQueueCloseOptions as AsyncIdQueueCloseOptions$1, AsyncIteratorClassCleanupFn, AsyncIteratorClass } from '@orpc/shared';
|
1
|
+
import { Promisable, AsyncIdQueueCloseOptions as AsyncIdQueueCloseOptions$1, SetSpanErrorOptions, AsyncIteratorClassCleanupFn, AsyncIteratorClass } from '@orpc/shared';
|
2
2
|
import { StandardRequest, StandardResponse, EventMeta } from '@orpc/standard-server';
|
3
3
|
|
4
4
|
type EncodedMessage = string | ArrayBufferLike | Uint8Array;
|
@@ -16,9 +16,22 @@ interface ClientPeerCloseOptions extends AsyncIdQueueCloseOptions$1 {
|
|
16
16
|
}
|
17
17
|
declare class ClientPeer {
|
18
18
|
private readonly idGenerator;
|
19
|
+
/**
|
20
|
+
* Queue of responses sent from server, awaiting consumption
|
21
|
+
*/
|
19
22
|
private readonly responseQueue;
|
23
|
+
/**
|
24
|
+
* Queue of event iterator messages sent from server, awaiting consumption
|
25
|
+
*/
|
20
26
|
private readonly serverEventIteratorQueue;
|
27
|
+
/**
|
28
|
+
* Controllers used to signal that the client should stop sending event iterator messages
|
29
|
+
*/
|
21
30
|
private readonly serverControllers;
|
31
|
+
/**
|
32
|
+
* Cleanup functions invoked when the request/response is closed
|
33
|
+
*/
|
34
|
+
private readonly cleanupFns;
|
22
35
|
private readonly send;
|
23
36
|
constructor(send: EncodedMessageSendFn);
|
24
37
|
get length(): number;
|
@@ -77,9 +90,14 @@ declare class AsyncIdQueue<T> {
|
|
77
90
|
assertOpen(id: string): void;
|
78
91
|
}
|
79
92
|
|
80
|
-
|
93
|
+
interface ToEventIteratorOptions extends SetSpanErrorOptions {
|
94
|
+
}
|
95
|
+
declare function toEventIterator(queue: AsyncIdQueue<EventIteratorPayload>, id: string, cleanup: AsyncIteratorClassCleanupFn, options?: ToEventIteratorOptions): AsyncIteratorClass<unknown>;
|
81
96
|
declare function resolveEventIterator(iterator: AsyncIterator<any>, callback: (payload: EventIteratorPayload) => Promise<'next' | 'abort'>): Promise<void>;
|
82
97
|
|
98
|
+
interface ServerPeerHandleRequestFn {
|
99
|
+
(request: StandardRequest): Promise<StandardResponse>;
|
100
|
+
}
|
83
101
|
interface ServerPeerCloseOptions extends AsyncIdQueueCloseOptions$1 {
|
84
102
|
/**
|
85
103
|
* Should abort or not?
|
@@ -89,16 +107,28 @@ interface ServerPeerCloseOptions extends AsyncIdQueueCloseOptions$1 {
|
|
89
107
|
abort?: boolean;
|
90
108
|
}
|
91
109
|
declare class ServerPeer {
|
110
|
+
/**
|
111
|
+
* Queue of event iterator messages sent from client, awaiting consumption
|
112
|
+
*/
|
92
113
|
private readonly clientEventIteratorQueue;
|
114
|
+
/**
|
115
|
+
* Map of active client request controllers, should be synced to request signal
|
116
|
+
*/
|
93
117
|
private readonly clientControllers;
|
94
118
|
private readonly send;
|
95
119
|
constructor(send: EncodedMessageSendFn);
|
96
120
|
get length(): number;
|
97
121
|
open(id: string): AbortController;
|
98
|
-
|
122
|
+
/**
|
123
|
+
* @todo This method will return Promise<void> in the next major version.
|
124
|
+
*/
|
125
|
+
message(raw: EncodedMessage, handleRequest?: ServerPeerHandleRequestFn): Promise<[id: string, StandardRequest | undefined]>;
|
126
|
+
/**
|
127
|
+
* @deprecated Please pass the `handleRequest` (second arg) function to the `message` method instead.
|
128
|
+
*/
|
99
129
|
response(id: string, response: StandardResponse): Promise<void>;
|
100
130
|
close({ abort, ...options }?: ServerPeerCloseOptions): void;
|
101
131
|
}
|
102
132
|
|
103
133
|
export { ClientPeer, MessageType, ServerPeer, decodeRequestMessage, decodeResponseMessage, encodeRequestMessage, encodeResponseMessage, resolveEventIterator, toEventIterator };
|
104
|
-
export type { ClientPeerCloseOptions, DecodedRequestMessage, DecodedResponseMessage, EncodedMessage, EncodedMessageSendFn, EventIteratorEvent, EventIteratorPayload, RequestMessageMap, ResponseMessageMap, ServerPeerCloseOptions };
|
134
|
+
export type { ClientPeerCloseOptions, DecodedRequestMessage, DecodedResponseMessage, EncodedMessage, EncodedMessageSendFn, EventIteratorEvent, EventIteratorPayload, RequestMessageMap, ResponseMessageMap, ServerPeerCloseOptions, ServerPeerHandleRequestFn, ToEventIteratorOptions };
|
package/dist/index.d.ts
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Promisable, AsyncIdQueueCloseOptions as AsyncIdQueueCloseOptions$1, AsyncIteratorClassCleanupFn, AsyncIteratorClass } from '@orpc/shared';
|
1
|
+
import { Promisable, AsyncIdQueueCloseOptions as AsyncIdQueueCloseOptions$1, SetSpanErrorOptions, AsyncIteratorClassCleanupFn, AsyncIteratorClass } from '@orpc/shared';
|
2
2
|
import { StandardRequest, StandardResponse, EventMeta } from '@orpc/standard-server';
|
3
3
|
|
4
4
|
type EncodedMessage = string | ArrayBufferLike | Uint8Array;
|
@@ -16,9 +16,22 @@ interface ClientPeerCloseOptions extends AsyncIdQueueCloseOptions$1 {
|
|
16
16
|
}
|
17
17
|
declare class ClientPeer {
|
18
18
|
private readonly idGenerator;
|
19
|
+
/**
|
20
|
+
* Queue of responses sent from server, awaiting consumption
|
21
|
+
*/
|
19
22
|
private readonly responseQueue;
|
23
|
+
/**
|
24
|
+
* Queue of event iterator messages sent from server, awaiting consumption
|
25
|
+
*/
|
20
26
|
private readonly serverEventIteratorQueue;
|
27
|
+
/**
|
28
|
+
* Controllers used to signal that the client should stop sending event iterator messages
|
29
|
+
*/
|
21
30
|
private readonly serverControllers;
|
31
|
+
/**
|
32
|
+
* Cleanup functions invoked when the request/response is closed
|
33
|
+
*/
|
34
|
+
private readonly cleanupFns;
|
22
35
|
private readonly send;
|
23
36
|
constructor(send: EncodedMessageSendFn);
|
24
37
|
get length(): number;
|
@@ -77,9 +90,14 @@ declare class AsyncIdQueue<T> {
|
|
77
90
|
assertOpen(id: string): void;
|
78
91
|
}
|
79
92
|
|
80
|
-
|
93
|
+
interface ToEventIteratorOptions extends SetSpanErrorOptions {
|
94
|
+
}
|
95
|
+
declare function toEventIterator(queue: AsyncIdQueue<EventIteratorPayload>, id: string, cleanup: AsyncIteratorClassCleanupFn, options?: ToEventIteratorOptions): AsyncIteratorClass<unknown>;
|
81
96
|
declare function resolveEventIterator(iterator: AsyncIterator<any>, callback: (payload: EventIteratorPayload) => Promise<'next' | 'abort'>): Promise<void>;
|
82
97
|
|
98
|
+
interface ServerPeerHandleRequestFn {
|
99
|
+
(request: StandardRequest): Promise<StandardResponse>;
|
100
|
+
}
|
83
101
|
interface ServerPeerCloseOptions extends AsyncIdQueueCloseOptions$1 {
|
84
102
|
/**
|
85
103
|
* Should abort or not?
|
@@ -89,16 +107,28 @@ interface ServerPeerCloseOptions extends AsyncIdQueueCloseOptions$1 {
|
|
89
107
|
abort?: boolean;
|
90
108
|
}
|
91
109
|
declare class ServerPeer {
|
110
|
+
/**
|
111
|
+
* Queue of event iterator messages sent from client, awaiting consumption
|
112
|
+
*/
|
92
113
|
private readonly clientEventIteratorQueue;
|
114
|
+
/**
|
115
|
+
* Map of active client request controllers, should be synced to request signal
|
116
|
+
*/
|
93
117
|
private readonly clientControllers;
|
94
118
|
private readonly send;
|
95
119
|
constructor(send: EncodedMessageSendFn);
|
96
120
|
get length(): number;
|
97
121
|
open(id: string): AbortController;
|
98
|
-
|
122
|
+
/**
|
123
|
+
* @todo This method will return Promise<void> in the next major version.
|
124
|
+
*/
|
125
|
+
message(raw: EncodedMessage, handleRequest?: ServerPeerHandleRequestFn): Promise<[id: string, StandardRequest | undefined]>;
|
126
|
+
/**
|
127
|
+
* @deprecated Please pass the `handleRequest` (second arg) function to the `message` method instead.
|
128
|
+
*/
|
99
129
|
response(id: string, response: StandardResponse): Promise<void>;
|
100
130
|
close({ abort, ...options }?: ServerPeerCloseOptions): void;
|
101
131
|
}
|
102
132
|
|
103
133
|
export { ClientPeer, MessageType, ServerPeer, decodeRequestMessage, decodeResponseMessage, encodeRequestMessage, encodeResponseMessage, resolveEventIterator, toEventIterator };
|
104
|
-
export type { ClientPeerCloseOptions, DecodedRequestMessage, DecodedResponseMessage, EncodedMessage, EncodedMessageSendFn, EventIteratorEvent, EventIteratorPayload, RequestMessageMap, ResponseMessageMap, ServerPeerCloseOptions };
|
134
|
+
export type { ClientPeerCloseOptions, DecodedRequestMessage, DecodedResponseMessage, EncodedMessage, EncodedMessageSendFn, EventIteratorEvent, EventIteratorPayload, RequestMessageMap, ResponseMessageMap, ServerPeerCloseOptions, ServerPeerHandleRequestFn, ToEventIteratorOptions };
|
package/dist/index.mjs
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
import { isAsyncIteratorObject, stringifyJSON, readAsBuffer, AsyncIteratorClass, isTypescriptObject, SequentialIdGenerator, AsyncIdQueue } from '@orpc/shared';
|
2
|
-
import { generateContentDisposition, flattenHeader, getFilenameFromContentDisposition, withEventMeta, ErrorEvent, getEventMeta, isEventIteratorHeaders,
|
1
|
+
import { isAsyncIteratorObject, stringifyJSON, readAsBuffer, AsyncIteratorClass, startSpan, runInSpanContext, isTypescriptObject, setSpanError, runWithSpan, SequentialIdGenerator, AsyncIdQueue, getGlobalOtelConfig, clone, AbortError } from '@orpc/shared';
|
2
|
+
import { generateContentDisposition, flattenHeader, getFilenameFromContentDisposition, withEventMeta, ErrorEvent, getEventMeta, isEventIteratorHeaders, HibernationEventIterator } from '@orpc/standard-server';
|
3
3
|
|
4
4
|
var MessageType = /* @__PURE__ */ ((MessageType2) => {
|
5
5
|
MessageType2[MessageType2["REQUEST"] = 1] = "REQUEST";
|
@@ -179,80 +179,138 @@ async function decodeRawMessage(raw) {
|
|
179
179
|
};
|
180
180
|
}
|
181
181
|
|
182
|
-
function toEventIterator(queue, id, cleanup) {
|
182
|
+
function toEventIterator(queue, id, cleanup, options = {}) {
|
183
|
+
let span;
|
183
184
|
return new AsyncIteratorClass(async () => {
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
data =
|
185
|
+
span ??= startSpan("consume_event_iterator_stream");
|
186
|
+
try {
|
187
|
+
const item = await runInSpanContext(span, () => queue.pull(id));
|
188
|
+
switch (item.event) {
|
189
|
+
case "message": {
|
190
|
+
let data = item.data;
|
191
|
+
if (item.meta && isTypescriptObject(data)) {
|
192
|
+
data = withEventMeta(data, item.meta);
|
193
|
+
}
|
194
|
+
span?.addEvent("message");
|
195
|
+
return { value: data, done: false };
|
190
196
|
}
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
197
|
+
case "error": {
|
198
|
+
let error = new ErrorEvent({
|
199
|
+
data: item.data
|
200
|
+
});
|
201
|
+
if (item.meta) {
|
202
|
+
error = withEventMeta(error, item.meta);
|
203
|
+
}
|
204
|
+
span?.addEvent("error");
|
205
|
+
throw error;
|
199
206
|
}
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
207
|
+
case "done": {
|
208
|
+
let data = item.data;
|
209
|
+
if (item.meta && isTypescriptObject(data)) {
|
210
|
+
data = withEventMeta(data, item.meta);
|
211
|
+
}
|
212
|
+
span?.addEvent("done");
|
213
|
+
return { value: data, done: true };
|
206
214
|
}
|
207
|
-
return { value: data, done: true };
|
208
215
|
}
|
209
|
-
}
|
210
|
-
|
211
|
-
|
212
|
-
async function resolveEventIterator(iterator, callback) {
|
213
|
-
while (true) {
|
214
|
-
const payload = await (async () => {
|
215
|
-
try {
|
216
|
-
const { value, done } = await iterator.next();
|
217
|
-
if (done) {
|
218
|
-
return { event: "done", data: value, meta: getEventMeta(value) };
|
219
|
-
}
|
220
|
-
return { event: "message", data: value, meta: getEventMeta(value) };
|
221
|
-
} catch (err) {
|
222
|
-
return {
|
223
|
-
meta: getEventMeta(err),
|
224
|
-
event: "error",
|
225
|
-
data: err instanceof ErrorEvent ? err.data : void 0
|
226
|
-
};
|
216
|
+
} catch (e) {
|
217
|
+
if (!(e instanceof ErrorEvent)) {
|
218
|
+
setSpanError(span, e, options);
|
227
219
|
}
|
228
|
-
|
220
|
+
throw e;
|
221
|
+
}
|
222
|
+
}, async (reason) => {
|
229
223
|
try {
|
230
|
-
|
231
|
-
|
232
|
-
return;
|
224
|
+
if (reason !== "next") {
|
225
|
+
span?.addEvent("cancelled");
|
233
226
|
}
|
234
|
-
|
227
|
+
await runInSpanContext(span, () => cleanup(reason));
|
228
|
+
} catch (e) {
|
229
|
+
setSpanError(span, e, options);
|
230
|
+
throw e;
|
231
|
+
} finally {
|
232
|
+
span?.end();
|
233
|
+
}
|
234
|
+
});
|
235
|
+
}
|
236
|
+
function resolveEventIterator(iterator, callback) {
|
237
|
+
return runWithSpan(
|
238
|
+
{ name: "stream_event_iterator" },
|
239
|
+
async (span) => {
|
240
|
+
while (true) {
|
241
|
+
const payload = await (async () => {
|
242
|
+
try {
|
243
|
+
const { value, done } = await iterator.next();
|
244
|
+
if (done) {
|
245
|
+
span?.addEvent("done");
|
246
|
+
return { event: "done", data: value, meta: getEventMeta(value) };
|
247
|
+
}
|
248
|
+
span?.addEvent("message");
|
249
|
+
return { event: "message", data: value, meta: getEventMeta(value) };
|
250
|
+
} catch (err) {
|
251
|
+
if (err instanceof ErrorEvent) {
|
252
|
+
span?.addEvent("error");
|
253
|
+
return {
|
254
|
+
event: "error",
|
255
|
+
data: err.data,
|
256
|
+
meta: getEventMeta(err)
|
257
|
+
};
|
258
|
+
} else {
|
259
|
+
try {
|
260
|
+
await callback({ event: "error", data: void 0 });
|
261
|
+
} catch (err2) {
|
262
|
+
setSpanError(span, err);
|
263
|
+
throw err2;
|
264
|
+
}
|
265
|
+
throw err;
|
266
|
+
}
|
267
|
+
}
|
268
|
+
})();
|
269
|
+
let isInvokeCleanupFn = false;
|
235
270
|
try {
|
236
|
-
await
|
237
|
-
|
271
|
+
const direction = await callback(payload);
|
272
|
+
if (payload.event === "done" || payload.event === "error") {
|
273
|
+
return;
|
274
|
+
}
|
275
|
+
if (direction === "abort") {
|
276
|
+
isInvokeCleanupFn = true;
|
277
|
+
await iterator.return?.();
|
278
|
+
return;
|
279
|
+
}
|
280
|
+
} catch (err) {
|
281
|
+
if (!isInvokeCleanupFn) {
|
282
|
+
try {
|
283
|
+
await iterator.return?.();
|
284
|
+
} catch (err2) {
|
285
|
+
setSpanError(span, err);
|
286
|
+
throw err2;
|
287
|
+
}
|
288
|
+
}
|
289
|
+
throw err;
|
238
290
|
}
|
239
|
-
return;
|
240
|
-
}
|
241
|
-
} catch (err) {
|
242
|
-
try {
|
243
|
-
await iterator.return?.();
|
244
|
-
} catch {
|
245
291
|
}
|
246
|
-
throw err;
|
247
292
|
}
|
248
|
-
|
293
|
+
);
|
249
294
|
}
|
250
295
|
|
251
296
|
class ClientPeer {
|
252
297
|
idGenerator = new SequentialIdGenerator();
|
298
|
+
/**
|
299
|
+
* Queue of responses sent from server, awaiting consumption
|
300
|
+
*/
|
253
301
|
responseQueue = new AsyncIdQueue();
|
302
|
+
/**
|
303
|
+
* Queue of event iterator messages sent from server, awaiting consumption
|
304
|
+
*/
|
254
305
|
serverEventIteratorQueue = new AsyncIdQueue();
|
306
|
+
/**
|
307
|
+
* Controllers used to signal that the client should stop sending event iterator messages
|
308
|
+
*/
|
255
309
|
serverControllers = /* @__PURE__ */ new Map();
|
310
|
+
/**
|
311
|
+
* Cleanup functions invoked when the request/response is closed
|
312
|
+
*/
|
313
|
+
cleanupFns = /* @__PURE__ */ new Map();
|
256
314
|
send;
|
257
315
|
constructor(send) {
|
258
316
|
this.send = async (id, ...rest) => encodeRequestMessage(id, ...rest).then(async (raw) => {
|
@@ -262,48 +320,85 @@ class ClientPeer {
|
|
262
320
|
});
|
263
321
|
}
|
264
322
|
get length() {
|
265
|
-
return (+this.responseQueue.length + this.serverEventIteratorQueue.length + this.serverControllers.size) /
|
323
|
+
return (+this.responseQueue.length + this.serverEventIteratorQueue.length + this.serverControllers.size + this.cleanupFns.size) / 4;
|
266
324
|
}
|
267
325
|
open(id) {
|
268
326
|
this.serverEventIteratorQueue.open(id);
|
269
327
|
this.responseQueue.open(id);
|
270
328
|
const controller = new AbortController();
|
271
329
|
this.serverControllers.set(id, controller);
|
330
|
+
this.cleanupFns.set(id, []);
|
272
331
|
return controller;
|
273
332
|
}
|
274
333
|
async request(request) {
|
275
334
|
const signal = request.signal;
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
const id = this.idGenerator.generate();
|
280
|
-
const serverController = this.open(id);
|
281
|
-
return new Promise((resolve, reject) => {
|
282
|
-
this.send(id, MessageType.REQUEST, request).then(async () => {
|
335
|
+
return runWithSpan(
|
336
|
+
{ name: "send_peer_request", signal },
|
337
|
+
async () => {
|
283
338
|
if (signal?.aborted) {
|
284
|
-
|
285
|
-
this.close({ id, reason: signal.reason });
|
286
|
-
return;
|
339
|
+
throw signal.reason;
|
287
340
|
}
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
341
|
+
const id = this.idGenerator.generate();
|
342
|
+
const serverController = this.open(id);
|
343
|
+
try {
|
344
|
+
const otelConfig = getGlobalOtelConfig();
|
345
|
+
if (otelConfig) {
|
346
|
+
const headers = clone(request.headers);
|
347
|
+
otelConfig.propagation.inject(otelConfig.context.active(), headers);
|
348
|
+
request = { ...request, headers };
|
349
|
+
}
|
350
|
+
await this.send(id, MessageType.REQUEST, request);
|
351
|
+
if (signal?.aborted) {
|
352
|
+
await this.send(id, MessageType.ABORT_SIGNAL, void 0);
|
353
|
+
throw signal.reason;
|
354
|
+
}
|
355
|
+
let abortListener;
|
356
|
+
signal?.addEventListener("abort", abortListener = async () => {
|
357
|
+
await this.send(id, MessageType.ABORT_SIGNAL, void 0);
|
358
|
+
this.close({ id, reason: signal.reason });
|
359
|
+
}, { once: true });
|
360
|
+
this.cleanupFns.get(id)?.push(() => {
|
361
|
+
signal?.removeEventListener("abort", abortListener);
|
299
362
|
});
|
363
|
+
if (isAsyncIteratorObject(request.body)) {
|
364
|
+
const iterator = request.body;
|
365
|
+
void resolveEventIterator(iterator, async (payload) => {
|
366
|
+
if (serverController.signal.aborted) {
|
367
|
+
return "abort";
|
368
|
+
}
|
369
|
+
await this.send(id, MessageType.EVENT_ITERATOR, payload);
|
370
|
+
return "next";
|
371
|
+
});
|
372
|
+
}
|
373
|
+
const response = await this.responseQueue.pull(id);
|
374
|
+
if (isEventIteratorHeaders(response.headers)) {
|
375
|
+
const iterator = toEventIterator(
|
376
|
+
this.serverEventIteratorQueue,
|
377
|
+
id,
|
378
|
+
async (reason) => {
|
379
|
+
try {
|
380
|
+
if (reason !== "next") {
|
381
|
+
await this.send(id, MessageType.ABORT_SIGNAL, void 0);
|
382
|
+
}
|
383
|
+
} finally {
|
384
|
+
this.close({ id });
|
385
|
+
}
|
386
|
+
},
|
387
|
+
{ signal }
|
388
|
+
);
|
389
|
+
return {
|
390
|
+
...response,
|
391
|
+
body: iterator
|
392
|
+
};
|
393
|
+
}
|
394
|
+
this.close({ id });
|
395
|
+
return response;
|
396
|
+
} catch (err) {
|
397
|
+
this.close({ id, reason: err });
|
398
|
+
throw err;
|
300
399
|
}
|
301
|
-
}
|
302
|
-
|
303
|
-
reject(err);
|
304
|
-
});
|
305
|
-
this.responseQueue.pull(id).then(resolve).catch(reject);
|
306
|
-
});
|
400
|
+
}
|
401
|
+
);
|
307
402
|
}
|
308
403
|
async message(raw) {
|
309
404
|
const [id, type, payload] = await decodeResponseMessage(raw);
|
@@ -320,31 +415,19 @@ class ClientPeer {
|
|
320
415
|
if (!this.responseQueue.isOpen(id)) {
|
321
416
|
return;
|
322
417
|
}
|
323
|
-
|
324
|
-
this.responseQueue.push(id, {
|
325
|
-
...payload,
|
326
|
-
body: toEventIterator(this.serverEventIteratorQueue, id, async (reason) => {
|
327
|
-
try {
|
328
|
-
if (reason !== "next") {
|
329
|
-
await this.send(id, MessageType.ABORT_SIGNAL, void 0);
|
330
|
-
}
|
331
|
-
} finally {
|
332
|
-
this.close({ id });
|
333
|
-
}
|
334
|
-
})
|
335
|
-
});
|
336
|
-
} else {
|
337
|
-
this.responseQueue.push(id, payload);
|
338
|
-
this.close({ id });
|
339
|
-
}
|
418
|
+
this.responseQueue.push(id, payload);
|
340
419
|
}
|
341
420
|
close(options = {}) {
|
342
421
|
if (options.id !== void 0) {
|
343
422
|
this.serverControllers.get(options.id)?.abort(options.reason);
|
344
423
|
this.serverControllers.delete(options.id);
|
424
|
+
this.cleanupFns.get(options.id)?.forEach((fn) => fn());
|
425
|
+
this.cleanupFns.delete(options.id);
|
345
426
|
} else {
|
346
427
|
this.serverControllers.forEach((c) => c.abort(options.reason));
|
347
428
|
this.serverControllers.clear();
|
429
|
+
this.cleanupFns.forEach((fns) => fns.forEach((fn) => fn()));
|
430
|
+
this.cleanupFns.clear();
|
348
431
|
}
|
349
432
|
this.responseQueue.close(options);
|
350
433
|
this.serverEventIteratorQueue.close(options);
|
@@ -352,7 +435,13 @@ class ClientPeer {
|
|
352
435
|
}
|
353
436
|
|
354
437
|
class ServerPeer {
|
438
|
+
/**
|
439
|
+
* Queue of event iterator messages sent from client, awaiting consumption
|
440
|
+
*/
|
355
441
|
clientEventIteratorQueue = new AsyncIdQueue();
|
442
|
+
/**
|
443
|
+
* Map of active client request controllers, should be synced to request signal
|
444
|
+
*/
|
356
445
|
clientControllers = /* @__PURE__ */ new Map();
|
357
446
|
send;
|
358
447
|
constructor(send) {
|
@@ -371,10 +460,13 @@ class ServerPeer {
|
|
371
460
|
this.clientControllers.set(id, controller);
|
372
461
|
return controller;
|
373
462
|
}
|
374
|
-
|
463
|
+
/**
|
464
|
+
* @todo This method will return Promise<void> in the next major version.
|
465
|
+
*/
|
466
|
+
async message(raw, handleRequest) {
|
375
467
|
const [id, type, payload] = await decodeRequestMessage(raw);
|
376
468
|
if (type === MessageType.ABORT_SIGNAL) {
|
377
|
-
this.close({ id });
|
469
|
+
this.close({ id, reason: new AbortError("Client aborted the request") });
|
378
470
|
return [id, void 0];
|
379
471
|
}
|
380
472
|
if (type === MessageType.EVENT_ITERATOR) {
|
@@ -384,28 +476,66 @@ class ServerPeer {
|
|
384
476
|
return [id, void 0];
|
385
477
|
}
|
386
478
|
const clientController = this.open(id);
|
479
|
+
const signal = clientController.signal;
|
387
480
|
const request = {
|
388
481
|
...payload,
|
389
|
-
signal
|
390
|
-
body: isEventIteratorHeaders(payload.headers) ? toEventIterator(
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
482
|
+
signal,
|
483
|
+
body: isEventIteratorHeaders(payload.headers) ? toEventIterator(
|
484
|
+
this.clientEventIteratorQueue,
|
485
|
+
id,
|
486
|
+
async (reason) => {
|
487
|
+
if (reason !== "next") {
|
488
|
+
await this.send(id, MessageType.ABORT_SIGNAL, void 0);
|
489
|
+
}
|
490
|
+
},
|
491
|
+
{ signal }
|
492
|
+
) : payload.body
|
395
493
|
};
|
494
|
+
if (handleRequest) {
|
495
|
+
let context;
|
496
|
+
const otelConfig = getGlobalOtelConfig();
|
497
|
+
if (otelConfig) {
|
498
|
+
context = otelConfig.propagation.extract(otelConfig.context.active(), request.headers);
|
499
|
+
}
|
500
|
+
await runWithSpan(
|
501
|
+
{ name: "receive_peer_request", context },
|
502
|
+
async () => {
|
503
|
+
const response = await runWithSpan(
|
504
|
+
{ name: "handle_request" },
|
505
|
+
async () => {
|
506
|
+
try {
|
507
|
+
return await handleRequest(request);
|
508
|
+
} catch (reason) {
|
509
|
+
this.close({ id, reason, abort: false });
|
510
|
+
throw reason;
|
511
|
+
}
|
512
|
+
}
|
513
|
+
);
|
514
|
+
await runWithSpan(
|
515
|
+
{ name: "send_peer_response" },
|
516
|
+
() => this.response(id, response)
|
517
|
+
);
|
518
|
+
}
|
519
|
+
);
|
520
|
+
}
|
396
521
|
return [id, request];
|
397
522
|
}
|
523
|
+
/**
|
524
|
+
* @deprecated Please pass the `handleRequest` (second arg) function to the `message` method instead.
|
525
|
+
*/
|
398
526
|
async response(id, response) {
|
399
527
|
const signal = this.clientControllers.get(id)?.signal;
|
400
528
|
if (!signal || signal.aborted) {
|
401
529
|
return;
|
402
530
|
}
|
403
|
-
|
531
|
+
try {
|
532
|
+
await this.send(id, MessageType.RESPONSE, response);
|
404
533
|
if (!signal.aborted && isAsyncIteratorObject(response.body)) {
|
405
|
-
if (response.body instanceof
|
534
|
+
if (response.body instanceof HibernationEventIterator) {
|
406
535
|
response.body.hibernationCallback?.(id);
|
407
536
|
} else {
|
408
|
-
|
537
|
+
const iterator = response.body;
|
538
|
+
await resolveEventIterator(iterator, async (payload) => {
|
409
539
|
if (signal.aborted) {
|
410
540
|
return "abort";
|
411
541
|
}
|
@@ -415,20 +545,20 @@ class ServerPeer {
|
|
415
545
|
}
|
416
546
|
}
|
417
547
|
this.close({ id, abort: false });
|
418
|
-
}
|
548
|
+
} catch (reason) {
|
419
549
|
this.close({ id, reason, abort: false });
|
420
550
|
throw reason;
|
421
|
-
}
|
551
|
+
}
|
422
552
|
}
|
423
553
|
close({ abort = true, ...options } = {}) {
|
424
554
|
if (options.id === void 0) {
|
425
555
|
if (abort) {
|
426
|
-
this.clientControllers.forEach((c) => c.abort());
|
556
|
+
this.clientControllers.forEach((c) => c.abort(options.reason));
|
427
557
|
}
|
428
558
|
this.clientControllers.clear();
|
429
559
|
} else {
|
430
560
|
if (abort) {
|
431
|
-
this.clientControllers.get(options.id)?.abort();
|
561
|
+
this.clientControllers.get(options.id)?.abort(options.reason);
|
432
562
|
}
|
433
563
|
this.clientControllers.delete(options.id);
|
434
564
|
}
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@orpc/standard-server-peer",
|
3
3
|
"type": "module",
|
4
|
-
"version": "1.
|
4
|
+
"version": "1.8.0",
|
5
5
|
"license": "MIT",
|
6
6
|
"homepage": "https://unnoq.com",
|
7
7
|
"repository": {
|
@@ -23,8 +23,8 @@
|
|
23
23
|
"dist"
|
24
24
|
],
|
25
25
|
"dependencies": {
|
26
|
-
"@orpc/shared": "1.
|
27
|
-
"@orpc/standard-server": "1.
|
26
|
+
"@orpc/shared": "1.8.0",
|
27
|
+
"@orpc/standard-server": "1.8.0"
|
28
28
|
},
|
29
29
|
"scripts": {
|
30
30
|
"build": "unbuild",
|