Skip to content

Commit 0c7de81

Browse files
authored
Add support for headers (#558)
Adding headers to the query key and fetching operation. Fixes #555 --------- Signed-off-by: Paul Sachs <[email protected]>
1 parent 52a6fd0 commit 0c7de81

12 files changed

+323
-37
lines changed

packages/connect-query-core/src/call-unary-method.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,14 @@ export async function callUnaryMethod<
3434
input: MessageInitShape<I> | undefined,
3535
options?: {
3636
signal?: AbortSignal;
37+
headers?: HeadersInit;
3738
},
3839
): Promise<MessageShape<O>> {
3940
const result = await transport.unary(
4041
schema,
4142
options?.signal,
4243
undefined,
43-
undefined,
44+
options?.headers,
4445
input ?? create(schema.input),
4546
undefined,
4647
);

packages/connect-query-core/src/connect-query-key.test.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,77 @@ describe("createConnectQueryKey", () => {
178178
});
179179
});
180180

181+
describe("headers", () => {
182+
it("allows headers to be passed as an object", () => {
183+
const key = createConnectQueryKey({
184+
schema: ElizaService.method.say,
185+
input: create(SayRequestSchema, { sentence: "hi" }),
186+
cardinality: "finite",
187+
headers: {
188+
"x-custom-header": "custom-value",
189+
},
190+
});
191+
expect(key[1].headers).toEqual({
192+
"x-custom-header": "custom-value",
193+
});
194+
});
195+
it("allows headers to be passed as a tuple", () => {
196+
const key = createConnectQueryKey({
197+
schema: ElizaService.method.say,
198+
input: create(SayRequestSchema, { sentence: "hi" }),
199+
cardinality: "finite",
200+
headers: [["x-custom-header", "custom-value"]],
201+
});
202+
expect(key[1].headers).toEqual({
203+
"x-custom-header": "custom-value",
204+
});
205+
});
206+
it("allows headers to be passed as a HeadersInit", () => {
207+
const key = createConnectQueryKey({
208+
schema: ElizaService.method.say,
209+
input: create(SayRequestSchema, { sentence: "hi" }),
210+
cardinality: "finite",
211+
headers: new Headers({
212+
"x-custom-header": "custom-value",
213+
}),
214+
});
215+
expect(key[1].headers).toEqual({
216+
"x-custom-header": "custom-value",
217+
});
218+
});
219+
it("normalizes header values", () => {
220+
const keyA = createConnectQueryKey({
221+
schema: ElizaService.method.say,
222+
input: create(SayRequestSchema, { sentence: "hi" }),
223+
cardinality: "finite",
224+
headers: {
225+
foo: "a",
226+
Foo: "b",
227+
},
228+
});
229+
const keyB = createConnectQueryKey({
230+
schema: ElizaService.method.say,
231+
input: create(SayRequestSchema, { sentence: "hi" }),
232+
cardinality: "finite",
233+
headers: {
234+
foo: "a, b",
235+
},
236+
});
237+
const keyC = createConnectQueryKey({
238+
schema: ElizaService.method.say,
239+
input: create(SayRequestSchema, { sentence: "hi" }),
240+
cardinality: "finite",
241+
headers: [
242+
["foo", "a"],
243+
["foo", "b"],
244+
],
245+
});
246+
247+
expect(keyA[1].headers).toEqual(keyB[1].headers);
248+
expect(keyA[1].headers).toEqual(keyC[1].headers);
249+
});
250+
});
251+
181252
describe("infinite queries", () => {
182253
it("contains type hints to indicate the output type", () => {
183254
const sampleQueryClient = new QueryClient();

packages/connect-query-core/src/connect-query-key.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ type SharedConnectQueryOptions = {
4444
* or "skipped".
4545
*/
4646
input?: Record<string, unknown> | "skipped";
47+
/**
48+
* Headers to be sent with the request.
49+
* Note that invalid HTTP header names will raise a TypeError, and that the Set-Cookie header is not supported.
50+
*/
51+
headers?: Record<string, string>;
4752
};
4853

4954
type InfiniteConnectQueryKey<OutputMessage extends DescMessage = DescMessage> =
@@ -125,6 +130,11 @@ type KeyParamsForMethod<Desc extends DescMethod> = {
125130
* If omit the field with this name from the key for infinite queries.
126131
*/
127132
pageParamKey?: keyof MessageInitShape<Desc["input"]>;
133+
/**
134+
* Set `headers` in the key.
135+
* Note that invalid HTTP header names will raise a TypeError, and that the Set-Cookie header is not supported.
136+
*/
137+
headers?: HeadersInit;
128138
};
129139

130140
type KeyParamsForService<Desc extends DescService> = {
@@ -220,6 +230,7 @@ export function createConnectQueryKey<
220230
transport?: string;
221231
cardinality?: "finite" | "infinite";
222232
input?: "skipped" | Record<string, unknown>;
233+
headers?: Record<string, string>;
223234
} =
224235
params.schema.kind == "rpc"
225236
? {
@@ -246,5 +257,25 @@ export function createConnectQueryKey<
246257
);
247258
}
248259
}
260+
if (
261+
params.schema.kind === "rpc" &&
262+
"headers" in params &&
263+
params.headers !== undefined
264+
) {
265+
props.headers = createHeadersKey(params.headers);
266+
}
249267
return ["connect-query", props] as ConnectQueryKey<O>;
250268
}
269+
270+
/**
271+
* Creates a record of headers from a HeadersInit object.
272+
*
273+
*/
274+
function createHeadersKey(headers: HeadersInit): Record<string, string> {
275+
const result: Record<string, string> = {};
276+
277+
for (const [key, value] of new Headers(headers)) {
278+
result[key] = value;
279+
}
280+
return result;
281+
}

packages/connect-query-core/src/create-infinite-query-options.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export interface ConnectInfiniteQueryOptions<
8383
MessageInitShape<I>[ParamKey],
8484
MessageShape<O>
8585
>;
86+
headers?: HeadersInit;
8687
}
8788

8889
// eslint-disable-next-line @typescript-eslint/max-params -- we have 4 required arguments
@@ -113,6 +114,7 @@ function createUnaryInfiniteQueryFn<
113114
};
114115
return callUnaryMethod(transport, schema, inputCombinedWithPageParam, {
115116
signal: context.signal,
117+
headers: context.queryKey[1].headers,
116118
});
117119
};
118120
}
@@ -131,6 +133,7 @@ export function createInfiniteQueryOptions<
131133
transport,
132134
getNextPageParam,
133135
pageParamKey,
136+
headers,
134137
}: ConnectInfiniteQueryOptions<I, O, ParamKey> & { transport: Transport },
135138
): InfiniteQueryOptions<I, O, ParamKey>;
136139
export function createInfiniteQueryOptions<
@@ -144,6 +147,7 @@ export function createInfiniteQueryOptions<
144147
transport,
145148
getNextPageParam,
146149
pageParamKey,
150+
headers,
147151
}: ConnectInfiniteQueryOptions<I, O, ParamKey> & { transport: Transport },
148152
): InfiniteQueryOptionsWithSkipToken<I, O, ParamKey>;
149153
export function createInfiniteQueryOptions<
@@ -159,6 +163,7 @@ export function createInfiniteQueryOptions<
159163
transport,
160164
getNextPageParam,
161165
pageParamKey,
166+
headers,
162167
}: ConnectInfiniteQueryOptions<I, O, ParamKey> & { transport: Transport },
163168
):
164169
| InfiniteQueryOptions<I, O, ParamKey>
@@ -176,6 +181,7 @@ export function createInfiniteQueryOptions<
176181
transport,
177182
getNextPageParam,
178183
pageParamKey,
184+
headers,
179185
}: ConnectInfiniteQueryOptions<I, O, ParamKey> & { transport: Transport },
180186
):
181187
| InfiniteQueryOptions<I, O, ParamKey>
@@ -185,6 +191,7 @@ export function createInfiniteQueryOptions<
185191
schema,
186192
transport,
187193
input,
194+
headers,
188195
});
189196
const structuralSharing = createStructuralSharing(schema.output);
190197
const queryFn =

packages/connect-query-core/src/create-query-options.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,18 @@ describe("createQueryOptions", () => {
4848
input: { sentence: "hi" },
4949
transport: mockedElizaTransport,
5050
cardinality: "finite",
51+
headers: {
52+
"x-custom-header": "custom-value",
53+
},
5154
});
5255
const opt = createQueryOptions(
5356
sayMethodDescriptor,
5457
{ sentence: "hi" },
5558
{
5659
transport: mockedElizaTransport,
60+
headers: {
61+
"x-custom-header": "custom-value",
62+
},
5763
},
5864
);
5965
expect(opt.queryKey).toStrictEqual(want);

packages/connect-query-core/src/create-query-options.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ function createUnaryQueryFn<I extends DescMessage, O extends DescMessage>(
5050
return async (context) => {
5151
return callUnaryMethod(transport, schema, input, {
5252
signal: context.signal,
53+
headers: context.queryKey[1].headers,
5354
});
5455
};
5556
}
@@ -65,8 +66,10 @@ export function createQueryOptions<
6566
input: MessageInitShape<I> | undefined,
6667
{
6768
transport,
69+
headers,
6870
}: {
6971
transport: Transport;
72+
headers?: HeadersInit;
7073
},
7174
): QueryOptions<O>;
7275
export function createQueryOptions<
@@ -77,8 +80,10 @@ export function createQueryOptions<
7780
input: SkipToken,
7881
{
7982
transport,
83+
headers,
8084
}: {
8185
transport: Transport;
86+
headers?: HeadersInit;
8287
},
8388
): QueryOptionsWithSkipToken<O>;
8489
export function createQueryOptions<
@@ -89,8 +94,10 @@ export function createQueryOptions<
8994
input: SkipToken | MessageInitShape<I> | undefined,
9095
{
9196
transport,
97+
headers,
9298
}: {
9399
transport: Transport;
100+
headers?: HeadersInit;
94101
},
95102
): QueryOptions<O> | QueryOptionsWithSkipToken<O>;
96103
export function createQueryOptions<
@@ -101,15 +108,18 @@ export function createQueryOptions<
101108
input: SkipToken | MessageInitShape<I> | undefined,
102109
{
103110
transport,
111+
headers,
104112
}: {
105113
transport: Transport;
114+
headers?: HeadersInit;
106115
},
107116
): QueryOptions<O> | QueryOptionsWithSkipToken<O> {
108117
const queryKey = createConnectQueryKey({
109118
schema,
110119
input: input ?? create(schema.input),
111120
transport,
112121
cardinality: "finite",
122+
headers,
113123
});
114124
const structuralSharing = createStructuralSharing(schema.output);
115125
const queryFn =

packages/connect-query/src/call-unary-method.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,42 @@ describe("callUnaryMethod", () => {
7272
});
7373
expect(result.current.query1.data?.sentence).toEqual("Response 1");
7474
});
75+
it("can pass headers through", async () => {
76+
let resolve: () => void;
77+
const promise = new Promise<void>((res) => {
78+
resolve = res;
79+
});
80+
const transport = mockEliza(
81+
{
82+
sentence: "Response 1",
83+
},
84+
false,
85+
{
86+
router: {
87+
interceptors: [
88+
(next) => (req) => {
89+
expect(req.header.get("x-custom-header")).toEqual("custom-value");
90+
resolve();
91+
return next(req);
92+
},
93+
],
94+
},
95+
},
96+
);
97+
const input: SayRequest = create(SayRequestSchema, {
98+
sentence: "query 1",
99+
});
100+
const res = await callUnaryMethod(
101+
transport,
102+
ElizaService.method.say,
103+
input,
104+
{
105+
headers: {
106+
"x-custom-header": "custom-value",
107+
},
108+
},
109+
);
110+
await promise;
111+
expect(res.sentence).toEqual("Response 1");
112+
});
75113
});

packages/connect-query/src/use-infinite-query.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,4 +396,53 @@ describe("useSuspenseInfiniteQuery", () => {
396396
wrapper({}, mockedPaginatedTransport),
397397
);
398398
});
399+
400+
it("can pass headers through", async () => {
401+
let resolve: () => void;
402+
const promise = new Promise<void>((res) => {
403+
resolve = res;
404+
});
405+
const transport = mockPaginatedTransport(
406+
{
407+
items: ["Intercepted!"],
408+
page: 0n,
409+
},
410+
false,
411+
{
412+
router: {
413+
interceptors: [
414+
(next) => (req) => {
415+
expect(req.header.get("x-custom-header")).toEqual("custom-value");
416+
resolve();
417+
return next(req);
418+
},
419+
],
420+
},
421+
},
422+
);
423+
const { result } = renderHook(() => {
424+
return useSuspenseInfiniteQuery(
425+
methodDescriptor,
426+
{
427+
page: 0n,
428+
},
429+
{
430+
getNextPageParam: (lastPage) => lastPage.page + 1n,
431+
pageParamKey: "page",
432+
transport,
433+
headers: {
434+
"x-custom-header": "custom-value",
435+
},
436+
},
437+
);
438+
}, wrapper({}));
439+
440+
await waitFor(() => {
441+
expect(result.current.isSuccess).toBeTruthy();
442+
});
443+
444+
await promise;
445+
446+
expect(result.current.data.pages[0].items).toEqual(["Intercepted!"]);
447+
});
399448
});

packages/connect-query/src/use-infinite-query.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ export function useSuspenseInfiniteQuery<
128128
transport,
129129
pageParamKey,
130130
getNextPageParam,
131+
headers,
131132
...queryOptions
132133
}: UseSuspenseInfiniteQueryOptions<I, O, ParamKey>,
133134
): UseSuspenseInfiniteQueryResult<InfiniteData<MessageShape<O>>, ConnectError> {
@@ -136,6 +137,7 @@ export function useSuspenseInfiniteQuery<
136137
transport: transport ?? transportFromCtx,
137138
getNextPageParam,
138139
pageParamKey,
140+
headers,
139141
});
140142
return tsUseSuspenseInfiniteQuery({
141143
...baseOptions,

0 commit comments

Comments
 (0)