@orpc/server 2.0.0-beta.16 → 2.0.0-beta.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -175,7 +175,7 @@ Like what we build over at [middleapi](https://github.com/middleapi)? You can he
175
175
  <a href="https://github.com/illarionvk?ref=orpc" target="_blank" rel="noopener" title="Illarion Koperski"><img src="https://avatars.githubusercontent.com/u/5012724?u=7cfa13652f7ac5fb3c56d880e3eb3fbe40c3ea34&amp;v=4" width="32" height="32" alt="Illarion Koperski" /></a>
176
176
  <a href="https://github.com/steelbrain?ref=orpc" target="_blank" rel="noopener" title="Anees Iqbal"><img src="https://avatars.githubusercontent.com/u/4278113?u=22b80b5399eed68ac76cd58b02961b0481f1db11&amp;v=4" width="32" height="32" alt="Anees Iqbal" /></a>
177
177
  <a href="https://github.com/Scrumplex?ref=orpc" target="_blank" rel="noopener" title="Sefa Eyeoglu"><img src="https://avatars.githubusercontent.com/u/11587657?u=ab503582165c0bbff0cca47ce31c9450bb1553c9&amp;v=4" width="32" height="32" alt="Sefa Eyeoglu" /></a>
178
- <a href="https://github.com/nattstack?ref=orpc" target="_blank" rel="noopener" title="NΛTT"><img src="https://avatars.githubusercontent.com/u/31426677?u=fa9dbb8b3e66eb0ea3c88db5dc07f31c8c5418fe&amp;v=4" width="32" height="32" alt="NΛTT" /></a>
178
+ <a href="https://github.com/nattstack?ref=orpc" target="_blank" rel="noopener" title="natt"><img src="https://avatars.githubusercontent.com/u/31426677?u=fa9dbb8b3e66eb0ea3c88db5dc07f31c8c5418fe&amp;v=4" width="32" height="32" alt="natt" /></a>
179
179
  <a href="https://github.com/ChromeGG?ref=orpc" target="_blank" rel="noopener" title="Adam Tkaczyk"><img src="https://avatars.githubusercontent.com/u/39050595?u=a58ca6042a6950e94e6e92442db76ef584279bc0&amp;v=4" width="32" height="32" alt="Adam Tkaczyk" /></a>
180
180
  <a href="https://github.com/plancraft?ref=orpc" target="_blank" rel="noopener" title="plancraft"><img src="https://avatars.githubusercontent.com/u/46482287?v=4" width="32" height="32" alt="plancraft" /></a>
181
181
  </p>
@@ -30,6 +30,29 @@ interface BatchHandlerPluginOptions<T extends Context> {
30
30
  * @default {}
31
31
  */
32
32
  headers?: Value<Promisable<StandardHeaders>, [batchOptions: StandardHandlerRoutingInterceptorOptions<T>]>;
33
+ /**
34
+ * Keep-alive settings for streaming batch responses.
35
+ *
36
+ * When enabled, a zero-length length-prefixed frame is sent periodically while the
37
+ * stream is idle (no message sent for `interval` ms). Clients ignore these frames.
38
+ * Only applies to streaming mode.
39
+ *
40
+ * @default { enabled: true, interval: 15000 }
41
+ */
42
+ keepAlive?: undefined | {
43
+ /**
44
+ * If true, a keep-alive frame is sent periodically while the stream is idle.
45
+ *
46
+ * @default true
47
+ */
48
+ enabled: boolean;
49
+ /**
50
+ * Interval (in milliseconds) between keep-alive frames after the last message.
51
+ *
52
+ * @default 15000
53
+ */
54
+ interval?: number;
55
+ };
33
56
  }
34
57
  declare class BatchHandlerPlugin<T extends Context> implements StandardHandlerPlugin<T> {
35
58
  name: string;
@@ -42,6 +65,8 @@ declare class BatchHandlerPlugin<T extends Context> implements StandardHandlerPl
42
65
  private readonly mapSubrequest;
43
66
  private readonly successStatus;
44
67
  private readonly headers;
68
+ private readonly keepAliveEnabled;
69
+ private readonly keepAliveInterval;
45
70
  constructor(options?: BatchHandlerPluginOptions<T>);
46
71
  init(options: StandardHandlerOptions<T>): StandardHandlerOptions<T>;
47
72
  }
@@ -30,6 +30,29 @@ interface BatchHandlerPluginOptions<T extends Context> {
30
30
  * @default {}
31
31
  */
32
32
  headers?: Value<Promisable<StandardHeaders>, [batchOptions: StandardHandlerRoutingInterceptorOptions<T>]>;
33
+ /**
34
+ * Keep-alive settings for streaming batch responses.
35
+ *
36
+ * When enabled, a zero-length length-prefixed frame is sent periodically while the
37
+ * stream is idle (no message sent for `interval` ms). Clients ignore these frames.
38
+ * Only applies to streaming mode.
39
+ *
40
+ * @default { enabled: true, interval: 15000 }
41
+ */
42
+ keepAlive?: undefined | {
43
+ /**
44
+ * If true, a keep-alive frame is sent periodically while the stream is idle.
45
+ *
46
+ * @default true
47
+ */
48
+ enabled: boolean;
49
+ /**
50
+ * Interval (in milliseconds) between keep-alive frames after the last message.
51
+ *
52
+ * @default 15000
53
+ */
54
+ interval?: number;
55
+ };
33
56
  }
34
57
  declare class BatchHandlerPlugin<T extends Context> implements StandardHandlerPlugin<T> {
35
58
  name: string;
@@ -42,6 +65,8 @@ declare class BatchHandlerPlugin<T extends Context> implements StandardHandlerPl
42
65
  private readonly mapSubrequest;
43
66
  private readonly successStatus;
44
67
  private readonly headers;
68
+ private readonly keepAliveEnabled;
69
+ private readonly keepAliveInterval;
45
70
  constructor(options?: BatchHandlerPluginOptions<T>);
46
71
  init(options: StandardHandlerOptions<T>): StandardHandlerOptions<T>;
47
72
  }
@@ -16,6 +16,8 @@ class BatchHandlerPlugin {
16
16
  mapSubrequest;
17
17
  successStatus;
18
18
  headers;
19
+ keepAliveEnabled;
20
+ keepAliveInterval;
19
21
  constructor(options = {}) {
20
22
  this.maxSize = options.maxSize ?? 10;
21
23
  this.mapSubrequest = options.mapSubrequest ?? ((subRequest, { request: batchRequest }) => ({
@@ -29,6 +31,8 @@ class BatchHandlerPlugin {
29
31
  }));
30
32
  this.successStatus = options.successStatus ?? 207;
31
33
  this.headers = options.headers ?? {};
34
+ this.keepAliveEnabled = options.keepAlive?.enabled ?? true;
35
+ this.keepAliveInterval = options.keepAlive?.interval ?? 15e3;
32
36
  }
33
37
  init(options) {
34
38
  const routingInterceptor = async (interceptorOptions) => {
@@ -127,9 +131,35 @@ class BatchHandlerPlugin {
127
131
  };
128
132
  }
129
133
  let streamController;
134
+ let keepAliveTimer;
135
+ const clearKeepAlive = () => {
136
+ if (keepAliveTimer !== void 0) {
137
+ clearInterval(keepAliveTimer);
138
+ keepAliveTimer = void 0;
139
+ }
140
+ };
141
+ const scheduleKeepAlive = () => {
142
+ if (!this.keepAliveEnabled) {
143
+ return;
144
+ }
145
+ clearKeepAlive();
146
+ keepAliveTimer = setInterval(() => {
147
+ try {
148
+ const lengthBuffer = new ArrayBuffer(4);
149
+ new DataView(lengthBuffer).setUint32(0, 0, false);
150
+ streamController.enqueue(new Uint8Array(lengthBuffer));
151
+ } catch {
152
+ clearKeepAlive();
153
+ }
154
+ }, this.keepAliveInterval);
155
+ };
130
156
  const stream = new ReadableStream({
131
157
  start(controller) {
132
158
  streamController = controller;
159
+ scheduleKeepAlive();
160
+ },
161
+ cancel() {
162
+ clearKeepAlive();
133
163
  }
134
164
  });
135
165
  const peer = new ServerPeer(async (message) => {
@@ -139,11 +169,14 @@ class BatchHandlerPlugin {
139
169
  new DataView(lengthBuffer).setUint32(0, bytes.byteLength, false);
140
170
  streamController.enqueue(new Uint8Array(lengthBuffer));
141
171
  streamController.enqueue(bytes);
172
+ scheduleKeepAlive();
142
173
  });
143
174
  Promise.all(messages.map((msg) => peer.message(msg, handleIndividualRequest))).then(async () => {
175
+ clearKeepAlive();
144
176
  streamController.close();
145
177
  await peer.close();
146
178
  }).catch(async (error) => {
179
+ clearKeepAlive();
147
180
  streamController.error(error);
148
181
  await peer.close(error);
149
182
  });
@@ -193,7 +226,10 @@ class CORSHandlerPlugin {
193
226
  if (allowedOrigins.includes(origin)) {
194
227
  resHeaders["access-control-allow-origin"] = origin;
195
228
  }
196
- resHeaders.vary = interceptorOptions.request.headers.vary ?? "origin";
229
+ const existingVary = flattenStandardHeader(resHeaders.vary);
230
+ if (!existingVary?.split(",").some((v) => v.trim().toLowerCase() === "origin")) {
231
+ resHeaders.vary = existingVary ? `${existingVary}, Origin` : "Origin";
232
+ }
197
233
  }
198
234
  const allowedTimingOrigins = toArray(await value(this.options.timingOrigin, origin, interceptorOptions));
199
235
  if (allowedTimingOrigins.includes("*")) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@orpc/server",
3
3
  "type": "module",
4
- "version": "2.0.0-beta.16",
4
+ "version": "2.0.0-beta.17",
5
5
  "license": "MIT",
6
6
  "homepage": "https://orpc.dev",
7
7
  "repository": {
@@ -85,14 +85,14 @@
85
85
  }
86
86
  },
87
87
  "dependencies": {
88
- "@standardserver/core": "^0.2.0",
89
- "@standardserver/fetch": "^0.2.0",
90
- "@standardserver/node": "^0.2.0",
91
- "@standardserver/peer": "^0.2.0",
88
+ "@standardserver/core": "^0.4.2",
89
+ "@standardserver/fetch": "^0.4.2",
90
+ "@standardserver/node": "^0.4.2",
91
+ "@standardserver/peer": "^0.4.2",
92
92
  "cookie": "^2.0.1",
93
- "@orpc/client": "2.0.0-beta.16",
94
- "@orpc/shared": "2.0.0-beta.16",
95
- "@orpc/contract": "2.0.0-beta.16"
93
+ "@orpc/client": "2.0.0-beta.17",
94
+ "@orpc/contract": "2.0.0-beta.17",
95
+ "@orpc/shared": "2.0.0-beta.17"
96
96
  },
97
97
  "devDependencies": {
98
98
  "crossws": "^0.4.6",