Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(server): fix regression introduced by #5017 #5039

Merged
merged 8 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 6 additions & 16 deletions packages/server/src/core/internals/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ import { ProcedureParams } from '../procedure';
/**
* @internal
* Overwrite properties in `TType` with properties in `TWith`
* Only overwrites properties when both types are objects
* Otherwise it will overwrite the entire TType with TWith,
* unless TWith is never.
* Only overwrites properties when the type to be overwritten
* is an object. Otherwise it will just use the type from `TWith`.
*/
export type Overwrite<TType, TWith> = TType extends object
? TWith extends object
? // Both TType and TWith are objects: overwrite key-by-key
{
export type Overwrite<TType, TWith> = TWith extends any
? TType extends object
? {
[K in // Exclude index signature from keys
| keyof WithoutIndexSignature<TType>
| keyof WithoutIndexSignature<TWith>]: K extends keyof TWith
Expand All @@ -24,15 +22,7 @@ export type Overwrite<TType, TWith> = TType extends object
: number extends keyof TWith
? { [key: number]: TWith[number] }
: object)
: TWith extends any
? // TWith is not an object but some non-never type, so fully overwrite TType
TWith
: never
: TType extends any
? TWith extends any
? // Same as above: just overwrite TType with TWith
TWith
: TType
: TWith
: never;

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export type DeepPartial<TObject> = TObject extends object
* type information about the keys and only the index signature will remain.
* @internal
*/
export type WithoutIndexSignature<TObj extends object> = {
export type WithoutIndexSignature<TObj> = {
[K in keyof TObj as string extends K
? never
: number extends K
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export function hardcodedExample() {
}

const t = initTRPC.create();
const symbol = Symbol('symbol');
const appRouter = t.router({
inputWithIndexSignature: t.procedure
.input(hardcodedExample())
Expand All @@ -21,6 +22,19 @@ const appRouter = t.router({
.query(({ input }) => {
return input;
}),

middlewareWithSymbolKey: t.procedure
.input(z.object({ name: z.string() }))
.use((opts) =>
opts.next({
ctx: {
[symbol]: true,
},
}),
)
.query(({ input }) => {
return input;
}),
});
type AppRouter = typeof appRouter;

Expand Down Expand Up @@ -60,4 +74,14 @@ describe('inferRouterInputs/inferRouterOutputs', () => {
expectTypeOf<Input>().toEqualTypeOf<{ name: string }>();
expectTypeOf<Output>().toEqualTypeOf<{ name: string }>();
});

test('middleware with symbol key', async () => {
type Input = AppRouterInputs['middlewareWithSymbolKey'];
type Output = AppRouterOutputs['middlewareWithSymbolKey'];
expectTypeOf<Input>().toEqualTypeOf<{ name: string }>();
expectTypeOf<Output>().toEqualTypeOf<{
name: string;
[symbol]: true;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a hard time with this... why are we expecting inferred outputs to contain symbols (which is not JSON serializable)? Plus, the input (which the query returns) doesn't contain the symbol, ctx does. And if you look at ctx inside the query, it's inferred correctly

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I messed up the test, it actually works as intended, sorry :D

}>();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { inferRouterOutputs, initTRPC } from '@trpc/server';
import { z } from 'zod';

/**
* See @sentry/node trpc middleware:
* https://github.com/getsentry/sentry-javascript/blob/6d424571e3cd5e99991b711f4e23d773e321e294/packages/node/src/handlers.ts#L328
*/
interface TrpcMiddlewareArguments<T> {
path: string;
type: string;
next: () => T;
rawInput: unknown;
}

/**
* See @sentry/node trpc middleware:
* https://github.com/getsentry/sentry-javascript/blob/6d424571e3cd5e99991b711f4e23d773e321e294/packages/node/src/handlers.ts#L328
*/
function sentryTrpcMiddleware(_options: any) {
return function <T>({
path,
type,
next,
rawInput,
}: TrpcMiddlewareArguments<T>): T {
path;
type;
next;
rawInput;

// This function is effectively what @sentry/node does to provide its trpc middleware.
return null as any as T;
};
}

type Context = {
some: 'prop';
};

describe('context inference w/ middlewares', () => {
test('a base procedure using a generically constructed middleware should be extensible using another middleware', async () => {
const t = initTRPC.context<Context>().create();

const baseMiddleware = t.middleware(sentryTrpcMiddleware({ foo: 'bar' }));

const someMiddleware = t.middleware(async (opts) => {
return opts.next({
ctx: {
object: {
a: 'b',
},
},
});
});

const baseProcedure = t.procedure.use(baseMiddleware);
const bazQuxProcedure = baseProcedure.use(someMiddleware);

const appRouter = t.router({
foo: bazQuxProcedure.input(z.string()).query(({ ctx }) => ctx),
});
type Output = inferRouterOutputs<typeof appRouter>['foo'];

expectTypeOf<Output>().toEqualTypeOf<{
object: { a: string };
some: 'prop';
}>();
});
});