@trpc/server 11.0.0-alpha-tmp-app-router-example.388 → 11.0.0-alpha-tmp-issues-5851-take-two.448

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.
Files changed (139) hide show
  1. package/dist/@trpc/server/http.d.ts +1 -2
  2. package/dist/@trpc/server/http.d.ts.map +1 -1
  3. package/dist/@trpc/server/index.d.ts +1 -1
  4. package/dist/@trpc/server/index.d.ts.map +1 -1
  5. package/dist/@trpc/server/rpc.d.ts +1 -1
  6. package/dist/@trpc/server/rpc.d.ts.map +1 -1
  7. package/dist/adapters/aws-lambda/getPlanner.d.ts.map +1 -1
  8. package/dist/adapters/aws-lambda/getPlanner.js +19 -2
  9. package/dist/adapters/aws-lambda/getPlanner.mjs +19 -2
  10. package/dist/adapters/next-app-dir/nextAppDirCaller.d.ts.map +1 -1
  11. package/dist/adapters/next-app-dir/nextAppDirCaller.js +1 -1
  12. package/dist/adapters/next-app-dir/nextAppDirCaller.mjs +1 -1
  13. package/dist/adapters/next-app-dir/redirect.d.ts.map +1 -1
  14. package/dist/adapters/next.js +1 -1
  15. package/dist/adapters/next.mjs +1 -1
  16. package/dist/adapters/node-http/incomingMessageToRequest.d.ts +0 -1
  17. package/dist/adapters/node-http/incomingMessageToRequest.d.ts.map +1 -1
  18. package/dist/adapters/node-http/incomingMessageToRequest.js +3 -1
  19. package/dist/adapters/node-http/incomingMessageToRequest.mjs +3 -1
  20. package/dist/adapters/node-http/nodeHTTPRequestHandler.d.ts.map +1 -1
  21. package/dist/adapters/node-http/nodeHTTPRequestHandler.js +30 -7
  22. package/dist/adapters/node-http/nodeHTTPRequestHandler.mjs +30 -7
  23. package/dist/adapters/node-http/types.d.ts +0 -1
  24. package/dist/adapters/node-http/types.d.ts.map +1 -1
  25. package/dist/adapters/standalone.d.ts +0 -1
  26. package/dist/adapters/standalone.d.ts.map +1 -1
  27. package/dist/adapters/ws.d.ts +3 -4
  28. package/dist/adapters/ws.d.ts.map +1 -1
  29. package/dist/adapters/ws.js +172 -114
  30. package/dist/adapters/ws.mjs +172 -114
  31. package/dist/bundle-analysis.json +236 -155
  32. package/dist/http.js +3 -0
  33. package/dist/http.mjs +1 -0
  34. package/dist/index.js +7 -5
  35. package/dist/index.mjs +3 -2
  36. package/dist/observable/observable.d.ts +1 -0
  37. package/dist/observable/observable.d.ts.map +1 -1
  38. package/dist/observable/observable.js +55 -0
  39. package/dist/observable/observable.mjs +55 -1
  40. package/dist/unstable-core-do-not-import/createProxy.d.ts +3 -3
  41. package/dist/unstable-core-do-not-import/createProxy.d.ts.map +1 -1
  42. package/dist/unstable-core-do-not-import/createProxy.js +15 -6
  43. package/dist/unstable-core-do-not-import/createProxy.mjs +15 -6
  44. package/dist/unstable-core-do-not-import/http/contentType.d.ts +7 -4
  45. package/dist/unstable-core-do-not-import/http/contentType.d.ts.map +1 -1
  46. package/dist/unstable-core-do-not-import/http/contentType.js +60 -17
  47. package/dist/unstable-core-do-not-import/http/contentType.mjs +61 -18
  48. package/dist/unstable-core-do-not-import/http/formDataToObject.d.ts.map +1 -0
  49. package/dist/unstable-core-do-not-import/http/formDataToObject.js +40 -0
  50. package/dist/unstable-core-do-not-import/http/formDataToObject.mjs +38 -0
  51. package/dist/unstable-core-do-not-import/http/getHTTPStatusCode.d.ts.map +1 -1
  52. package/dist/unstable-core-do-not-import/http/getHTTPStatusCode.js +4 -4
  53. package/dist/unstable-core-do-not-import/http/getHTTPStatusCode.mjs +4 -4
  54. package/dist/unstable-core-do-not-import/http/parseConnectionParams.d.ts +4 -0
  55. package/dist/unstable-core-do-not-import/http/parseConnectionParams.d.ts.map +1 -0
  56. package/dist/unstable-core-do-not-import/http/parseConnectionParams.js +42 -0
  57. package/dist/unstable-core-do-not-import/http/parseConnectionParams.mjs +39 -0
  58. package/dist/unstable-core-do-not-import/http/resolveResponse.d.ts.map +1 -1
  59. package/dist/unstable-core-do-not-import/http/resolveResponse.js +302 -149
  60. package/dist/unstable-core-do-not-import/http/resolveResponse.mjs +301 -148
  61. package/dist/unstable-core-do-not-import/http/types.d.ts +26 -2
  62. package/dist/unstable-core-do-not-import/http/types.d.ts.map +1 -1
  63. package/dist/unstable-core-do-not-import/initTRPC.d.ts +12 -12
  64. package/dist/unstable-core-do-not-import/initTRPC.d.ts.map +1 -1
  65. package/dist/unstable-core-do-not-import/middleware.d.ts +3 -3
  66. package/dist/unstable-core-do-not-import/middleware.d.ts.map +1 -1
  67. package/dist/unstable-core-do-not-import/procedureBuilder.d.ts +3 -1
  68. package/dist/unstable-core-do-not-import/procedureBuilder.d.ts.map +1 -1
  69. package/dist/unstable-core-do-not-import/rootConfig.d.ts +12 -0
  70. package/dist/unstable-core-do-not-import/rootConfig.d.ts.map +1 -1
  71. package/dist/unstable-core-do-not-import/router.d.ts +2 -2
  72. package/dist/unstable-core-do-not-import/router.d.ts.map +1 -1
  73. package/dist/unstable-core-do-not-import/router.js +7 -2
  74. package/dist/unstable-core-do-not-import/router.mjs +7 -2
  75. package/dist/unstable-core-do-not-import/rpc/envelopes.d.ts +7 -0
  76. package/dist/unstable-core-do-not-import/rpc/envelopes.d.ts.map +1 -1
  77. package/dist/unstable-core-do-not-import/rpc/index.d.ts +1 -1
  78. package/dist/unstable-core-do-not-import/rpc/index.d.ts.map +1 -1
  79. package/dist/unstable-core-do-not-import/stream/{stream.d.ts → jsonl.d.ts} +5 -5
  80. package/dist/unstable-core-do-not-import/stream/jsonl.d.ts.map +1 -0
  81. package/dist/unstable-core-do-not-import/stream/{stream.js → jsonl.js} +148 -111
  82. package/dist/unstable-core-do-not-import/stream/{stream.mjs → jsonl.mjs} +147 -110
  83. package/dist/unstable-core-do-not-import/stream/sse.d.ts +86 -0
  84. package/dist/unstable-core-do-not-import/stream/sse.d.ts.map +1 -0
  85. package/dist/unstable-core-do-not-import/stream/sse.js +178 -0
  86. package/dist/unstable-core-do-not-import/stream/sse.mjs +172 -0
  87. package/dist/unstable-core-do-not-import/stream/utils/createDeferred.d.ts +18 -0
  88. package/dist/unstable-core-do-not-import/stream/utils/createDeferred.d.ts.map +1 -0
  89. package/dist/unstable-core-do-not-import/stream/utils/createDeferred.js +46 -0
  90. package/dist/unstable-core-do-not-import/stream/utils/createDeferred.mjs +43 -0
  91. package/dist/unstable-core-do-not-import/stream/utils/createReadableStream.d.ts +10 -0
  92. package/dist/unstable-core-do-not-import/stream/utils/createReadableStream.d.ts.map +1 -0
  93. package/dist/unstable-core-do-not-import/stream/utils/createReadableStream.js +31 -0
  94. package/dist/unstable-core-do-not-import/stream/utils/createReadableStream.mjs +29 -0
  95. package/dist/unstable-core-do-not-import/stream/utils/createServer.d.ts +7 -0
  96. package/dist/unstable-core-do-not-import/stream/utils/createServer.d.ts.map +1 -0
  97. package/dist/unstable-core-do-not-import/transformer.d.ts +5 -5
  98. package/dist/unstable-core-do-not-import/utils.d.ts +4 -0
  99. package/dist/unstable-core-do-not-import/utils.d.ts.map +1 -1
  100. package/dist/unstable-core-do-not-import/utils.js +4 -0
  101. package/dist/unstable-core-do-not-import/utils.mjs +4 -1
  102. package/dist/unstable-core-do-not-import.d.ts +5 -2
  103. package/dist/unstable-core-do-not-import.d.ts.map +1 -1
  104. package/dist/unstable-core-do-not-import.js +19 -7
  105. package/dist/unstable-core-do-not-import.mjs +6 -3
  106. package/package.json +6 -6
  107. package/src/@trpc/server/http.ts +7 -2
  108. package/src/@trpc/server/index.ts +1 -0
  109. package/src/@trpc/server/rpc.ts +1 -0
  110. package/src/adapters/aws-lambda/getPlanner.ts +21 -2
  111. package/src/adapters/next-app-dir/nextAppDirCaller.ts +2 -1
  112. package/src/adapters/node-http/incomingMessageToRequest.ts +3 -2
  113. package/src/adapters/node-http/nodeHTTPRequestHandler.ts +32 -7
  114. package/src/adapters/ws.ts +193 -107
  115. package/src/observable/observable.ts +63 -0
  116. package/src/unstable-core-do-not-import/createProxy.ts +23 -8
  117. package/src/unstable-core-do-not-import/http/contentType.ts +83 -21
  118. package/src/{adapters/next-app-dir → unstable-core-do-not-import/http}/formDataToObject.ts +18 -10
  119. package/src/unstable-core-do-not-import/http/getHTTPStatusCode.ts +4 -7
  120. package/src/unstable-core-do-not-import/http/parseConnectionParams.ts +49 -0
  121. package/src/unstable-core-do-not-import/http/resolveResponse.ts +333 -164
  122. package/src/unstable-core-do-not-import/http/types.ts +31 -2
  123. package/src/unstable-core-do-not-import/procedureBuilder.ts +8 -1
  124. package/src/unstable-core-do-not-import/rootConfig.ts +12 -0
  125. package/src/unstable-core-do-not-import/router.ts +47 -35
  126. package/src/unstable-core-do-not-import/rpc/envelopes.ts +9 -0
  127. package/src/unstable-core-do-not-import/rpc/index.ts +1 -0
  128. package/src/unstable-core-do-not-import/stream/{stream.ts → jsonl.ts} +163 -110
  129. package/src/unstable-core-do-not-import/stream/sse.ts +288 -0
  130. package/src/unstable-core-do-not-import/stream/utils/createDeferred.ts +48 -0
  131. package/src/unstable-core-do-not-import/stream/utils/createReadableStream.ts +31 -0
  132. package/src/unstable-core-do-not-import/stream/utils/createServer.ts +44 -0
  133. package/src/unstable-core-do-not-import/utils.ts +5 -0
  134. package/src/unstable-core-do-not-import.ts +5 -2
  135. package/dist/adapters/next-app-dir/formDataToObject.d.ts.map +0 -1
  136. package/dist/adapters/next-app-dir/formDataToObject.js +0 -34
  137. package/dist/adapters/next-app-dir/formDataToObject.mjs +0 -32
  138. package/dist/unstable-core-do-not-import/stream/stream.d.ts.map +0 -1
  139. /package/dist/{adapters/next-app-dir → unstable-core-do-not-import/http}/formDataToObject.d.ts +0 -0
@@ -12,19 +12,26 @@ import {
12
12
  transformTRPCResponse,
13
13
  TRPCError,
14
14
  } from '../@trpc/server';
15
- import type { BaseHandlerOptions } from '../@trpc/server/http';
15
+ import type { TRPCRequestInfo } from '../@trpc/server/http';
16
+ import { toURL, type BaseHandlerOptions } from '../@trpc/server/http';
16
17
  import { parseTRPCMessage } from '../@trpc/server/rpc';
17
18
  // @trpc/server/rpc
18
19
  import type {
19
- JSONRPC2,
20
20
  TRPCClientOutgoingMessage,
21
+ TRPCConnectionParamsMessage,
21
22
  TRPCReconnectNotification,
22
23
  TRPCResponseMessage,
23
24
  } from '../@trpc/server/rpc';
25
+ import { parseConnectionParamsFromUnknown } from '../http';
24
26
  import { isObservable } from '../observable';
25
- import type { Unsubscribable } from '../observable';
27
+ import { observableToAsyncIterable } from '../observable/observable';
26
28
  // eslint-disable-next-line no-restricted-imports
27
- import type { MaybePromise } from '../unstable-core-do-not-import';
29
+ import {
30
+ isAsyncIterable,
31
+ isObject,
32
+ run,
33
+ type MaybePromise,
34
+ } from '../unstable-core-do-not-import';
28
35
  import type { NodeHTTPCreateContextFnOptions } from './node-http';
29
36
 
30
37
  /**
@@ -36,9 +43,9 @@ const WEBSOCKET_OPEN = 1; /* ws.WebSocket.OPEN */
36
43
  /**
37
44
  * @public
38
45
  */
39
- export type CreateWSSContextFnOptions = Omit<
40
- NodeHTTPCreateContextFnOptions<IncomingMessage, ws.WebSocket>,
41
- 'info'
46
+ export type CreateWSSContextFnOptions = NodeHTTPCreateContextFnOptions<
47
+ IncomingMessage,
48
+ ws.WebSocket
42
49
  >;
43
50
 
44
51
  /**
@@ -81,6 +88,7 @@ export type WSSHandlerOptions<TRouter extends AnyRouter> =
81
88
  };
82
89
  };
83
90
 
91
+ const unsetContextSymbol = Symbol('unsetContext');
84
92
  export function getWSConnectionHandler<TRouter extends AnyRouter>(
85
93
  opts: WSConnectionHandlerOptions<TRouter>,
86
94
  ) {
@@ -88,7 +96,7 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
88
96
  const { transformer } = router._def._config;
89
97
 
90
98
  return async (client: ws.WebSocket, req: IncomingMessage) => {
91
- const clientSubscriptions = new Map<number | string, Unsubscribable>();
99
+ const clientSubscriptions = new Map<number | string, AbortController>();
92
100
 
93
101
  function respond(untransformedJSON: TRPCResponseMessage) {
94
102
  client.send(
@@ -98,22 +106,65 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
98
106
  );
99
107
  }
100
108
 
101
- function stopSubscription(
102
- subscription: Unsubscribable,
103
- { id, jsonrpc }: JSONRPC2.BaseEnvelope & { id: JSONRPC2.RequestId },
104
- ) {
105
- subscription.unsubscribe();
106
-
107
- respond({
108
- id,
109
- jsonrpc,
110
- result: {
111
- type: 'stopped',
112
- },
109
+ function createCtxPromise(
110
+ getConnectionParams: () => TRPCRequestInfo['connectionParams'],
111
+ ): Promise<inferRouterContext<TRouter>> {
112
+ return run(async () => {
113
+ ctx = await createContext?.({
114
+ req,
115
+ res: client,
116
+ info: {
117
+ connectionParams: getConnectionParams(),
118
+ calls: [],
119
+ isBatchCall: false,
120
+ accept: null,
121
+ type: 'unknown',
122
+ },
123
+ });
124
+
125
+ return ctx;
126
+ }).catch((cause) => {
127
+ const error = getTRPCErrorFromUnknown(cause);
128
+ opts.onError?.({
129
+ error,
130
+ path: undefined,
131
+ type: 'unknown',
132
+ ctx,
133
+ req,
134
+ input: undefined,
135
+ });
136
+ respond({
137
+ id: null,
138
+ error: getErrorShape({
139
+ config: router._def._config,
140
+ error,
141
+ type: 'unknown',
142
+ path: undefined,
143
+ input: undefined,
144
+ ctx,
145
+ }),
146
+ });
147
+
148
+ // close in next tick
149
+ (global.setImmediate ?? global.setTimeout)(() => {
150
+ client.close();
151
+ });
152
+
153
+ throw error;
113
154
  });
114
155
  }
115
156
 
116
- const ctxPromise = createContext?.({ req, res: client });
157
+ /**
158
+ * promise for initializing the context
159
+ *
160
+ * - the context promise will be created immediately on connection if no connectionParams are expected
161
+ * - if connection params are expected, they will be created once received
162
+ */
163
+ let ctxPromise =
164
+ toURL(req.url ?? '').searchParams.get('connectionParams') === '1'
165
+ ? unsetContextSymbol
166
+ : createCtxPromise(() => null);
167
+
117
168
  let ctx: inferRouterContext<TRouter> | undefined = undefined;
118
169
 
119
170
  async function handleRequest(msg: TRPCClientOutgoingMessage) {
@@ -126,11 +177,7 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
126
177
  });
127
178
  }
128
179
  if (msg.method === 'subscription.stop') {
129
- const sub = clientSubscriptions.get(id);
130
- if (sub) {
131
- stopSubscription(sub, { id, jsonrpc });
132
- }
133
- clientSubscriptions.delete(id);
180
+ clientSubscriptions.get(id)?.abort();
134
181
  return;
135
182
  }
136
183
  const { path, input } = msg.params;
@@ -146,14 +193,7 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
146
193
  type,
147
194
  });
148
195
 
149
- if (type === 'subscription') {
150
- if (!isObservable(result)) {
151
- throw new TRPCError({
152
- message: `Subscription ${path} did not return an observable`,
153
- code: 'INTERNAL_SERVER_ERROR',
154
- });
155
- }
156
- } else {
196
+ if (type !== 'subscription') {
157
197
  // send the value as data if the method is not a subscription
158
198
  respond({
159
199
  id,
@@ -166,62 +206,112 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
166
206
  return;
167
207
  }
168
208
 
169
- const observable = result;
170
- const sub = observable.subscribe({
171
- next(data) {
172
- respond({
173
- id,
174
- jsonrpc,
175
- result: {
176
- type: 'data',
177
- data,
178
- },
179
- });
180
- },
181
- error(err) {
182
- const error = getTRPCErrorFromUnknown(err);
183
- opts.onError?.({ error, path, type, ctx, req, input });
184
- respond({
185
- id,
186
- jsonrpc,
187
- error: getErrorShape({
188
- config: router._def._config,
189
- error,
190
- type,
191
- path,
192
- input,
193
- ctx,
194
- }),
195
- });
196
- },
197
- complete() {
198
- respond({
199
- id,
200
- jsonrpc,
201
- result: {
202
- type: 'stopped',
203
- },
204
- });
205
- },
206
- });
209
+ if (!isObservable(result) && !isAsyncIterable(result)) {
210
+ throw new TRPCError({
211
+ message: `Subscription ${path} did not return an observable or a AsyncGenerator`,
212
+ code: 'INTERNAL_SERVER_ERROR',
213
+ });
214
+ }
215
+
207
216
  /* istanbul ignore next -- @preserve */
208
217
  if (client.readyState !== WEBSOCKET_OPEN) {
209
218
  // if the client got disconnected whilst initializing the subscription
210
219
  // no need to send stopped message if the client is disconnected
211
- sub.unsubscribe();
220
+
212
221
  return;
213
222
  }
214
223
 
215
224
  /* istanbul ignore next -- @preserve */
216
225
  if (clientSubscriptions.has(id)) {
217
226
  // duplicate request ids for client
218
- stopSubscription(sub, { id, jsonrpc });
227
+
219
228
  throw new TRPCError({
220
229
  message: `Duplicate id ${id}`,
221
230
  code: 'BAD_REQUEST',
222
231
  });
223
232
  }
224
- clientSubscriptions.set(id, sub);
233
+
234
+ const iterable = isObservable(result)
235
+ ? observableToAsyncIterable(result)
236
+ : result;
237
+
238
+ const iterator: AsyncIterator<unknown> =
239
+ iterable[Symbol.asyncIterator]();
240
+ const abortController = new AbortController();
241
+
242
+ const abortPromise = new Promise<'abort'>((resolve) => {
243
+ abortController.signal.onabort = () => resolve('abort');
244
+ });
245
+
246
+ run(async () => {
247
+ while (true) {
248
+ const next = await Promise.race([
249
+ iterator.next().catch(getTRPCErrorFromUnknown),
250
+ abortPromise,
251
+ ]);
252
+
253
+ if (next === 'abort') {
254
+ await iterator.return?.();
255
+ break;
256
+ }
257
+ if (next instanceof Error) {
258
+ const error = getTRPCErrorFromUnknown(next);
259
+ opts.onError?.({ error, path, type, ctx, req, input });
260
+ respond({
261
+ id,
262
+ jsonrpc,
263
+ error: getErrorShape({
264
+ config: router._def._config,
265
+ error,
266
+ type,
267
+ path,
268
+ input,
269
+ ctx,
270
+ }),
271
+ });
272
+ break;
273
+ }
274
+ if (next.done) {
275
+ break;
276
+ }
277
+
278
+ respond({
279
+ id,
280
+ jsonrpc,
281
+ result: {
282
+ type: 'data',
283
+ data: next.value,
284
+ },
285
+ });
286
+ }
287
+
288
+ await iterator.return?.();
289
+ respond({
290
+ id,
291
+ jsonrpc,
292
+ result: {
293
+ type: 'stopped',
294
+ },
295
+ });
296
+ clientSubscriptions.delete(id);
297
+ }).catch((cause) => {
298
+ const error = getTRPCErrorFromUnknown(cause);
299
+ opts.onError?.({ error, path, type, ctx, req, input });
300
+ respond({
301
+ id,
302
+ jsonrpc,
303
+ error: getErrorShape({
304
+ config: router._def._config,
305
+ error,
306
+ type,
307
+ path,
308
+ input,
309
+ ctx,
310
+ }),
311
+ });
312
+ abortController.abort();
313
+ });
314
+ clientSubscriptions.set(id, abortController);
225
315
 
226
316
  respond({
227
317
  id,
@@ -249,8 +339,31 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
249
339
  }
250
340
  }
251
341
  client.on('message', async (message) => {
342
+ if (ctxPromise === unsetContextSymbol) {
343
+ // If the ctxPromise wasn't created immediately, we're expecting the first message to be a TRPCConnectionParamsMessage
344
+ ctxPromise = createCtxPromise(() => {
345
+ let msg;
346
+ try {
347
+ msg = JSON.parse(message.toString()) as TRPCConnectionParamsMessage;
348
+
349
+ if (!isObject(msg)) {
350
+ throw new Error('Message was not an object');
351
+ }
352
+ } catch (cause) {
353
+ throw new TRPCError({
354
+ code: 'PARSE_ERROR',
355
+ message: `Malformed TRPCConnectionParamsMessage`,
356
+ cause,
357
+ });
358
+ }
359
+
360
+ const connectionParams = parseConnectionParamsFromUnknown(msg.data);
361
+
362
+ return connectionParams;
363
+ });
364
+ return;
365
+ }
252
366
  try {
253
- // eslint-disable-next-line @typescript-eslint/no-base-to-string
254
367
  const msgJSON: unknown = JSON.parse(message.toString());
255
368
  const msgs: unknown[] = Array.isArray(msgJSON) ? msgJSON : [msgJSON];
256
369
  const promises = msgs
@@ -294,42 +407,15 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
294
407
 
295
408
  client.once('close', () => {
296
409
  for (const sub of clientSubscriptions.values()) {
297
- sub.unsubscribe();
410
+ sub.abort();
298
411
  }
299
412
  clientSubscriptions.clear();
300
413
  });
301
- async function createContextAsync() {
302
- try {
303
- ctx = await ctxPromise;
304
- } catch (cause) {
305
- const error = getTRPCErrorFromUnknown(cause);
306
- opts.onError?.({
307
- error,
308
- path: undefined,
309
- type: 'unknown',
310
- ctx,
311
- req,
312
- input: undefined,
313
- });
314
- respond({
315
- id: null,
316
- error: getErrorShape({
317
- config: router._def._config,
318
- error,
319
- type: 'unknown',
320
- path: undefined,
321
- input: undefined,
322
- ctx,
323
- }),
324
- });
325
414
 
326
- // close in next tick
327
- (global.setImmediate ?? global.setTimeout)(() => {
328
- client.close();
329
- });
330
- }
415
+ if (ctxPromise !== unsetContextSymbol) {
416
+ // prevent unhandled promise rejection errors
417
+ await ctxPromise.catch(() => null);
331
418
  }
332
- await createContextAsync();
333
419
  };
334
420
  }
335
421
 
@@ -4,6 +4,7 @@ import type {
4
4
  OperatorFunction,
5
5
  TeardownLogic,
6
6
  UnaryFunction,
7
+ Unsubscribable,
7
8
  } from './types';
8
9
 
9
10
  /** @public */
@@ -136,3 +137,65 @@ export function observableToPromise<TValue>(
136
137
  abort: abort!,
137
138
  };
138
139
  }
140
+
141
+ /**
142
+ * @internal
143
+ */
144
+ function observableToReadableStream<TValue>(
145
+ observable: Observable<TValue, unknown>,
146
+ ): ReadableStream<TValue> {
147
+ let unsub: Unsubscribable | null = null;
148
+ return new ReadableStream<TValue>({
149
+ start(controller) {
150
+ unsub = observable.subscribe({
151
+ next(data) {
152
+ controller.enqueue(data);
153
+ },
154
+ error(err) {
155
+ controller.error(err);
156
+ },
157
+ complete() {
158
+ controller.close();
159
+ },
160
+ });
161
+ },
162
+ cancel() {
163
+ unsub?.unsubscribe();
164
+ },
165
+ });
166
+ }
167
+
168
+ export function observableToAsyncIterable<TValue>(
169
+ observable: Observable<TValue, unknown>,
170
+ ): AsyncIterable<TValue> {
171
+ const stream = observableToReadableStream(observable);
172
+
173
+ const reader = stream.getReader();
174
+ const iterator: AsyncIterator<TValue> = {
175
+ async next() {
176
+ const value = await reader.read();
177
+ if (value.done) {
178
+ return {
179
+ value: undefined,
180
+ done: true,
181
+ };
182
+ }
183
+ return {
184
+ value: value.value,
185
+ done: false,
186
+ };
187
+ },
188
+ async return() {
189
+ await reader.cancel();
190
+ return {
191
+ value: undefined,
192
+ done: true,
193
+ };
194
+ },
195
+ };
196
+ return {
197
+ [Symbol.asyncIterator]() {
198
+ return iterator;
199
+ },
200
+ };
201
+ }
@@ -1,6 +1,6 @@
1
1
  interface ProxyCallbackOptions {
2
- path: string[];
3
- args: unknown[];
2
+ path: readonly string[];
3
+ args: readonly unknown[];
4
4
  }
5
5
  type ProxyCallback = (opts: ProxyCallbackOptions) => unknown;
6
6
 
@@ -8,15 +8,27 @@ const noop = () => {
8
8
  // noop
9
9
  };
10
10
 
11
- function createInnerProxy(callback: ProxyCallback, path: string[]) {
12
- const proxy: unknown = new Proxy(noop, {
11
+ const freezeIfAvailable = (obj: object) => {
12
+ if (Object.freeze) {
13
+ Object.freeze(obj);
14
+ }
15
+ };
16
+
17
+ function createInnerProxy(
18
+ callback: ProxyCallback,
19
+ path: readonly string[],
20
+ memo: Record<string, unknown>,
21
+ ) {
22
+ const cacheKey = path.join('.');
23
+
24
+ memo[cacheKey] ??= new Proxy(noop, {
13
25
  get(_obj, key) {
14
26
  if (typeof key !== 'string' || key === 'then') {
15
27
  // special case for if the proxy is accidentally treated
16
28
  // like a PromiseLike (like in `Promise.resolve(proxy)`)
17
29
  return undefined;
18
30
  }
19
- return createInnerProxy(callback, [...path, key]);
31
+ return createInnerProxy(callback, [...path, key], memo);
20
32
  },
21
33
  apply(_1, _2, args) {
22
34
  const lastOfPath = path[path.length - 1];
@@ -34,11 +46,13 @@ function createInnerProxy(callback: ProxyCallback, path: string[]) {
34
46
  path: path.slice(0, -1),
35
47
  };
36
48
  }
49
+ freezeIfAvailable(opts.args);
50
+ freezeIfAvailable(opts.path);
37
51
  return callback(opts);
38
52
  },
39
53
  });
40
54
 
41
- return proxy;
55
+ return memo[cacheKey];
42
56
  }
43
57
 
44
58
  /**
@@ -46,8 +60,9 @@ function createInnerProxy(callback: ProxyCallback, path: string[]) {
46
60
  *
47
61
  * @internal
48
62
  */
49
- export const createRecursiveProxy = (callback: ProxyCallback) =>
50
- createInnerProxy(callback, []);
63
+ export const createRecursiveProxy = <TFaux = unknown>(
64
+ callback: ProxyCallback,
65
+ ): TFaux => createInnerProxy(callback, [], Object.create(null)) as TFaux;
51
66
 
52
67
  /**
53
68
  * Used in place of `new Proxy` where each handler will map 1 level deep to another value.