@trpc/server 11.0.0-rc.746 → 11.0.0-rc.748
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 +1 -1
- package/dist/@trpc/server/index.d.ts.map +1 -1
- package/dist/adapters/node-http/nodeHTTPRequestHandler.js +1 -1
- package/dist/adapters/node-http/nodeHTTPRequestHandler.mjs +1 -1
- package/dist/adapters/ws.js +2 -2
- package/dist/adapters/ws.mjs +2 -2
- package/dist/bundle-analysis.json +122 -119
- package/dist/index.js +4 -3
- package/dist/index.mjs +1 -1
- package/dist/unstable-core-do-not-import/http/contentType.d.ts +2 -2
- package/dist/unstable-core-do-not-import/http/contentType.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/http/contentType.js +12 -10
- package/dist/unstable-core-do-not-import/http/contentType.mjs +12 -10
- package/dist/unstable-core-do-not-import/http/resolveResponse.js +2 -2
- package/dist/unstable-core-do-not-import/http/resolveResponse.mjs +2 -2
- package/dist/unstable-core-do-not-import/procedureBuilder.js +1 -0
- package/dist/unstable-core-do-not-import/procedureBuilder.mjs +1 -0
- package/dist/unstable-core-do-not-import/router.d.ts +20 -3
- package/dist/unstable-core-do-not-import/router.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/router.js +102 -6
- package/dist/unstable-core-do-not-import/router.mjs +102 -8
- package/dist/unstable-core-do-not-import/stream/jsonl.d.ts +3 -9
- package/dist/unstable-core-do-not-import/stream/jsonl.d.ts.map +1 -1
- package/dist/unstable-core-do-not-import/stream/jsonl.js +24 -33
- package/dist/unstable-core-do-not-import/stream/jsonl.mjs +24 -33
- package/dist/unstable-core-do-not-import.js +2 -0
- package/dist/unstable-core-do-not-import.mjs +1 -1
- package/package.json +2 -2
- package/src/@trpc/server/index.ts +1 -0
- package/src/adapters/ws.ts +1 -1
- package/src/unstable-core-do-not-import/http/contentType.ts +48 -42
- package/src/unstable-core-do-not-import/http/resolveResponse.ts +2 -2
- package/src/unstable-core-do-not-import/procedureBuilder.ts +1 -0
- package/src/unstable-core-do-not-import/router.ts +156 -14
- package/src/unstable-core-do-not-import/stream/jsonl.ts +28 -40
|
@@ -13,7 +13,13 @@ import type { ProcedureCallOptions } from './procedureBuilder';
|
|
|
13
13
|
import type { AnyRootTypes, RootConfig } from './rootConfig';
|
|
14
14
|
import { defaultTransformer } from './transformer';
|
|
15
15
|
import type { MaybePromise, ValueOf } from './types';
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
isFunction,
|
|
18
|
+
isObject,
|
|
19
|
+
mergeWithoutOverrides,
|
|
20
|
+
omitPrototype,
|
|
21
|
+
run,
|
|
22
|
+
} from './utils';
|
|
17
23
|
|
|
18
24
|
export interface RouterRecord {
|
|
19
25
|
[key: string]: AnyProcedure | RouterRecord;
|
|
@@ -69,6 +75,58 @@ export type RouterCaller<
|
|
|
69
75
|
},
|
|
70
76
|
) => DecorateRouterRecord<TRecord>;
|
|
71
77
|
|
|
78
|
+
const lazySymbol = Symbol('lazy');
|
|
79
|
+
export type Lazy<TAny> = (() => Promise<TAny>) & { [lazySymbol]: true };
|
|
80
|
+
|
|
81
|
+
type LazyLoader<TAny> = {
|
|
82
|
+
load: () => Promise<void>;
|
|
83
|
+
ref: Lazy<TAny>;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Lazy load a router
|
|
88
|
+
* @see https://trpc.io/docs/server/merging-routers#lazy-load
|
|
89
|
+
*/
|
|
90
|
+
export function lazy<TRouter extends AnyRouter>(
|
|
91
|
+
getRouter: () => Promise<
|
|
92
|
+
| TRouter
|
|
93
|
+
| {
|
|
94
|
+
[key: string]: TRouter;
|
|
95
|
+
}
|
|
96
|
+
>,
|
|
97
|
+
): Lazy<NoInfer<TRouter>> {
|
|
98
|
+
let cachedPromise: Promise<TRouter> | null = null;
|
|
99
|
+
const lazyGetter = (() => {
|
|
100
|
+
if (!cachedPromise) {
|
|
101
|
+
cachedPromise = run(async (): Promise<TRouter> => {
|
|
102
|
+
const mod = await getRouter();
|
|
103
|
+
|
|
104
|
+
// if the module is a router, return it
|
|
105
|
+
if (isRouter(mod)) {
|
|
106
|
+
return mod;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const routers = Object.values(mod);
|
|
110
|
+
|
|
111
|
+
if (routers.length !== 1 || !isRouter(routers[0])) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
"Invalid router module - either define exactly 1 export or return the router directly.\nExample: `experimental_lazy(() => import('./slow.js').then((m) => m.slowRouter))`",
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return routers[0];
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
return cachedPromise;
|
|
121
|
+
}) as Lazy<TRouter>;
|
|
122
|
+
lazyGetter[lazySymbol] = true;
|
|
123
|
+
return lazyGetter;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function isLazy<TAny>(input: unknown): input is Lazy<TAny> {
|
|
127
|
+
return typeof input === 'function' && lazySymbol in input;
|
|
128
|
+
}
|
|
129
|
+
|
|
72
130
|
export interface Router<
|
|
73
131
|
TRoot extends AnyRootTypes,
|
|
74
132
|
TRecord extends RouterRecord,
|
|
@@ -79,6 +137,7 @@ export interface Router<
|
|
|
79
137
|
procedure?: never;
|
|
80
138
|
procedures: TRecord;
|
|
81
139
|
record: TRecord;
|
|
140
|
+
lazy: Record<string, LazyLoader<AnyRouter>>;
|
|
82
141
|
};
|
|
83
142
|
/**
|
|
84
143
|
* @deprecated use `t.createCallerFactory(router)` instead
|
|
@@ -104,10 +163,10 @@ export type inferRouterError<TRouter extends AnyRouter> =
|
|
|
104
163
|
export type inferRouterMeta<TRouter extends AnyRouter> =
|
|
105
164
|
inferRouterRootTypes<TRouter>['meta'];
|
|
106
165
|
|
|
107
|
-
function isRouter(
|
|
108
|
-
|
|
109
|
-
)
|
|
110
|
-
|
|
166
|
+
function isRouter(value: unknown): value is AnyRouter {
|
|
167
|
+
return (
|
|
168
|
+
isObject(value) && isObject(value['_def']) && 'router' in value['_def']
|
|
169
|
+
);
|
|
111
170
|
}
|
|
112
171
|
|
|
113
172
|
const emptyRouter = {
|
|
@@ -138,7 +197,11 @@ const reservedWords = [
|
|
|
138
197
|
];
|
|
139
198
|
|
|
140
199
|
export type CreateRouterOptions = {
|
|
141
|
-
[key: string]:
|
|
200
|
+
[key: string]:
|
|
201
|
+
| AnyProcedure
|
|
202
|
+
| AnyRouter
|
|
203
|
+
| CreateRouterOptions
|
|
204
|
+
| Lazy<AnyRouter>;
|
|
142
205
|
};
|
|
143
206
|
|
|
144
207
|
export type DecorateCreateRouterOptions<
|
|
@@ -149,9 +212,11 @@ export type DecorateCreateRouterOptions<
|
|
|
149
212
|
? $Value
|
|
150
213
|
: $Value extends Router<any, infer TRecord>
|
|
151
214
|
? TRecord
|
|
152
|
-
: $Value extends
|
|
153
|
-
?
|
|
154
|
-
:
|
|
215
|
+
: $Value extends Lazy<Router<any, infer TRecord>>
|
|
216
|
+
? TRecord
|
|
217
|
+
: $Value extends CreateRouterOptions
|
|
218
|
+
? DecorateCreateRouterOptions<$Value>
|
|
219
|
+
: never
|
|
155
220
|
: never;
|
|
156
221
|
};
|
|
157
222
|
|
|
@@ -175,10 +240,55 @@ export function createRouterFactory<TRoot extends AnyRootTypes>(
|
|
|
175
240
|
}
|
|
176
241
|
|
|
177
242
|
const procedures: Record<string, AnyProcedure> = omitPrototype({});
|
|
243
|
+
const lazy: Record<string, LazyLoader<AnyRouter>> = omitPrototype({});
|
|
244
|
+
|
|
245
|
+
function createLazyLoader(opts: {
|
|
246
|
+
ref: Lazy<AnyRouter>;
|
|
247
|
+
path: readonly string[];
|
|
248
|
+
key: string;
|
|
249
|
+
aggregate: RouterRecord;
|
|
250
|
+
}): LazyLoader<AnyRouter> {
|
|
251
|
+
return {
|
|
252
|
+
ref: opts.ref,
|
|
253
|
+
load: async () => {
|
|
254
|
+
const router = await opts.ref();
|
|
255
|
+
const lazyPath = [...opts.path, opts.key];
|
|
256
|
+
const lazyKey = lazyPath.join('.');
|
|
257
|
+
|
|
258
|
+
opts.aggregate[opts.key] = step(router._def.record, lazyPath);
|
|
259
|
+
|
|
260
|
+
delete lazy[lazyKey];
|
|
261
|
+
|
|
262
|
+
// add lazy loaders for nested routers
|
|
263
|
+
for (const [nestedKey, nestedItem] of Object.entries(
|
|
264
|
+
router._def.lazy,
|
|
265
|
+
)) {
|
|
266
|
+
const nestedRouterKey = [...lazyPath, nestedKey].join('.');
|
|
267
|
+
|
|
268
|
+
// console.log('adding lazy', nestedRouterKey);
|
|
269
|
+
lazy[nestedRouterKey] = createLazyLoader({
|
|
270
|
+
ref: nestedItem.ref,
|
|
271
|
+
path: lazyPath,
|
|
272
|
+
key: nestedKey,
|
|
273
|
+
aggregate: opts.aggregate[opts.key] as RouterRecord,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
}
|
|
178
279
|
|
|
179
280
|
function step(from: CreateRouterOptions, path: readonly string[] = []) {
|
|
180
281
|
const aggregate: RouterRecord = omitPrototype({});
|
|
181
282
|
for (const [key, item] of Object.entries(from ?? {})) {
|
|
283
|
+
if (isLazy(item)) {
|
|
284
|
+
lazy[[...path, key].join('.')] = createLazyLoader({
|
|
285
|
+
path,
|
|
286
|
+
ref: item,
|
|
287
|
+
key,
|
|
288
|
+
aggregate,
|
|
289
|
+
});
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
182
292
|
if (isRouter(item)) {
|
|
183
293
|
aggregate[key] = step(item._def.record, [...path, key]);
|
|
184
294
|
continue;
|
|
@@ -207,6 +317,7 @@ export function createRouterFactory<TRoot extends AnyRootTypes>(
|
|
|
207
317
|
_config: config,
|
|
208
318
|
router: true,
|
|
209
319
|
procedures,
|
|
320
|
+
lazy,
|
|
210
321
|
...emptyRouter,
|
|
211
322
|
record,
|
|
212
323
|
};
|
|
@@ -229,17 +340,42 @@ function isProcedure(
|
|
|
229
340
|
): procedureOrRouter is AnyProcedure {
|
|
230
341
|
return typeof procedureOrRouter === 'function';
|
|
231
342
|
}
|
|
343
|
+
|
|
344
|
+
export async function getProcedureAtPath(
|
|
345
|
+
_def: AnyRouter['_def'],
|
|
346
|
+
path: string,
|
|
347
|
+
): Promise<AnyProcedure | null> {
|
|
348
|
+
let procedure = _def.procedures[path];
|
|
349
|
+
|
|
350
|
+
while (!procedure) {
|
|
351
|
+
const key = Object.keys(_def.lazy).find((key) => path.startsWith(key));
|
|
352
|
+
// console.log(`found lazy: ${key ?? 'NOPE'} (fullPath: ${fullPath})`);
|
|
353
|
+
|
|
354
|
+
if (!key) {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
// console.log('loading', key, '.......');
|
|
358
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
359
|
+
const lazyRouter = _def.lazy[key]!;
|
|
360
|
+
await lazyRouter.load();
|
|
361
|
+
|
|
362
|
+
procedure = _def.procedures[path];
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return procedure;
|
|
366
|
+
}
|
|
367
|
+
|
|
232
368
|
/**
|
|
233
369
|
* @internal
|
|
234
370
|
*/
|
|
235
|
-
export function callProcedure(
|
|
371
|
+
export async function callProcedure(
|
|
236
372
|
opts: ProcedureCallOptions<unknown> & {
|
|
237
|
-
|
|
373
|
+
_def: AnyRouter['_def'];
|
|
238
374
|
allowMethodOverride?: boolean;
|
|
239
375
|
},
|
|
240
376
|
) {
|
|
241
377
|
const { type, path } = opts;
|
|
242
|
-
const proc = opts.
|
|
378
|
+
const proc = await getProcedureAtPath(opts._def, path);
|
|
243
379
|
if (
|
|
244
380
|
!proc ||
|
|
245
381
|
!isProcedure(proc) ||
|
|
@@ -282,10 +418,16 @@ export function createCallerFactory<TRoot extends AnyRootTypes>() {
|
|
|
282
418
|
return _def;
|
|
283
419
|
}
|
|
284
420
|
|
|
285
|
-
const procedure = _def
|
|
421
|
+
const procedure = await getProcedureAtPath(_def, fullPath);
|
|
286
422
|
|
|
287
423
|
let ctx: Context | undefined = undefined;
|
|
288
424
|
try {
|
|
425
|
+
if (!procedure) {
|
|
426
|
+
throw new TRPCError({
|
|
427
|
+
code: 'NOT_FOUND',
|
|
428
|
+
message: `No procedure found on path "${path}"`,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
289
431
|
ctx = isFunction(ctxOrCallback)
|
|
290
432
|
? await Promise.resolve(ctxOrCallback())
|
|
291
433
|
: ctxOrCallback;
|
|
@@ -303,7 +445,7 @@ export function createCallerFactory<TRoot extends AnyRootTypes>() {
|
|
|
303
445
|
error: getTRPCErrorFromUnknown(cause),
|
|
304
446
|
input: args[0],
|
|
305
447
|
path: fullPath,
|
|
306
|
-
type: procedure
|
|
448
|
+
type: procedure?._def.type ?? 'unknown',
|
|
307
449
|
});
|
|
308
450
|
throw cause;
|
|
309
451
|
}
|
|
@@ -309,13 +309,6 @@ export function jsonlStreamProducer(opts: JSONLProducerOptions) {
|
|
|
309
309
|
.pipeThrough(new TextEncoderStream());
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
-
class StreamInterruptedError extends Error {
|
|
313
|
-
constructor(cause?: unknown) {
|
|
314
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
315
|
-
// @ts-ignore https://github.com/tc39/proposal-error-cause
|
|
316
|
-
super('Invalid response or stream interrupted', { cause });
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
312
|
class AsyncError extends Error {
|
|
320
313
|
constructor(public readonly data: unknown) {
|
|
321
314
|
super('Received error from server');
|
|
@@ -403,10 +396,6 @@ function createConsumerStream<THead>(
|
|
|
403
396
|
}),
|
|
404
397
|
);
|
|
405
398
|
}
|
|
406
|
-
/**
|
|
407
|
-
* Represents a chunk of data or stream interruption error that can be enqueued to a controller
|
|
408
|
-
*/
|
|
409
|
-
type ControllerChunk = ChunkData | StreamInterruptedError;
|
|
410
399
|
|
|
411
400
|
/**
|
|
412
401
|
* Creates a handler for managing stream controllers and their lifecycle
|
|
@@ -428,29 +417,19 @@ function createStreamsManager(abortController: AbortController) {
|
|
|
428
417
|
* Creates a stream controller
|
|
429
418
|
*/
|
|
430
419
|
function createStreamController() {
|
|
431
|
-
let originalController: ReadableStreamDefaultController<
|
|
432
|
-
const stream = new ReadableStream<
|
|
420
|
+
let originalController: ReadableStreamDefaultController<ChunkData>;
|
|
421
|
+
const stream = new ReadableStream<ChunkData>({
|
|
433
422
|
start(controller) {
|
|
434
423
|
originalController = controller;
|
|
435
424
|
},
|
|
436
425
|
});
|
|
437
426
|
|
|
438
427
|
const streamController = {
|
|
439
|
-
enqueue: (v:
|
|
428
|
+
enqueue: (v: ChunkData) => originalController.enqueue(v),
|
|
440
429
|
close: () => {
|
|
441
430
|
originalController.close();
|
|
442
431
|
|
|
443
|
-
|
|
444
|
-
Object.assign(streamController, {
|
|
445
|
-
closed: true,
|
|
446
|
-
close: () => {
|
|
447
|
-
// noop
|
|
448
|
-
},
|
|
449
|
-
enqueue: () => {
|
|
450
|
-
// noop
|
|
451
|
-
},
|
|
452
|
-
getReaderResource: null,
|
|
453
|
-
});
|
|
432
|
+
clear();
|
|
454
433
|
|
|
455
434
|
if (isEmpty()) {
|
|
456
435
|
abortController.abort();
|
|
@@ -465,7 +444,26 @@ function createStreamsManager(abortController: AbortController) {
|
|
|
465
444
|
streamController.close();
|
|
466
445
|
});
|
|
467
446
|
},
|
|
447
|
+
error: (reason: unknown) => {
|
|
448
|
+
originalController.error(reason);
|
|
449
|
+
clear();
|
|
450
|
+
},
|
|
468
451
|
};
|
|
452
|
+
function clear() {
|
|
453
|
+
Object.assign(streamController, {
|
|
454
|
+
closed: true,
|
|
455
|
+
close: () => {
|
|
456
|
+
// noop
|
|
457
|
+
},
|
|
458
|
+
enqueue: () => {
|
|
459
|
+
// noop
|
|
460
|
+
},
|
|
461
|
+
getReaderResource: null,
|
|
462
|
+
error: () => {
|
|
463
|
+
// noop
|
|
464
|
+
},
|
|
465
|
+
});
|
|
466
|
+
}
|
|
469
467
|
|
|
470
468
|
return streamController;
|
|
471
469
|
}
|
|
@@ -486,10 +484,8 @@ function createStreamsManager(abortController: AbortController) {
|
|
|
486
484
|
* Cancels all pending controllers and rejects deferred promises
|
|
487
485
|
*/
|
|
488
486
|
function cancelAll(reason: unknown) {
|
|
489
|
-
const error = new StreamInterruptedError(reason);
|
|
490
487
|
for (const controller of controllerMap.values()) {
|
|
491
|
-
controller.
|
|
492
|
-
controller.close();
|
|
488
|
+
controller.error(reason);
|
|
493
489
|
}
|
|
494
490
|
}
|
|
495
491
|
|
|
@@ -541,9 +537,6 @@ export async function jsonlStreamConsumer<THead>(opts: {
|
|
|
541
537
|
using reader = controller.getReaderResource();
|
|
542
538
|
|
|
543
539
|
const { value } = await reader.read();
|
|
544
|
-
if (value instanceof StreamInterruptedError) {
|
|
545
|
-
throw value;
|
|
546
|
-
}
|
|
547
540
|
const [_chunkId, status, data] = value as PromiseChunk;
|
|
548
541
|
switch (status) {
|
|
549
542
|
case PROMISE_STATUS_FULFILLED:
|
|
@@ -559,9 +552,6 @@ export async function jsonlStreamConsumer<THead>(opts: {
|
|
|
559
552
|
|
|
560
553
|
while (true) {
|
|
561
554
|
const { value } = await reader.read();
|
|
562
|
-
if (value instanceof StreamInterruptedError) {
|
|
563
|
-
throw value;
|
|
564
|
-
}
|
|
565
555
|
|
|
566
556
|
const [_chunkId, status, data] = value as IterableChunk;
|
|
567
557
|
|
|
@@ -598,11 +588,9 @@ export async function jsonlStreamConsumer<THead>(opts: {
|
|
|
598
588
|
return data;
|
|
599
589
|
}
|
|
600
590
|
|
|
601
|
-
const closeOrAbort = (reason
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
headDeferred?.reject(error);
|
|
605
|
-
streamManager.cancelAll(error);
|
|
591
|
+
const closeOrAbort = (reason: unknown) => {
|
|
592
|
+
headDeferred?.reject(reason);
|
|
593
|
+
streamManager.cancelAll(reason);
|
|
606
594
|
};
|
|
607
595
|
source
|
|
608
596
|
.pipeTo(
|
|
@@ -626,7 +614,7 @@ export async function jsonlStreamConsumer<THead>(opts: {
|
|
|
626
614
|
const controller = streamManager.getOrCreate(idx);
|
|
627
615
|
controller.enqueue(chunk);
|
|
628
616
|
},
|
|
629
|
-
close: closeOrAbort,
|
|
617
|
+
close: () => closeOrAbort(new Error('Stream closed')),
|
|
630
618
|
abort: closeOrAbort,
|
|
631
619
|
}),
|
|
632
620
|
{
|