@trpc/server 11.0.0-rc.560 → 11.0.0-rc.563

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.
@@ -105,60 +105,141 @@ const SERIALIZED_ERROR_EVENT = 'serialized-error';
105
105
  /**
106
106
  * @see https://html.spec.whatwg.org/multipage/server-sent-events.html
107
107
  */ function sseStreamConsumer(opts) {
108
- const { deserialize =(v)=>v } = opts;
109
- const eventSource = opts.from;
108
+ const { deserialize =(v)=>v , shouldRecreateOnError } = opts;
109
+ const signal = opts.signal;
110
+ let eventSource = null;
111
+ let lock = null;
110
112
  const stream = createReadableStream.createReadableStream();
111
- const transform = new TransformStream({
112
- async transform (chunk, controller) {
113
- const data = deserialize(JSON.parse(chunk.data));
114
- if (chunk.type === SERIALIZED_ERROR_EVENT) {
115
- controller.enqueue({
116
- ok: false,
117
- error: data
118
- });
113
+ function createEventSource(...args) {
114
+ const es = new EventSource(...args);
115
+ if (signal.aborted) {
116
+ es.close();
117
+ } else {
118
+ signal.addEventListener('abort', ()=>es.close());
119
+ }
120
+ /**
121
+ * Dispatch an event to the stream controller
122
+ *
123
+ * Will be a no-op if the event source has been replaced
124
+ */ const dispatch = (fn)=>{
125
+ utils.run(async ()=>{
126
+ while(lock){
127
+ await lock;
128
+ }
129
+ if (es === eventSource) {
130
+ await fn(stream.controller);
131
+ }
132
+ }).catch((error)=>{
133
+ stream.controller.error(error);
134
+ });
135
+ };
136
+ const pauseDispatch = async (fn)=>{
137
+ while(lock){
138
+ await lock;
139
+ }
140
+ if (es !== eventSource) {
119
141
  return;
120
142
  }
121
- // console.debug('transforming', chunk.type, chunk.data);
122
- const def = {
123
- data
124
- };
125
- if (chunk.lastEventId) {
126
- def.id = chunk.lastEventId;
143
+ const deferred = createDeferred.createDeferred();
144
+ lock = deferred.promise;
145
+ try {
146
+ await fn();
147
+ } finally{
148
+ lock = null;
149
+ deferred.resolve();
127
150
  }
128
- controller.enqueue({
129
- ok: true,
130
- data: def
151
+ };
152
+ es.addEventListener('open', ()=>{
153
+ dispatch((controller)=>{
154
+ controller.enqueue({
155
+ type: 'opened',
156
+ eventSource: es
157
+ });
131
158
  });
132
- }
133
- });
134
- let errorLock = undefined;
135
- eventSource.addEventListener('message', (msg)=>{
136
- stream.controller.enqueue(msg);
137
- });
138
- eventSource.addEventListener(SERIALIZED_ERROR_EVENT, (msg)=>{
139
- stream.controller.enqueue(msg);
140
- });
141
- eventSource.addEventListener('error', async (cause)=>{
142
- // We prevent more than 1 error handler from waiting at the same time
143
- const result = await errorLock;
144
- if (result === 'CANCEL_ALL') {
145
- return;
146
- }
147
- const resolvable = createDeferred.createDeferred();
148
- errorLock = resolvable.promise;
149
- const handled = await opts.tryHandleError?.(cause);
150
- resolvable.resolve();
151
- if (handled === true) {
152
- return;
153
- }
154
- if (eventSource.readyState === EventSource.CLOSED) {
155
- stream.controller.error(cause);
156
- }
159
+ });
160
+ es.addEventListener(SERIALIZED_ERROR_EVENT, (msg)=>{
161
+ dispatch(async ()=>{
162
+ if (shouldRecreateOnError) {
163
+ await pauseDispatch(async ()=>{
164
+ const recreate = await shouldRecreateOnError({
165
+ type: SERIALIZED_ERROR_EVENT,
166
+ error: deserialize(JSON.parse(msg.data))
167
+ });
168
+ if (recreate) {
169
+ await recreateEventSource();
170
+ }
171
+ });
172
+ }
173
+ dispatch((controller)=>{
174
+ controller.enqueue({
175
+ type: 'error',
176
+ error: deserialize(JSON.parse(msg.data)),
177
+ eventSource: es
178
+ });
179
+ });
180
+ });
181
+ });
182
+ es.addEventListener('error', (event)=>{
183
+ dispatch(async ()=>{
184
+ if (shouldRecreateOnError) {
185
+ await pauseDispatch(async ()=>{
186
+ const recreate = await shouldRecreateOnError({
187
+ type: 'event',
188
+ event
189
+ });
190
+ if (recreate) {
191
+ await recreateEventSource();
192
+ }
193
+ });
194
+ }
195
+ dispatch((controller)=>{
196
+ if (es.readyState === EventSource.CLOSED) {
197
+ controller.error(event);
198
+ } else {
199
+ controller.enqueue({
200
+ type: 'connecting',
201
+ eventSource: es
202
+ });
203
+ }
204
+ });
205
+ });
206
+ });
207
+ es.addEventListener('message', (msg)=>{
208
+ dispatch((controller)=>{
209
+ const chunk = deserialize(JSON.parse(msg.data));
210
+ const def = {
211
+ data: chunk
212
+ };
213
+ if (msg.lastEventId) {
214
+ def.id = msg.lastEventId;
215
+ }
216
+ controller.enqueue({
217
+ type: 'data',
218
+ data: def,
219
+ eventSource: es
220
+ });
221
+ });
222
+ });
223
+ return es;
224
+ }
225
+ async function recreateEventSource() {
226
+ eventSource?.close();
227
+ const [url, init] = await Promise.all([
228
+ opts.url(),
229
+ opts.init()
230
+ ]);
231
+ eventSource = createEventSource(url, init);
232
+ stream.controller.enqueue({
233
+ type: 'connecting',
234
+ eventSource
235
+ });
236
+ }
237
+ recreateEventSource().catch(()=>{
238
+ // prevent unhandled promise rejection
157
239
  });
158
- const readable = stream.readable.pipeThrough(transform);
159
240
  return {
160
241
  [Symbol.asyncIterator] () {
161
- const reader = readable.getReader();
242
+ const reader = stream.readable.getReader();
162
243
  const iterator = {
163
244
  async next () {
164
245
  const value = await reader.read();
@@ -103,60 +103,141 @@ const SERIALIZED_ERROR_EVENT = 'serialized-error';
103
103
  /**
104
104
  * @see https://html.spec.whatwg.org/multipage/server-sent-events.html
105
105
  */ function sseStreamConsumer(opts) {
106
- const { deserialize =(v)=>v } = opts;
107
- const eventSource = opts.from;
106
+ const { deserialize =(v)=>v , shouldRecreateOnError } = opts;
107
+ const signal = opts.signal;
108
+ let eventSource = null;
109
+ let lock = null;
108
110
  const stream = createReadableStream();
109
- const transform = new TransformStream({
110
- async transform (chunk, controller) {
111
- const data = deserialize(JSON.parse(chunk.data));
112
- if (chunk.type === SERIALIZED_ERROR_EVENT) {
113
- controller.enqueue({
114
- ok: false,
115
- error: data
116
- });
111
+ function createEventSource(...args) {
112
+ const es = new EventSource(...args);
113
+ if (signal.aborted) {
114
+ es.close();
115
+ } else {
116
+ signal.addEventListener('abort', ()=>es.close());
117
+ }
118
+ /**
119
+ * Dispatch an event to the stream controller
120
+ *
121
+ * Will be a no-op if the event source has been replaced
122
+ */ const dispatch = (fn)=>{
123
+ run(async ()=>{
124
+ while(lock){
125
+ await lock;
126
+ }
127
+ if (es === eventSource) {
128
+ await fn(stream.controller);
129
+ }
130
+ }).catch((error)=>{
131
+ stream.controller.error(error);
132
+ });
133
+ };
134
+ const pauseDispatch = async (fn)=>{
135
+ while(lock){
136
+ await lock;
137
+ }
138
+ if (es !== eventSource) {
117
139
  return;
118
140
  }
119
- // console.debug('transforming', chunk.type, chunk.data);
120
- const def = {
121
- data
122
- };
123
- if (chunk.lastEventId) {
124
- def.id = chunk.lastEventId;
141
+ const deferred = createDeferred();
142
+ lock = deferred.promise;
143
+ try {
144
+ await fn();
145
+ } finally{
146
+ lock = null;
147
+ deferred.resolve();
125
148
  }
126
- controller.enqueue({
127
- ok: true,
128
- data: def
149
+ };
150
+ es.addEventListener('open', ()=>{
151
+ dispatch((controller)=>{
152
+ controller.enqueue({
153
+ type: 'opened',
154
+ eventSource: es
155
+ });
129
156
  });
130
- }
131
- });
132
- let errorLock = undefined;
133
- eventSource.addEventListener('message', (msg)=>{
134
- stream.controller.enqueue(msg);
135
- });
136
- eventSource.addEventListener(SERIALIZED_ERROR_EVENT, (msg)=>{
137
- stream.controller.enqueue(msg);
138
- });
139
- eventSource.addEventListener('error', async (cause)=>{
140
- // We prevent more than 1 error handler from waiting at the same time
141
- const result = await errorLock;
142
- if (result === 'CANCEL_ALL') {
143
- return;
144
- }
145
- const resolvable = createDeferred();
146
- errorLock = resolvable.promise;
147
- const handled = await opts.tryHandleError?.(cause);
148
- resolvable.resolve();
149
- if (handled === true) {
150
- return;
151
- }
152
- if (eventSource.readyState === EventSource.CLOSED) {
153
- stream.controller.error(cause);
154
- }
157
+ });
158
+ es.addEventListener(SERIALIZED_ERROR_EVENT, (msg)=>{
159
+ dispatch(async ()=>{
160
+ if (shouldRecreateOnError) {
161
+ await pauseDispatch(async ()=>{
162
+ const recreate = await shouldRecreateOnError({
163
+ type: SERIALIZED_ERROR_EVENT,
164
+ error: deserialize(JSON.parse(msg.data))
165
+ });
166
+ if (recreate) {
167
+ await recreateEventSource();
168
+ }
169
+ });
170
+ }
171
+ dispatch((controller)=>{
172
+ controller.enqueue({
173
+ type: 'error',
174
+ error: deserialize(JSON.parse(msg.data)),
175
+ eventSource: es
176
+ });
177
+ });
178
+ });
179
+ });
180
+ es.addEventListener('error', (event)=>{
181
+ dispatch(async ()=>{
182
+ if (shouldRecreateOnError) {
183
+ await pauseDispatch(async ()=>{
184
+ const recreate = await shouldRecreateOnError({
185
+ type: 'event',
186
+ event
187
+ });
188
+ if (recreate) {
189
+ await recreateEventSource();
190
+ }
191
+ });
192
+ }
193
+ dispatch((controller)=>{
194
+ if (es.readyState === EventSource.CLOSED) {
195
+ controller.error(event);
196
+ } else {
197
+ controller.enqueue({
198
+ type: 'connecting',
199
+ eventSource: es
200
+ });
201
+ }
202
+ });
203
+ });
204
+ });
205
+ es.addEventListener('message', (msg)=>{
206
+ dispatch((controller)=>{
207
+ const chunk = deserialize(JSON.parse(msg.data));
208
+ const def = {
209
+ data: chunk
210
+ };
211
+ if (msg.lastEventId) {
212
+ def.id = msg.lastEventId;
213
+ }
214
+ controller.enqueue({
215
+ type: 'data',
216
+ data: def,
217
+ eventSource: es
218
+ });
219
+ });
220
+ });
221
+ return es;
222
+ }
223
+ async function recreateEventSource() {
224
+ eventSource?.close();
225
+ const [url, init] = await Promise.all([
226
+ opts.url(),
227
+ opts.init()
228
+ ]);
229
+ eventSource = createEventSource(url, init);
230
+ stream.controller.enqueue({
231
+ type: 'connecting',
232
+ eventSource
233
+ });
234
+ }
235
+ recreateEventSource().catch(()=>{
236
+ // prevent unhandled promise rejection
155
237
  });
156
- const readable = stream.readable.pipeThrough(transform);
157
238
  return {
158
239
  [Symbol.asyncIterator] () {
159
- const reader = readable.getReader();
240
+ const reader = stream.readable.getReader();
160
241
  const iterator = {
161
242
  async next () {
162
243
  const value = await reader.read();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trpc/server",
3
- "version": "11.0.0-rc.560+137b085f8",
3
+ "version": "11.0.0-rc.563+e5ae464b2",
4
4
  "description": "The tRPC server library",
5
5
  "author": "KATT",
6
6
  "license": "MIT",
@@ -149,5 +149,5 @@
149
149
  "funding": [
150
150
  "https://trpc.io/sponsor"
151
151
  ],
152
- "gitHead": "137b085f86cb4ceb397b29c69ccbe38a93dbe79a"
152
+ "gitHead": "e5ae464b2428b591d816ee4e01725c86b232d620"
153
153
  }
@@ -24,8 +24,7 @@ import type {
24
24
  TRPCResultMessage,
25
25
  } from '../@trpc/server/rpc';
26
26
  import { parseConnectionParamsFromUnknown } from '../http';
27
- import { isObservable } from '../observable';
28
- import { observableToAsyncIterable } from '../observable/observable';
27
+ import { isObservable, observableToAsyncIterable } from '../observable';
29
28
  // eslint-disable-next-line no-restricted-imports
30
29
  import {
31
30
  isAsyncIterable,
@@ -1,5 +1,5 @@
1
1
  export type { inferObservableValue } from './observable';
2
- export { isObservable, observable, observableToPromise } from './observable';
2
+ export { isObservable, observable, observableToAsyncIterable, observableToPromise } from './observable';
3
3
  export { map, share, tap } from './operators';
4
4
  export type {
5
5
  Observable,
@@ -152,6 +152,7 @@ function observableToReadableStream<TValue>(
152
152
  });
153
153
  }
154
154
 
155
+ /** @internal */
155
156
  export function observableToAsyncIterable<TValue>(
156
157
  observable: Observable<TValue, unknown>,
157
158
  ): AsyncIterable<TValue> {
@@ -436,6 +436,9 @@ export async function jsonlStreamConsumer<THead>(opts: {
436
436
  deserialize?: Deserialize;
437
437
  onError?: ConsumerOnError;
438
438
  formatError?: (opts: { error: unknown }) => Error;
439
+ /**
440
+ * This `AbortController` will be triggered when there are no more listeners to the stream.
441
+ */
439
442
  abortController: AbortController | null;
440
443
  }) {
441
444
  const { deserialize = (v) => v } = opts;