@trpc/server 11.0.0-alpha-tmp-subscription-connection-state.488 → 11.0.0-alpha-tmp-subscription-connection-state.489
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/@trpc/server/index.d.ts +5 -1
- package/dist/@trpc/server/index.d.ts.map +1 -1
- package/dist/adapters/ws.d.ts.map +1 -1
- package/dist/adapters/ws.js +36 -6
- package/dist/adapters/ws.mjs +37 -7
- package/dist/bundle-analysis.json +102 -87
- package/dist/index.js +3 -2
- package/dist/index.mjs +1 -1
- package/dist/unstable-core-do-not-import/clientish/serialize.d.ts +1 -1
- package/dist/unstable-core-do-not-import/clientish/serialize.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/http/resolveResponse.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/http/resolveResponse.js +13 -2
- package/dist/unstable-core-do-not-import/http/resolveResponse.mjs +13 -2
- package/dist/unstable-core-do-not-import/procedureBuilder.d.ts +2 -2
- package/dist/unstable-core-do-not-import/procedureBuilder.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/rootConfig.d.ts +1 -1
- package/dist/unstable-core-do-not-import/rootConfig.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/rpc/envelopes.d.ts +8 -0
- package/dist/unstable-core-do-not-import/rpc/envelopes.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/rpc/parseTRPCMessage.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/rpc/parseTRPCMessage.js +6 -2
- package/dist/unstable-core-do-not-import/rpc/parseTRPCMessage.mjs +6 -2
- package/dist/unstable-core-do-not-import/stream/jsonl.d.ts +7 -3
- package/dist/unstable-core-do-not-import/stream/jsonl.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/stream/jsonl.js +5 -4
- package/dist/unstable-core-do-not-import/stream/jsonl.mjs +5 -4
- package/dist/unstable-core-do-not-import/stream/sse.d.ts +12 -30
- package/dist/unstable-core-do-not-import/stream/sse.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/stream/sse.js +29 -23
- package/dist/unstable-core-do-not-import/stream/sse.mjs +30 -22
- package/dist/unstable-core-do-not-import/stream/tracked.d.ts +31 -0
- package/dist/unstable-core-do-not-import/stream/tracked.d.ts.map +1 -0
- package/dist/unstable-core-do-not-import/stream/tracked.js +29 -0
- package/dist/unstable-core-do-not-import/stream/tracked.mjs +25 -0
- package/dist/unstable-core-do-not-import/transformer.d.ts +1 -0
- package/dist/unstable-core-do-not-import/transformer.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import.d.ts +1 -0
- package/dist/unstable-core-do-not-import.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import.js +4 -2
- package/dist/unstable-core-do-not-import.mjs +2 -1
- package/package.json +7 -7
- package/src/@trpc/server/index.ts +4 -0
- package/src/adapters/ws.ts +42 -6
- package/src/unstable-core-do-not-import/clientish/serialize.ts +1 -1
- package/src/unstable-core-do-not-import/http/resolveResponse.ts +13 -1
- package/src/unstable-core-do-not-import/procedureBuilder.ts +2 -2
- package/src/unstable-core-do-not-import/rootConfig.ts +4 -1
- package/src/unstable-core-do-not-import/rpc/envelopes.ts +18 -2
- package/src/unstable-core-do-not-import/rpc/parseTRPCMessage.ts +5 -1
- package/src/unstable-core-do-not-import/stream/jsonl.ts +14 -6
- package/src/unstable-core-do-not-import/stream/sse.ts +59 -68
- package/src/unstable-core-do-not-import/stream/tracked.ts +55 -0
- package/src/unstable-core-do-not-import.ts +1 -0
|
@@ -34,6 +34,7 @@ export * from './unstable-core-do-not-import/router';
|
|
|
34
34
|
export * from './unstable-core-do-not-import/rpc';
|
|
35
35
|
export * from './unstable-core-do-not-import/stream/jsonl';
|
|
36
36
|
export * from './unstable-core-do-not-import/stream/sse';
|
|
37
|
+
export * from './unstable-core-do-not-import/stream/tracked';
|
|
37
38
|
export * from './unstable-core-do-not-import/stream/utils/createDeferred';
|
|
38
39
|
export * from './unstable-core-do-not-import/transformer';
|
|
39
40
|
export * from './unstable-core-do-not-import/types';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unstable-core-do-not-import.d.ts","sourceRoot":"","sources":["../src/unstable-core-do-not-import.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,cAAc,mDAAmD,CAAC;AAClE,cAAc,oDAAoD,CAAC;AACnE,cAAc,mDAAmD,CAAC;AAClE,cAAc,2CAA2C,CAAC;AAC1D,cAAc,+CAA+C,CAAC;AAC9D,cAAc,mDAAmD,CAAC;AAClE,cAAc,+CAA+C,CAAC;AAC9D,cAAc,yDAAyD,CAAC;AACxE,cAAc,gDAAgD,CAAC;AAC/D,cAAc,uDAAuD,CAAC;AACtE,cAAc,qDAAqD,CAAC;AACpE,cAAc,sDAAsD,CAAC;AACrE,cAAc,0DAA0D,CAAC;AACzE,cAAc,oDAAoD,CAAC;AACnE,cAAc,0CAA0C,CAAC;AACzD,cAAc,0CAA0C,CAAC;AACzD,cAAc,wCAAwC,CAAC;AACvD,cAAc,0CAA0C,CAAC;AACzD,cAAc,sCAAsC,CAAC;AACrD,cAAc,yCAAyC,CAAC;AACxD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,0CAA0C,CAAC;AACzD,cAAc,sCAAsC,CAAC;AACrD,cAAc,mCAAmC,CAAC;AAClD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,0CAA0C,CAAC;AACzD,cAAc,2DAA2D,CAAC;AAC1E,cAAc,2CAA2C,CAAC;AAC1D,cAAc,qCAAqC,CAAC;AACpD,cAAc,qCAAqC,CAAC"}
|
|
1
|
+
{"version":3,"file":"unstable-core-do-not-import.d.ts","sourceRoot":"","sources":["../src/unstable-core-do-not-import.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,cAAc,mDAAmD,CAAC;AAClE,cAAc,oDAAoD,CAAC;AACnE,cAAc,mDAAmD,CAAC;AAClE,cAAc,2CAA2C,CAAC;AAC1D,cAAc,+CAA+C,CAAC;AAC9D,cAAc,mDAAmD,CAAC;AAClE,cAAc,+CAA+C,CAAC;AAC9D,cAAc,yDAAyD,CAAC;AACxE,cAAc,gDAAgD,CAAC;AAC/D,cAAc,uDAAuD,CAAC;AACtE,cAAc,qDAAqD,CAAC;AACpE,cAAc,sDAAsD,CAAC;AACrE,cAAc,0DAA0D,CAAC;AACzE,cAAc,oDAAoD,CAAC;AACnE,cAAc,0CAA0C,CAAC;AACzD,cAAc,0CAA0C,CAAC;AACzD,cAAc,wCAAwC,CAAC;AACvD,cAAc,0CAA0C,CAAC;AACzD,cAAc,sCAAsC,CAAC;AACrD,cAAc,yCAAyC,CAAC;AACxD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,0CAA0C,CAAC;AACzD,cAAc,sCAAsC,CAAC;AACrD,cAAc,mCAAmC,CAAC;AAClD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,0CAA0C,CAAC;AACzD,cAAc,8CAA8C,CAAC;AAC7D,cAAc,2DAA2D,CAAC;AAC1E,cAAc,2CAA2C,CAAC;AAC1D,cAAc,qCAAqC,CAAC;AACpD,cAAc,qCAAqC,CAAC"}
|
|
@@ -23,6 +23,7 @@ var codes = require('./unstable-core-do-not-import/rpc/codes.js');
|
|
|
23
23
|
var parseTRPCMessage = require('./unstable-core-do-not-import/rpc/parseTRPCMessage.js');
|
|
24
24
|
var jsonl = require('./unstable-core-do-not-import/stream/jsonl.js');
|
|
25
25
|
var sse = require('./unstable-core-do-not-import/stream/sse.js');
|
|
26
|
+
var tracked = require('./unstable-core-do-not-import/stream/tracked.js');
|
|
26
27
|
var createDeferred = require('./unstable-core-do-not-import/stream/utils/createDeferred.js');
|
|
27
28
|
var transformer = require('./unstable-core-do-not-import/transformer.js');
|
|
28
29
|
var types = require('./unstable-core-do-not-import/types.js');
|
|
@@ -67,11 +68,12 @@ exports.parseTRPCMessage = parseTRPCMessage.parseTRPCMessage;
|
|
|
67
68
|
exports.isPromise = jsonl.isPromise;
|
|
68
69
|
exports.jsonlStreamConsumer = jsonl.jsonlStreamConsumer;
|
|
69
70
|
exports.jsonlStreamProducer = jsonl.jsonlStreamProducer;
|
|
70
|
-
exports.isSSEMessageEnvelope = sse.isSSEMessageEnvelope;
|
|
71
|
-
exports.sse = sse.sse;
|
|
72
71
|
exports.sseHeaders = sse.sseHeaders;
|
|
73
72
|
exports.sseStreamConsumer = sse.sseStreamConsumer;
|
|
74
73
|
exports.sseStreamProducer = sse.sseStreamProducer;
|
|
74
|
+
exports.isTrackedEnvelope = tracked.isTrackedEnvelope;
|
|
75
|
+
exports.sse = tracked.sse;
|
|
76
|
+
exports.tracked = tracked.tracked;
|
|
75
77
|
exports.createDeferred = createDeferred.createDeferred;
|
|
76
78
|
exports.createTimeoutPromise = createDeferred.createTimeoutPromise;
|
|
77
79
|
exports.defaultTransformer = transformer.defaultTransformer;
|
|
@@ -20,7 +20,8 @@ export { callProcedure, createCallerFactory, createRouterFactory, mergeRouters }
|
|
|
20
20
|
export { TRPC_ERROR_CODES_BY_KEY, TRPC_ERROR_CODES_BY_NUMBER } from './unstable-core-do-not-import/rpc/codes.mjs';
|
|
21
21
|
export { parseTRPCMessage } from './unstable-core-do-not-import/rpc/parseTRPCMessage.mjs';
|
|
22
22
|
export { isPromise, jsonlStreamConsumer, jsonlStreamProducer } from './unstable-core-do-not-import/stream/jsonl.mjs';
|
|
23
|
-
export {
|
|
23
|
+
export { sseHeaders, sseStreamConsumer, sseStreamProducer } from './unstable-core-do-not-import/stream/sse.mjs';
|
|
24
|
+
export { isTrackedEnvelope, sse, tracked } from './unstable-core-do-not-import/stream/tracked.mjs';
|
|
24
25
|
export { createDeferred, createTimeoutPromise } from './unstable-core-do-not-import/stream/utils/createDeferred.mjs';
|
|
25
26
|
export { defaultTransformer, getDataTransformer, transformResult, transformTRPCResponse } from './unstable-core-do-not-import/transformer.mjs';
|
|
26
27
|
export { ERROR_SYMBOL } from './unstable-core-do-not-import/types.mjs';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trpc/server",
|
|
3
|
-
"version": "11.0.0-alpha-tmp-subscription-connection-state.
|
|
3
|
+
"version": "11.0.0-alpha-tmp-subscription-connection-state.489+04c141d1b",
|
|
4
4
|
"description": "The tRPC server library",
|
|
5
5
|
"author": "KATT",
|
|
6
6
|
"license": "MIT",
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
},
|
|
118
118
|
"devDependencies": {
|
|
119
119
|
"@fastify/websocket": "^10.0.1",
|
|
120
|
-
"@tanstack/react-query": "^5.
|
|
120
|
+
"@tanstack/react-query": "^5.51.11",
|
|
121
121
|
"@types/aws-lambda": "^8.10.137",
|
|
122
122
|
"@types/express": "^4.17.17",
|
|
123
123
|
"@types/hash-sum": "^1.0.0",
|
|
@@ -126,13 +126,13 @@
|
|
|
126
126
|
"@types/react-dom": "^18.3.0",
|
|
127
127
|
"@types/ws": "^8.2.0",
|
|
128
128
|
"devalue": "^5.0.0",
|
|
129
|
-
"eslint": "^8.
|
|
129
|
+
"eslint": "^8.57.0",
|
|
130
130
|
"express": "^4.17.1",
|
|
131
131
|
"fastify": "^4.16.0",
|
|
132
132
|
"fastify-plugin": "^4.5.0",
|
|
133
133
|
"hash-sum": "^2.0.0",
|
|
134
134
|
"myzod": "^1.3.1",
|
|
135
|
-
"next": "^14.
|
|
135
|
+
"next": "^14.2.5",
|
|
136
136
|
"react": "^18.3.1",
|
|
137
137
|
"react-dom": "^18.3.1",
|
|
138
138
|
"rollup": "^4.9.5",
|
|
@@ -140,8 +140,8 @@
|
|
|
140
140
|
"superstruct": "^2.0.0",
|
|
141
141
|
"tslib": "^2.5.0",
|
|
142
142
|
"tsx": "^4.0.0",
|
|
143
|
-
"typescript": "^5.5.
|
|
144
|
-
"valibot": "^0.
|
|
143
|
+
"typescript": "^5.5.3",
|
|
144
|
+
"valibot": "^0.37.0",
|
|
145
145
|
"ws": "^8.0.0",
|
|
146
146
|
"yup": "^1.0.0",
|
|
147
147
|
"zod": "^3.0.0"
|
|
@@ -149,5 +149,5 @@
|
|
|
149
149
|
"funding": [
|
|
150
150
|
"https://trpc.io/sponsor"
|
|
151
151
|
],
|
|
152
|
-
"gitHead": "
|
|
152
|
+
"gitHead": "04c141d1b4bb50631826aa956a874ba1d363f492"
|
|
153
153
|
}
|
|
@@ -37,7 +37,11 @@ export {
|
|
|
37
37
|
type QueryProcedure as TRPCQueryProcedure,
|
|
38
38
|
type SubscriptionProcedure as TRPCSubscriptionProcedure,
|
|
39
39
|
type TRPCBuilder,
|
|
40
|
+
/**
|
|
41
|
+
* @deprecated use `tracked(id, data)` instead
|
|
42
|
+
*/
|
|
40
43
|
sse,
|
|
44
|
+
tracked,
|
|
41
45
|
} from '../../unstable-core-do-not-import';
|
|
42
46
|
|
|
43
47
|
export type {
|
package/src/adapters/ws.ts
CHANGED
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
TRPCConnectionParamsMessage,
|
|
22
22
|
TRPCReconnectNotification,
|
|
23
23
|
TRPCResponseMessage,
|
|
24
|
+
TRPCResultMessage,
|
|
24
25
|
} from '../@trpc/server/rpc';
|
|
25
26
|
import { parseConnectionParamsFromUnknown } from '../http';
|
|
26
27
|
import { isObservable } from '../observable';
|
|
@@ -29,6 +30,7 @@ import { observableToAsyncIterable } from '../observable/observable';
|
|
|
29
30
|
import {
|
|
30
31
|
isAsyncIterable,
|
|
31
32
|
isObject,
|
|
33
|
+
isTrackedEnvelope,
|
|
32
34
|
run,
|
|
33
35
|
type MaybePromise,
|
|
34
36
|
} from '../unstable-core-do-not-import';
|
|
@@ -171,6 +173,7 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
|
|
|
171
173
|
|
|
172
174
|
async function handleRequest(msg: TRPCClientOutgoingMessage) {
|
|
173
175
|
const { id, jsonrpc } = msg;
|
|
176
|
+
|
|
174
177
|
/* istanbul ignore next -- @preserve */
|
|
175
178
|
if (id === null) {
|
|
176
179
|
throw new TRPCError({
|
|
@@ -182,9 +185,22 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
|
|
|
182
185
|
clientSubscriptions.get(id)?.abort();
|
|
183
186
|
return;
|
|
184
187
|
}
|
|
185
|
-
const { path,
|
|
188
|
+
const { path, lastEventId } = msg.params;
|
|
189
|
+
let { input } = msg.params;
|
|
186
190
|
const type = msg.method;
|
|
187
191
|
try {
|
|
192
|
+
if (lastEventId !== undefined) {
|
|
193
|
+
if (isObject(input)) {
|
|
194
|
+
input = {
|
|
195
|
+
...input,
|
|
196
|
+
lastEventId: lastEventId,
|
|
197
|
+
};
|
|
198
|
+
} else {
|
|
199
|
+
input ??= {
|
|
200
|
+
lastEventId: lastEventId,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
188
204
|
await ctxPromise; // asserts context has been set
|
|
189
205
|
|
|
190
206
|
const result = await callProcedure({
|
|
@@ -195,7 +211,16 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
|
|
|
195
211
|
type,
|
|
196
212
|
});
|
|
197
213
|
|
|
214
|
+
const isIterableResult =
|
|
215
|
+
isAsyncIterable(result) || isObservable(result);
|
|
216
|
+
|
|
198
217
|
if (type !== 'subscription') {
|
|
218
|
+
if (isIterableResult) {
|
|
219
|
+
throw new TRPCError({
|
|
220
|
+
code: 'UNSUPPORTED_MEDIA_TYPE',
|
|
221
|
+
message: `Cannot return an async iterable or observable from a ${type} procedure with WebSockets`,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
199
224
|
// send the value as data if the method is not a subscription
|
|
200
225
|
respond({
|
|
201
226
|
id,
|
|
@@ -208,7 +233,7 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
|
|
|
208
233
|
return;
|
|
209
234
|
}
|
|
210
235
|
|
|
211
|
-
if (!
|
|
236
|
+
if (!isIterableResult) {
|
|
212
237
|
throw new TRPCError({
|
|
213
238
|
message: `Subscription ${path} did not return an observable or a AsyncGenerator`,
|
|
214
239
|
code: 'INTERNAL_SERVER_ERROR',
|
|
@@ -277,13 +302,24 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
|
|
|
277
302
|
break;
|
|
278
303
|
}
|
|
279
304
|
|
|
305
|
+
const result: TRPCResultMessage<unknown>['result'] = {
|
|
306
|
+
type: 'data',
|
|
307
|
+
data: next.value,
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
if (isTrackedEnvelope(next.value)) {
|
|
311
|
+
const [id, data] = next.value;
|
|
312
|
+
result.id = id;
|
|
313
|
+
result.data = {
|
|
314
|
+
id,
|
|
315
|
+
data,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
280
319
|
respond({
|
|
281
320
|
id,
|
|
282
321
|
jsonrpc,
|
|
283
|
-
result
|
|
284
|
-
type: 'data',
|
|
285
|
-
data: next.value,
|
|
286
|
-
},
|
|
322
|
+
result,
|
|
287
323
|
});
|
|
288
324
|
}
|
|
289
325
|
|
|
@@ -26,7 +26,7 @@ type IsRecord<T extends object> = keyof WithoutIndexSignature<T> extends never
|
|
|
26
26
|
export type Serialize<T> =
|
|
27
27
|
IsAny<T> extends true ? any :
|
|
28
28
|
unknown extends T ? unknown :
|
|
29
|
-
T extends
|
|
29
|
+
T extends AsyncGenerator<infer $T, infer $Return, infer $Next> ? AsyncGenerator<Serialize<$T>, Serialize<$Return>, Serialize<$Next>> :
|
|
30
30
|
T extends JsonReturnable ? T :
|
|
31
31
|
T extends Map<any, any> | Set<any> ? object :
|
|
32
32
|
T extends NonJsonPrimitive ? never :
|
|
@@ -380,9 +380,21 @@ export async function resolveResponse<TRouter extends AnyRouter>(
|
|
|
380
380
|
? observableToAsyncIterable(data)
|
|
381
381
|
: data;
|
|
382
382
|
const stream = sseStreamProducer({
|
|
383
|
-
...
|
|
383
|
+
...config.experimental?.sseSubscriptions,
|
|
384
384
|
data: dataAsIterable,
|
|
385
385
|
serialize: (v) => config.transformer.output.serialize(v),
|
|
386
|
+
formatError(errorOpts) {
|
|
387
|
+
const shape = getErrorShape({
|
|
388
|
+
config,
|
|
389
|
+
ctx,
|
|
390
|
+
error: getTRPCErrorFromUnknown(errorOpts.error),
|
|
391
|
+
input: call?.result(),
|
|
392
|
+
path: call?.path,
|
|
393
|
+
type: call?.procedure?._def.type ?? 'unknown',
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
return shape;
|
|
397
|
+
},
|
|
386
398
|
});
|
|
387
399
|
for (const [key, value] of Object.entries(sseHeaders)) {
|
|
388
400
|
headers.set(key, value);
|
|
@@ -23,7 +23,7 @@ import type {
|
|
|
23
23
|
QueryProcedure,
|
|
24
24
|
SubscriptionProcedure,
|
|
25
25
|
} from './procedure';
|
|
26
|
-
import type {
|
|
26
|
+
import type { inferTrackedOutput } from './stream/tracked';
|
|
27
27
|
import type {
|
|
28
28
|
GetRawInputFn,
|
|
29
29
|
MaybePromise,
|
|
@@ -47,7 +47,7 @@ type DefaultValue<TValue, TFallback> = TValue extends UnsetMarker
|
|
|
47
47
|
type inferSubscriptionOutput<TOutput> = TOutput extends AsyncIterable<
|
|
48
48
|
infer $Output
|
|
49
49
|
>
|
|
50
|
-
?
|
|
50
|
+
? inferTrackedOutput<$Output>
|
|
51
51
|
: inferObservableValue<TOutput>;
|
|
52
52
|
|
|
53
53
|
export type CallerOverride<TContext> = (opts: {
|
|
@@ -80,7 +80,10 @@ export interface RootConfig<TTypes extends RootTypes> {
|
|
|
80
80
|
* @default true
|
|
81
81
|
*/
|
|
82
82
|
enabled?: boolean;
|
|
83
|
-
} &
|
|
83
|
+
} & Pick<
|
|
84
|
+
SSEStreamProducerOptions,
|
|
85
|
+
'ping' | 'emitAndEndImmediately' | 'maxDurationMs'
|
|
86
|
+
>;
|
|
84
87
|
};
|
|
85
88
|
}
|
|
86
89
|
|
|
@@ -49,7 +49,17 @@ export namespace JSONRPC2 {
|
|
|
49
49
|
/////////////////////////// HTTP envelopes ///////////////////////
|
|
50
50
|
|
|
51
51
|
export interface TRPCRequest
|
|
52
|
-
extends JSONRPC2.Request<
|
|
52
|
+
extends JSONRPC2.Request<
|
|
53
|
+
ProcedureType,
|
|
54
|
+
{
|
|
55
|
+
path: string;
|
|
56
|
+
input: unknown;
|
|
57
|
+
/**
|
|
58
|
+
* The last event id that the client received
|
|
59
|
+
*/
|
|
60
|
+
lastEventId?: string;
|
|
61
|
+
}
|
|
62
|
+
> {}
|
|
53
63
|
|
|
54
64
|
export interface TRPCResult<TData = unknown> {
|
|
55
65
|
data: TData;
|
|
@@ -101,7 +111,13 @@ export interface TRPCResultMessage<TData>
|
|
|
101
111
|
extends JSONRPC2.ResultResponse<
|
|
102
112
|
| { type: 'started'; data?: never }
|
|
103
113
|
| { type: 'stopped'; data?: never }
|
|
104
|
-
| (TRPCResult<TData> & {
|
|
114
|
+
| (TRPCResult<TData> & {
|
|
115
|
+
type: 'data';
|
|
116
|
+
/**
|
|
117
|
+
* The id of the message to keep track of in case of a reconnect
|
|
118
|
+
*/
|
|
119
|
+
id?: string;
|
|
120
|
+
})
|
|
105
121
|
> {}
|
|
106
122
|
|
|
107
123
|
export type TRPCResponseMessage<
|
|
@@ -67,9 +67,12 @@ export function parseTRPCMessage(
|
|
|
67
67
|
}
|
|
68
68
|
assertIsProcedureType(method);
|
|
69
69
|
assertIsObject(params);
|
|
70
|
-
const { input: rawInput, path } = params;
|
|
70
|
+
const { input: rawInput, path, lastEventId } = params;
|
|
71
71
|
|
|
72
72
|
assertIsString(path);
|
|
73
|
+
if (lastEventId !== undefined) {
|
|
74
|
+
assertIsString(lastEventId);
|
|
75
|
+
}
|
|
73
76
|
|
|
74
77
|
const input = transformer.input.deserialize(rawInput);
|
|
75
78
|
|
|
@@ -80,6 +83,7 @@ export function parseTRPCMessage(
|
|
|
80
83
|
params: {
|
|
81
84
|
input,
|
|
82
85
|
path,
|
|
86
|
+
lastEventId,
|
|
83
87
|
},
|
|
84
88
|
};
|
|
85
89
|
}
|
|
@@ -30,8 +30,8 @@ type PROMISE_STATUS_FULFILLED = typeof PROMISE_STATUS_FULFILLED;
|
|
|
30
30
|
const PROMISE_STATUS_REJECTED = 1;
|
|
31
31
|
type PROMISE_STATUS_REJECTED = typeof PROMISE_STATUS_REJECTED;
|
|
32
32
|
|
|
33
|
-
const
|
|
34
|
-
type
|
|
33
|
+
const ASYNC_ITERABLE_STATUS_RETURN = 0;
|
|
34
|
+
type ASYNC_ITERABLE_STATUS_RETURN = typeof ASYNC_ITERABLE_STATUS_RETURN;
|
|
35
35
|
const ASYNC_ITERABLE_STATUS_VALUE = 1;
|
|
36
36
|
type ASYNC_ITERABLE_STATUS_VALUE = typeof ASYNC_ITERABLE_STATUS_VALUE;
|
|
37
37
|
const ASYNC_ITERABLE_STATUS_ERROR = 2;
|
|
@@ -70,7 +70,11 @@ type PromiseChunk =
|
|
|
70
70
|
]
|
|
71
71
|
| [chunkIndex: ChunkIndex, status: PROMISE_STATUS_REJECTED, error: unknown];
|
|
72
72
|
type IterableChunk =
|
|
73
|
-
| [
|
|
73
|
+
| [
|
|
74
|
+
chunkIndex: ChunkIndex,
|
|
75
|
+
status: ASYNC_ITERABLE_STATUS_RETURN,
|
|
76
|
+
value: DehydratedValue,
|
|
77
|
+
]
|
|
74
78
|
| [
|
|
75
79
|
chunkIndex: ChunkIndex,
|
|
76
80
|
status: ASYNC_ITERABLE_STATUS_VALUE,
|
|
@@ -204,7 +208,11 @@ function createBatchStreamProducer(opts: ProducerOptions) {
|
|
|
204
208
|
break;
|
|
205
209
|
}
|
|
206
210
|
if (next.done) {
|
|
207
|
-
stream.controller.enqueue([
|
|
211
|
+
stream.controller.enqueue([
|
|
212
|
+
idx,
|
|
213
|
+
ASYNC_ITERABLE_STATUS_RETURN,
|
|
214
|
+
dehydrate(next.value, path),
|
|
215
|
+
]);
|
|
208
216
|
break;
|
|
209
217
|
}
|
|
210
218
|
stream.controller.enqueue([
|
|
@@ -541,12 +549,12 @@ export async function jsonlStreamConsumer<THead>(opts: {
|
|
|
541
549
|
done: false,
|
|
542
550
|
value: hydrate(data),
|
|
543
551
|
};
|
|
544
|
-
case
|
|
552
|
+
case ASYNC_ITERABLE_STATUS_RETURN:
|
|
545
553
|
controllers.delete(chunkId);
|
|
546
554
|
maybeAbort();
|
|
547
555
|
return {
|
|
548
556
|
done: true,
|
|
549
|
-
value:
|
|
557
|
+
value: hydrate(data),
|
|
550
558
|
};
|
|
551
559
|
case ASYNC_ITERABLE_STATUS_ERROR:
|
|
552
560
|
controllers.delete(chunkId);
|
|
@@ -1,59 +1,14 @@
|
|
|
1
1
|
import { getTRPCErrorFromUnknown } from '../error/TRPCError';
|
|
2
|
-
import type { ValidateShape } from '../types';
|
|
3
2
|
import { run } from '../utils';
|
|
4
3
|
import type { ConsumerOnError } from './jsonl';
|
|
4
|
+
import type { inferTrackedOutput } from './tracked';
|
|
5
|
+
import { isTrackedEnvelope } from './tracked';
|
|
5
6
|
import { createTimeoutPromise } from './utils/createDeferred';
|
|
6
7
|
import { createReadableStream } from './utils/createReadableStream';
|
|
7
8
|
|
|
8
9
|
type Serialize = (value: any) => any;
|
|
9
10
|
type Deserialize = (value: any) => any;
|
|
10
11
|
|
|
11
|
-
/**
|
|
12
|
-
* Server-sent Event Message
|
|
13
|
-
* @see https://html.spec.whatwg.org/multipage/server-sent-events.html
|
|
14
|
-
* @public
|
|
15
|
-
*/
|
|
16
|
-
export interface SSEMessage {
|
|
17
|
-
/**
|
|
18
|
-
* The data field of the message - this can be anything
|
|
19
|
-
*/
|
|
20
|
-
data: unknown;
|
|
21
|
-
/**
|
|
22
|
-
* The id for this message
|
|
23
|
-
* Passing this id will allow the client to resume the connection from this point if the connection is lost
|
|
24
|
-
* @see https://html.spec.whatwg.org/multipage/server-sent-events.html#the-last-event-id-header
|
|
25
|
-
*/
|
|
26
|
-
id: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const sseSymbol = Symbol('SSEMessageEnvelope');
|
|
30
|
-
export type SSEMessageEnvelope<TData> = [typeof sseSymbol, TData];
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Produce a typed server-sent event message
|
|
34
|
-
*/
|
|
35
|
-
export function sse<TData extends SSEMessage>(
|
|
36
|
-
event: ValidateShape<TData, SSEMessage>,
|
|
37
|
-
): SSEMessageEnvelope<TData> {
|
|
38
|
-
if (event.id === '') {
|
|
39
|
-
// This could be removed by using different event names for `yield sse(x)`-emitted events and `yield y`-emitted events
|
|
40
|
-
throw new Error(
|
|
41
|
-
'`id` must not be an empty string as empty string is the same as not setting the id at all',
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
return [sseSymbol, event as TData];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function isSSEMessageEnvelope<TData extends SSEMessage>(
|
|
48
|
-
value: unknown,
|
|
49
|
-
): value is SSEMessageEnvelope<TData> {
|
|
50
|
-
return Array.isArray(value) && value[0] === sseSymbol;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export type SerializedSSEvent = Omit<SSEMessage, 'data'> & {
|
|
54
|
-
data?: string;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
12
|
/**
|
|
58
13
|
* @internal
|
|
59
14
|
*/
|
|
@@ -87,14 +42,17 @@ export interface SSEStreamProducerOptions {
|
|
|
87
42
|
* @default false
|
|
88
43
|
*/
|
|
89
44
|
emitAndEndImmediately?: boolean;
|
|
45
|
+
formatError?: (opts: { error: unknown }) => unknown;
|
|
90
46
|
}
|
|
91
47
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
48
|
+
const SERIALIZED_ERROR_EVENT = 'serialized-error';
|
|
49
|
+
|
|
50
|
+
type SSEvent = Partial<{
|
|
51
|
+
id: string;
|
|
52
|
+
data: unknown;
|
|
53
|
+
comment: string;
|
|
54
|
+
event: string;
|
|
55
|
+
}>;
|
|
98
56
|
/**
|
|
99
57
|
*
|
|
100
58
|
* @see https://html.spec.whatwg.org/multipage/server-sent-events.html
|
|
@@ -151,7 +109,13 @@ export function sseStreamProducer(opts: SSEStreamProducerOptions) {
|
|
|
151
109
|
}
|
|
152
110
|
|
|
153
111
|
if (next instanceof Error) {
|
|
154
|
-
|
|
112
|
+
const data = opts.formatError
|
|
113
|
+
? opts.formatError({ error: next })
|
|
114
|
+
: null;
|
|
115
|
+
stream.controller.enqueue({
|
|
116
|
+
event: SERIALIZED_ERROR_EVENT,
|
|
117
|
+
data: JSON.stringify(serialize(data)),
|
|
118
|
+
});
|
|
155
119
|
break;
|
|
156
120
|
}
|
|
157
121
|
if (next.done) {
|
|
@@ -160,8 +124,11 @@ export function sseStreamProducer(opts: SSEStreamProducerOptions) {
|
|
|
160
124
|
|
|
161
125
|
const value = next.value;
|
|
162
126
|
|
|
163
|
-
const chunk: SSEvent =
|
|
164
|
-
? {
|
|
127
|
+
const chunk: SSEvent = isTrackedEnvelope(value)
|
|
128
|
+
? {
|
|
129
|
+
id: value[0],
|
|
130
|
+
data: value[1],
|
|
131
|
+
}
|
|
165
132
|
: {
|
|
166
133
|
data: value,
|
|
167
134
|
};
|
|
@@ -207,41 +174,65 @@ export function sseStreamProducer(opts: SSEStreamProducerOptions) {
|
|
|
207
174
|
}),
|
|
208
175
|
);
|
|
209
176
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
177
|
+
|
|
178
|
+
type ConsumerStreamResult<TData> =
|
|
179
|
+
| {
|
|
180
|
+
ok: true;
|
|
181
|
+
data: inferTrackedOutput<TData>;
|
|
182
|
+
}
|
|
183
|
+
| {
|
|
184
|
+
ok: false;
|
|
185
|
+
error: unknown;
|
|
186
|
+
};
|
|
187
|
+
|
|
215
188
|
/**
|
|
216
189
|
* @see https://html.spec.whatwg.org/multipage/server-sent-events.html
|
|
217
190
|
*/
|
|
218
|
-
|
|
219
191
|
export function sseStreamConsumer<TData>(opts: {
|
|
220
192
|
from: EventSource;
|
|
221
193
|
onError?: ConsumerOnError;
|
|
222
194
|
deserialize?: Deserialize;
|
|
223
|
-
}): AsyncIterable<
|
|
195
|
+
}): AsyncIterable<ConsumerStreamResult<TData>> {
|
|
224
196
|
const { deserialize = (v) => v } = opts;
|
|
225
197
|
const eventSource = opts.from;
|
|
226
198
|
|
|
227
199
|
const stream = createReadableStream<MessageEvent>();
|
|
228
200
|
|
|
229
|
-
const transform = new TransformStream<
|
|
201
|
+
const transform = new TransformStream<
|
|
202
|
+
MessageEvent,
|
|
203
|
+
ConsumerStreamResult<TData>
|
|
204
|
+
>({
|
|
230
205
|
async transform(chunk, controller) {
|
|
231
|
-
const
|
|
232
|
-
|
|
206
|
+
const data = deserialize(JSON.parse(chunk.data));
|
|
207
|
+
if (chunk.type === SERIALIZED_ERROR_EVENT) {
|
|
208
|
+
controller.enqueue({
|
|
209
|
+
ok: false,
|
|
210
|
+
error: data,
|
|
211
|
+
});
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
// console.debug('transforming', chunk.type, chunk.data);
|
|
215
|
+
const def: SSEvent = {
|
|
216
|
+
data,
|
|
233
217
|
};
|
|
234
218
|
|
|
235
219
|
if (chunk.lastEventId) {
|
|
236
220
|
def.id = chunk.lastEventId;
|
|
237
221
|
}
|
|
238
|
-
|
|
222
|
+
|
|
223
|
+
controller.enqueue({
|
|
224
|
+
ok: true,
|
|
225
|
+
data: def as inferTrackedOutput<TData>,
|
|
226
|
+
});
|
|
239
227
|
},
|
|
240
228
|
});
|
|
241
229
|
|
|
242
230
|
eventSource.addEventListener('message', (msg) => {
|
|
243
231
|
stream.controller.enqueue(msg);
|
|
244
232
|
});
|
|
233
|
+
eventSource.addEventListener(SERIALIZED_ERROR_EVENT, (msg) => {
|
|
234
|
+
stream.controller.enqueue(msg);
|
|
235
|
+
});
|
|
245
236
|
eventSource.addEventListener('error', (cause) => {
|
|
246
237
|
if (eventSource.readyState === EventSource.CLOSED) {
|
|
247
238
|
stream.controller.error(cause);
|
|
@@ -253,7 +244,7 @@ export function sseStreamConsumer<TData>(opts: {
|
|
|
253
244
|
[Symbol.asyncIterator]() {
|
|
254
245
|
const reader = readable.getReader();
|
|
255
246
|
|
|
256
|
-
const iterator: AsyncIterator<
|
|
247
|
+
const iterator: AsyncIterator<ConsumerStreamResult<TData>> = {
|
|
257
248
|
async next() {
|
|
258
249
|
const value = await reader.read();
|
|
259
250
|
if (value.done) {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const trackedSymbol = Symbol('TrackedEnvelope');
|
|
2
|
+
|
|
3
|
+
type TrackedId = string & {
|
|
4
|
+
__brand: 'TrackedId';
|
|
5
|
+
};
|
|
6
|
+
export type TrackedEnvelope<TData> = [TrackedId, TData, typeof trackedSymbol];
|
|
7
|
+
|
|
8
|
+
type Tracked<TData> = {
|
|
9
|
+
/**
|
|
10
|
+
* The id of the message to keep track of in case the connection gets lost
|
|
11
|
+
*/
|
|
12
|
+
id: string;
|
|
13
|
+
/**
|
|
14
|
+
* The data field of the message - this can be anything
|
|
15
|
+
*/
|
|
16
|
+
data: TData;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Produce a typed server-sent event message
|
|
20
|
+
* @deprecated use `tracked(id, data)` instead
|
|
21
|
+
*/
|
|
22
|
+
export function sse<TData>(event: {
|
|
23
|
+
id: string;
|
|
24
|
+
data: TData;
|
|
25
|
+
}): TrackedEnvelope<TData> {
|
|
26
|
+
return tracked(event.id, event.data);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isTrackedEnvelope<TData>(
|
|
30
|
+
value: unknown,
|
|
31
|
+
): value is TrackedEnvelope<TData> {
|
|
32
|
+
return Array.isArray(value) && value[2] === trackedSymbol;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Automatically track an event so that it can be resumed from a given id if the connection is lost
|
|
37
|
+
*/
|
|
38
|
+
export function tracked<TData>(
|
|
39
|
+
id: string,
|
|
40
|
+
data: TData,
|
|
41
|
+
): TrackedEnvelope<TData> {
|
|
42
|
+
if (id === '') {
|
|
43
|
+
// This limitation could be removed by using different SSE event names / channels for tracked event and non-tracked event
|
|
44
|
+
throw new Error(
|
|
45
|
+
'`id` must not be an empty string as empty string is the same as not setting the id at all',
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return [id as TrackedId, data, trackedSymbol];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type inferTrackedOutput<TData> = TData extends TrackedEnvelope<
|
|
52
|
+
infer $Data
|
|
53
|
+
>
|
|
54
|
+
? Tracked<$Data>
|
|
55
|
+
: TData;
|
|
@@ -34,6 +34,7 @@ export * from './unstable-core-do-not-import/router';
|
|
|
34
34
|
export * from './unstable-core-do-not-import/rpc';
|
|
35
35
|
export * from './unstable-core-do-not-import/stream/jsonl';
|
|
36
36
|
export * from './unstable-core-do-not-import/stream/sse';
|
|
37
|
+
export * from './unstable-core-do-not-import/stream/tracked';
|
|
37
38
|
export * from './unstable-core-do-not-import/stream/utils/createDeferred';
|
|
38
39
|
export * from './unstable-core-do-not-import/transformer';
|
|
39
40
|
export * from './unstable-core-do-not-import/types';
|