Skip to content

Commit 13cbf42

Browse files
committed
Stronger typing for data connect params
1 parent 5dbf89b commit 13cbf42

File tree

3 files changed

+127
-28
lines changed

3 files changed

+127
-28
lines changed

spec/common/metaprogramming.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@
2323
/* eslint-disable @typescript-eslint/no-unused-vars,@typescript-eslint/no-empty-function */
2424
export function expectType<Type>(value: Type) {}
2525
export function expectNever<Type extends never>() {}
26+
export function expectExtends<Type, Test extends Type>() {}

spec/v2/providers/dataconnect.spec.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { expect } from "chai";
2424
import * as dataconnect from "../../../src/v2/providers/dataconnect";
2525
import { CloudEvent } from "../../../src/v2";
2626
import { onInit } from "../../../src/v2/core";
27+
import { expectExtends } from "../../common/metaprogramming";
2728

2829
const expectedEndpointBase = {
2930
platform: "gcfv2",
@@ -51,6 +52,81 @@ function makeExpectedEndpoint(eventType: string, eventFilters, eventFilterPathPa
5152
}
5253

5354
describe("dataconnect", () => {
55+
describe("params", () => {
56+
it("extracts {segment} captures", () => {
57+
expectExtends<
58+
Record<"myConnector", string>,
59+
dataconnect.DataConnectParams<"/{myConnector}">
60+
>();
61+
});
62+
63+
it("extracts nothing from strings without params", () => {
64+
expectExtends<Record<never, string>, dataconnect.DataConnectParams<"foo/bar">>();
65+
expectExtends<Record<never, string>, dataconnect.DataConnectParams<"/foo/bar">>();
66+
});
67+
68+
it("extracts {segment} captures from options", () => {
69+
expectExtends<
70+
Record<"myService", string>,
71+
dataconnect.DataConnectParams<{
72+
service: "{myService}";
73+
connector: "connector";
74+
operation: "operation";
75+
}>
76+
>();
77+
78+
expectExtends<
79+
{ myService: string; [key: string]: string },
80+
dataconnect.DataConnectParams<
81+
dataconnect.OperationOptions<"{myService}", "connector", "operation">
82+
>
83+
>();
84+
});
85+
86+
it("extracts {segment=*} captures from options", () => {
87+
expectExtends<
88+
Record<"myConnector", string>,
89+
dataconnect.DataConnectParams<
90+
dataconnect.OperationOptions<string, "{myConnector=*}", string>
91+
>
92+
>();
93+
});
94+
95+
it("extracts {segment=**} captures from options", () => {
96+
expectExtends<
97+
Record<"myOperation", string>,
98+
dataconnect.DataConnectParams<
99+
dataconnect.OperationOptions<string, "unused", "{myOperation=**}">
100+
>
101+
>();
102+
});
103+
104+
it("extracts multiple captures from options", () => {
105+
expectExtends<
106+
Record<"myService" | "myConnector" | "myOperation", string>,
107+
dataconnect.DataConnectParams<
108+
dataconnect.OperationOptions<"{myService}", "{myConnector=*}", "{myOperation=**}">
109+
>
110+
>();
111+
});
112+
113+
it("extracts nothing from options without params", () => {
114+
expectExtends<
115+
Record<never, string>,
116+
dataconnect.DataConnectParams<{
117+
service: "service";
118+
connector: "connector";
119+
operation: "operation";
120+
}>
121+
>();
122+
123+
expectExtends<
124+
Record<never, string>,
125+
dataconnect.DataConnectParams<dataconnect.OperationOptions<string, string, string>>
126+
>();
127+
});
128+
});
129+
54130
describe("onMutationExecuted", () => {
55131
it("should create a func", () => {
56132
const expectedEndpoint = makeExpectedEndpoint(
@@ -424,6 +500,21 @@ describe("dataconnect", () => {
424500
expect(func.__endpoint).to.deep.eq(expectedEndpoint);
425501
});
426502

503+
it("should create a func in the absence of param opts", () => {
504+
const expectedEndpoint = makeExpectedEndpoint(
505+
dataconnect.mutationExecutedEventType,
506+
{
507+
connector: undefined,
508+
operation: undefined,
509+
service: undefined,
510+
},
511+
{}
512+
);
513+
514+
const func = dataconnect.onMutationExecuted({}, () => true);
515+
expect(func.__endpoint).to.deep.eq(expectedEndpoint);
516+
});
517+
427518
it("calls init function", async () => {
428519
const event: CloudEvent<string> = {
429520
specversion: "1.0",

src/v2/providers/dataconnect.ts

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
// SOFTWARE.
2222

2323
import { CloudEvent, CloudFunction } from "../core";
24-
import { ParamsOf } from "../../common/params";
24+
import { ParamsOf, VarName } from "../../common/params";
2525
import { EventHandlerOptions, getGlobalOptions, optionsToEndpoint } from "../options";
2626
import { normalizePath } from "../../common/utilities/path";
2727
import { wrapTraceContext } from "../trace";
@@ -88,16 +88,23 @@ export interface RawDataConnectEvent<T> extends CloudEvent<T> {
8888
export type AuthType = "app_user" | "admin" | "unknown";
8989

9090
/** OperationOptions extend EventHandlerOptions with a provided service, connector, and operation. */
91-
export interface OperationOptions extends EventHandlerOptions {
92-
/** Firebase Data Connect service ID */
93-
service: string;
94-
/** Firebase Data Connect connector ID */
95-
connector: string;
96-
/** Name of the operation */
97-
operation: string;
91+
export interface OperationOptions<Service extends string = string, Connector extends string = string, Operation extends string = string> extends EventHandlerOptions{
92+
/** Firebase Data Connect service ID */
93+
service?: Service;
94+
/** Firebase Data Connect connector ID */
95+
connector?: Connector;
96+
/** Name of the operation */
97+
operation?: Operation;
9898
}
9999

100-
export interface DataConnectEvent<T, Params = Record<string, string>> extends CloudEvent<T> {
100+
export type DataConnectParams<PathPatternOrOptions extends string | OperationOptions> =
101+
PathPatternOrOptions extends string
102+
? ParamsOf<PathPatternOrOptions>
103+
: PathPatternOrOptions extends OperationOptions<infer Service extends string, infer Connector extends string, infer Operation extends string>
104+
? Record<VarName<Service> | VarName<Connector> | VarName<Operation>, string>
105+
: never;
106+
107+
export interface DataConnectEvent<T, Params extends Record<never, string>> extends CloudEvent<T> {
101108
/** The location of the Firebase Data Connect instance */
102109
location: string;
103110
/** The project identifier */
@@ -126,9 +133,9 @@ export function onMutationExecuted<
126133
>(
127134
mutation: Mutation,
128135
handler: (
129-
event: DataConnectEvent<MutationEventData<Variables, ResponseData>, ParamsOf<Mutation>>
136+
event: DataConnectEvent<MutationEventData<Variables, ResponseData>, DataConnectParams<Mutation>>
130137
) => unknown | Promise<unknown>
131-
): CloudFunction<DataConnectEvent<MutationEventData<Variables, ResponseData>, ParamsOf<Mutation>>>;
138+
): CloudFunction<DataConnectEvent<MutationEventData<Variables, ResponseData>, DataConnectParams<Mutation>>>;
132139

133140
/**
134141
* Event handler that triggers when a mutation is executed in Firebase Data Connect.
@@ -137,15 +144,15 @@ export function onMutationExecuted<
137144
* @param handler - Event handler which is run every time a mutation is executed.
138145
*/
139146
export function onMutationExecuted<
140-
Mutation extends string,
147+
Options extends OperationOptions,
141148
Variables = unknown,
142149
ResponseData = unknown
143150
>(
144-
opts: OperationOptions,
151+
opts: Options,
145152
handler: (
146-
event: DataConnectEvent<MutationEventData<Variables, ResponseData>, ParamsOf<Mutation>>
153+
event: DataConnectEvent<MutationEventData<Variables, ResponseData>, DataConnectParams<Options>>
147154
) => unknown | Promise<unknown>
148-
): CloudFunction<DataConnectEvent<MutationEventData<Variables, ResponseData>, ParamsOf<Mutation>>>;
155+
): CloudFunction<DataConnectEvent<MutationEventData<Variables, ResponseData>, DataConnectParams<Options>>>;
149156

150157
/**
151158
* Event handler that triggers when a mutation is executed in Firebase Data Connect.
@@ -154,16 +161,16 @@ export function onMutationExecuted<
154161
* @param handler - Event handler which is run every time a mutation is executed.
155162
*/
156163
export function onMutationExecuted<
157-
Mutation extends string,
164+
PathPatternOrOptions extends string | OperationOptions<string, string, string>,
158165
Variables = unknown,
159166
ResponseData = unknown
160167
>(
161-
mutationOrOpts: Mutation | OperationOptions,
168+
mutationOrOpts: PathPatternOrOptions,
162169
handler: (
163-
event: DataConnectEvent<MutationEventData<Variables, ResponseData>, ParamsOf<Mutation>>
170+
event: DataConnectEvent<MutationEventData<Variables, ResponseData>, DataConnectParams<PathPatternOrOptions>>
164171
) => unknown | Promise<unknown>
165-
): CloudFunction<DataConnectEvent<MutationEventData<Variables, ResponseData>, ParamsOf<Mutation>>> {
166-
return onOperation<Variables, ResponseData>(mutationExecutedEventType, mutationOrOpts, handler);
172+
): CloudFunction<DataConnectEvent<MutationEventData<Variables, ResponseData>, DataConnectParams<PathPatternOrOptions>>> {
173+
return onOperation<Variables, ResponseData, PathPatternOrOptions>(mutationExecutedEventType, mutationOrOpts, handler);
167174
}
168175

169176
function getOpts(mutationOrOpts: string | OperationOptions) {
@@ -257,11 +264,11 @@ function makeParams<V, R>(
257264
};
258265
}
259266

260-
function onOperation<V, R>(
267+
function onOperation<Variables, ResponseData, PathPatternOrOptions>(
261268
eventType: string,
262-
mutationOrOpts: string | OperationOptions,
263-
handler: (event: DataConnectEvent<MutationEventData<V, R>>) => any | Promise<any>
264-
): CloudFunction<DataConnectEvent<MutationEventData<V, R>>> {
269+
mutationOrOpts: PathPatternOrOptions,
270+
handler: (event: DataConnectEvent<MutationEventData<Variables, ResponseData>, DataConnectParams<PathPatternOrOptions>>) => any | Promise<any>
271+
): CloudFunction<DataConnectEvent<MutationEventData<Variables, ResponseData>, DataConnectParams<PathPatternOrOptions>>> {
265272
const { service, connector, operation, opts } = getOpts(mutationOrOpts);
266273

267274
const servicePattern = new PathPattern(service);
@@ -270,14 +277,14 @@ function onOperation<V, R>(
270277

271278
// wrap the handler
272279
const func = (raw: CloudEvent<unknown>) => {
273-
const event = raw as RawDataConnectEvent<MutationEventData<V, R>>;
274-
const params = makeParams<V, R>(event, servicePattern, connectorPattern, operationPattern);
280+
const event = raw as RawDataConnectEvent<MutationEventData<Variables, ResponseData>>;
281+
const params = makeParams<Variables, ResponseData>(event, servicePattern, connectorPattern, operationPattern);
275282

276-
const dataConnectEvent: DataConnectEvent<MutationEventData<V, R>> = {
283+
const dataConnectEvent: DataConnectEvent<MutationEventData<Variables, ResponseData>, DataConnectParams<PathPatternOrOptions>> = {
277284
...event,
278285
authType: event.authtype,
279286
authId: event.authid,
280-
params,
287+
params: params as DataConnectParams<PathPatternOrOptions>,
281288
};
282289
delete (dataConnectEvent as any).authtype;
283290
delete (dataConnectEvent as any).authid;

0 commit comments

Comments
 (0)