@trpc/server 11.0.0-rc.747 → 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.
Files changed (30) hide show
  1. package/dist/@trpc/server/index.d.ts +1 -1
  2. package/dist/@trpc/server/index.d.ts.map +1 -1
  3. package/dist/adapters/node-http/nodeHTTPRequestHandler.js +1 -1
  4. package/dist/adapters/node-http/nodeHTTPRequestHandler.mjs +1 -1
  5. package/dist/adapters/ws.js +2 -2
  6. package/dist/adapters/ws.mjs +2 -2
  7. package/dist/bundle-analysis.json +92 -89
  8. package/dist/index.js +4 -3
  9. package/dist/index.mjs +1 -1
  10. package/dist/unstable-core-do-not-import/http/contentType.d.ts +2 -2
  11. package/dist/unstable-core-do-not-import/http/contentType.d.ts.map +1 -1
  12. package/dist/unstable-core-do-not-import/http/contentType.js +12 -10
  13. package/dist/unstable-core-do-not-import/http/contentType.mjs +12 -10
  14. package/dist/unstable-core-do-not-import/http/resolveResponse.js +2 -2
  15. package/dist/unstable-core-do-not-import/http/resolveResponse.mjs +2 -2
  16. package/dist/unstable-core-do-not-import/procedureBuilder.js +1 -0
  17. package/dist/unstable-core-do-not-import/procedureBuilder.mjs +1 -0
  18. package/dist/unstable-core-do-not-import/router.d.ts +20 -3
  19. package/dist/unstable-core-do-not-import/router.d.ts.map +1 -1
  20. package/dist/unstable-core-do-not-import/router.js +102 -6
  21. package/dist/unstable-core-do-not-import/router.mjs +102 -8
  22. package/dist/unstable-core-do-not-import.js +2 -0
  23. package/dist/unstable-core-do-not-import.mjs +1 -1
  24. package/package.json +2 -2
  25. package/src/@trpc/server/index.ts +1 -0
  26. package/src/adapters/ws.ts +1 -1
  27. package/src/unstable-core-do-not-import/http/contentType.ts +48 -42
  28. package/src/unstable-core-do-not-import/http/resolveResponse.ts +2 -2
  29. package/src/unstable-core-do-not-import/procedureBuilder.ts +1 -0
  30. package/src/unstable-core-do-not-import/router.ts +156 -14
@@ -66,6 +66,8 @@ exports.isServerDefault = rootConfig.isServerDefault;
66
66
  exports.callProcedure = router.callProcedure;
67
67
  exports.createCallerFactory = router.createCallerFactory;
68
68
  exports.createRouterFactory = router.createRouterFactory;
69
+ exports.getProcedureAtPath = router.getProcedureAtPath;
70
+ exports.lazy = router.lazy;
69
71
  exports.mergeRouters = router.mergeRouters;
70
72
  exports.TRPC_ERROR_CODES_BY_KEY = codes.TRPC_ERROR_CODES_BY_KEY;
71
73
  exports.TRPC_ERROR_CODES_BY_NUMBER = codes.TRPC_ERROR_CODES_BY_NUMBER;
@@ -15,7 +15,7 @@ export { getParseFn } from './unstable-core-do-not-import/parser.mjs';
15
15
  export { procedureTypes } from './unstable-core-do-not-import/procedure.mjs';
16
16
  export { createBuilder } from './unstable-core-do-not-import/procedureBuilder.mjs';
17
17
  export { isServerDefault } from './unstable-core-do-not-import/rootConfig.mjs';
18
- export { callProcedure, createCallerFactory, createRouterFactory, mergeRouters } from './unstable-core-do-not-import/router.mjs';
18
+ export { callProcedure, createCallerFactory, createRouterFactory, getProcedureAtPath, lazy, mergeRouters } from './unstable-core-do-not-import/router.mjs';
19
19
  export { TRPC_ERROR_CODES_BY_KEY, TRPC_ERROR_CODES_BY_NUMBER } from './unstable-core-do-not-import/rpc/codes.mjs';
20
20
  export { parseTRPCMessage } from './unstable-core-do-not-import/rpc/parseTRPCMessage.mjs';
21
21
  export { isPromise, jsonlStreamConsumer, jsonlStreamProducer } from './unstable-core-do-not-import/stream/jsonl.mjs';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trpc/server",
3
- "version": "11.0.0-rc.747+64714681c",
3
+ "version": "11.0.0-rc.748+b19e7e9a6",
4
4
  "description": "The tRPC server library",
5
5
  "author": "KATT",
6
6
  "license": "MIT",
@@ -152,5 +152,5 @@
152
152
  "peerDependencies": {
153
153
  "typescript": ">=5.7.2"
154
154
  },
155
- "gitHead": "64714681ca127cd859a819d61ce8a990b928baf9"
155
+ "gitHead": "b19e7e9a640511f49de05bcd4436dc0a514f5e0f"
156
156
  }
@@ -44,6 +44,7 @@ export {
44
44
  tracked,
45
45
  type TrackedEnvelope,
46
46
  isTrackedEnvelope,
47
+ lazy as experimental_lazy,
47
48
  } from '../../unstable-core-do-not-import';
48
49
 
49
50
  export type {
@@ -219,7 +219,7 @@ export function getWSConnectionHandler<TRouter extends AnyRouter>(
219
219
 
220
220
  const abortController = new AbortController();
221
221
  const result = await callProcedure({
222
- procedures: router._def.procedures,
222
+ _def: router._def,
223
223
  path,
224
224
  getRawInput: async () => input,
225
225
  ctx,
@@ -1,6 +1,6 @@
1
1
  import { TRPCError } from '../error/TRPCError';
2
- import type { AnyProcedure, ProcedureType } from '../procedure';
3
- import type { AnyRouter } from '../router';
2
+ import type { ProcedureType } from '../procedure';
3
+ import { getProcedureAtPath, type AnyRouter } from '../router';
4
4
  import { isObject, unsetMarker } from '../utils';
5
5
  import { parseConnectionParamsFromString } from './parseConnectionParams';
6
6
  import type { TRPCAcceptHeader, TRPCRequestInfo } from './types';
@@ -16,7 +16,7 @@ type GetRequestInfoOptions = {
16
16
 
17
17
  type ContentTypeHandler = {
18
18
  isMatch: (opts: Request) => boolean;
19
- parse: (opts: GetRequestInfoOptions) => TRPCRequestInfo;
19
+ parse: (opts: GetRequestInfoOptions) => Promise<TRPCRequestInfo>;
20
20
  };
21
21
 
22
22
  /**
@@ -66,7 +66,7 @@ const jsonContentTypeHandler: ContentTypeHandler = {
66
66
  isMatch(req) {
67
67
  return !!req.headers.get('content-type')?.startsWith('application/json');
68
68
  },
69
- parse(opts) {
69
+ async parse(opts) {
70
70
  const { req } = opts;
71
71
  const isBatchCall = opts.searchParams.get('batch') === '1';
72
72
  const paths = isBatchCall ? opts.path.split(',') : [opts.path];
@@ -110,42 +110,45 @@ const jsonContentTypeHandler: ContentTypeHandler = {
110
110
  return acc;
111
111
  });
112
112
 
113
- const calls = paths.map((path, index): TRPCRequestInfo['calls'][number] => {
114
- const procedure: AnyProcedure | null =
115
- opts.router._def.procedures[path] ?? null;
116
- return {
117
- path,
118
- procedure,
119
- getRawInput: async () => {
120
- const inputs = await getInputs.read();
121
- let input = inputs[index];
113
+ const calls = await Promise.all(
114
+ paths.map(
115
+ async (path, index): Promise<TRPCRequestInfo['calls'][number]> => {
116
+ const procedure = await getProcedureAtPath(opts.router._def, path);
117
+ return {
118
+ path,
119
+ procedure,
120
+ getRawInput: async () => {
121
+ const inputs = await getInputs.read();
122
+ let input = inputs[index];
122
123
 
123
- if (procedure?._def.type === 'subscription') {
124
- const lastEventId =
125
- opts.headers.get('last-event-id') ??
126
- opts.searchParams.get('lastEventId') ??
127
- opts.searchParams.get('Last-Event-Id');
124
+ if (procedure?._def.type === 'subscription') {
125
+ const lastEventId =
126
+ opts.headers.get('last-event-id') ??
127
+ opts.searchParams.get('lastEventId') ??
128
+ opts.searchParams.get('Last-Event-Id');
128
129
 
129
- if (lastEventId) {
130
- if (isObject(input)) {
131
- input = {
132
- ...input,
133
- lastEventId: lastEventId,
134
- };
135
- } else {
136
- input ??= {
137
- lastEventId: lastEventId,
138
- };
130
+ if (lastEventId) {
131
+ if (isObject(input)) {
132
+ input = {
133
+ ...input,
134
+ lastEventId: lastEventId,
135
+ };
136
+ } else {
137
+ input ??= {
138
+ lastEventId: lastEventId,
139
+ };
140
+ }
141
+ }
139
142
  }
140
- }
141
- }
142
- return input;
143
- },
144
- result: () => {
145
- return getInputs.result()?.[index];
143
+ return input;
144
+ },
145
+ result: () => {
146
+ return getInputs.result()?.[index];
147
+ },
148
+ };
146
149
  },
147
- };
148
- });
150
+ ),
151
+ );
149
152
 
150
153
  const types = new Set(
151
154
  calls.map((call) => call.procedure?._def.type).filter(Boolean),
@@ -185,7 +188,7 @@ const formDataContentTypeHandler: ContentTypeHandler = {
185
188
  isMatch(req) {
186
189
  return !!req.headers.get('content-type')?.startsWith('multipart/form-data');
187
190
  },
188
- parse(opts) {
191
+ async parse(opts) {
189
192
  const { req } = opts;
190
193
  if (req.method !== 'POST') {
191
194
  throw new TRPCError({
@@ -198,6 +201,7 @@ const formDataContentTypeHandler: ContentTypeHandler = {
198
201
  const fd = await req.formData();
199
202
  return fd;
200
203
  });
204
+ const procedure = await getProcedureAtPath(opts.router._def, opts.path);
201
205
  return {
202
206
  accept: null,
203
207
  calls: [
@@ -205,7 +209,7 @@ const formDataContentTypeHandler: ContentTypeHandler = {
205
209
  path: opts.path,
206
210
  getRawInput: getInputs.read,
207
211
  result: getInputs.result,
208
- procedure: opts.router._def.procedures[opts.path] ?? null,
212
+ procedure,
209
213
  },
210
214
  ],
211
215
  isBatchCall: false,
@@ -223,7 +227,7 @@ const octetStreamContentTypeHandler: ContentTypeHandler = {
223
227
  .get('content-type')
224
228
  ?.startsWith('application/octet-stream');
225
229
  },
226
- parse(opts) {
230
+ async parse(opts) {
227
231
  const { req } = opts;
228
232
  if (req.method !== 'POST') {
229
233
  throw new TRPCError({
@@ -241,7 +245,7 @@ const octetStreamContentTypeHandler: ContentTypeHandler = {
241
245
  path: opts.path,
242
246
  getRawInput: getInputs.read,
243
247
  result: getInputs.result,
244
- procedure: opts.router._def.procedures[opts.path] ?? null,
248
+ procedure: await getProcedureAtPath(opts.router._def, opts.path),
245
249
  },
246
250
  ],
247
251
  isBatchCall: false,
@@ -279,7 +283,9 @@ function getContentTypeHandler(req: Request): ContentTypeHandler {
279
283
  });
280
284
  }
281
285
 
282
- export function getRequestInfo(opts: GetRequestInfoOptions): TRPCRequestInfo {
286
+ export async function getRequestInfo(
287
+ opts: GetRequestInfoOptions,
288
+ ): Promise<TRPCRequestInfo> {
283
289
  const handler = getContentTypeHandler(opts.req);
284
- return handler.parse(opts);
290
+ return await handler.parse(opts);
285
291
  }
@@ -223,11 +223,11 @@ export async function resolveResponse<TRouter extends AnyRouter>(
223
223
 
224
224
  type $Context = inferRouterContext<TRouter>;
225
225
 
226
- const infoTuple: ResultTuple<TRPCRequestInfo> = run(() => {
226
+ const infoTuple: ResultTuple<TRPCRequestInfo> = await run(async () => {
227
227
  try {
228
228
  return [
229
229
  undefined,
230
- getRequestInfo({
230
+ await getRequestInfo({
231
231
  req,
232
232
  path: decodeURIComponent(opts.path),
233
233
  router,
@@ -637,6 +637,7 @@ function createProcedureCaller(_def: AnyProcedureBuilderDef): AnyProcedure {
637
637
  }
638
638
 
639
639
  procedure._def = _def;
640
+ procedure.procedure = true;
640
641
 
641
642
  // FIXME typecast shouldn't be needed - fixittt
642
643
  return procedure as unknown as AnyProcedure;
@@ -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 { isFunction, mergeWithoutOverrides, omitPrototype } from './utils';
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
- procedureOrRouter: ValueOf<CreateRouterOptions>,
109
- ): procedureOrRouter is AnyRouter {
110
- return procedureOrRouter._def && 'router' in procedureOrRouter._def;
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]: AnyProcedure | AnyRouter | CreateRouterOptions;
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 CreateRouterOptions
153
- ? DecorateCreateRouterOptions<$Value>
154
- : never
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
- procedures: RouterRecord;
373
+ _def: AnyRouter['_def'];
238
374
  allowMethodOverride?: boolean;
239
375
  },
240
376
  ) {
241
377
  const { type, path } = opts;
242
- const proc = opts.procedures[path];
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.procedures[fullPath] as AnyProcedure;
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._def.type,
448
+ type: procedure?._def.type ?? 'unknown',
307
449
  });
308
450
  throw cause;
309
451
  }