diff --git a/package.json b/package.json index a9ec85b6f..b9546c955 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "./remoteConfig": "./lib/v2/providers/remoteConfig.js", "./testLab": "./lib/v2/providers/testLab.js", "./firestore": "./lib/v2/providers/firestore.js", + "./dataconnect": "./lib/v2/providers/dataconnect.js", "./v2": "./lib/v2/index.js", "./v2/core": "./lib/v2/core.js", "./v2/options": "./lib/v2/options.js", @@ -79,7 +80,8 @@ "./v2/scheduler": "./lib/v2/providers/scheduler.js", "./v2/remoteConfig": "./lib/v2/providers/remoteConfig.js", "./v2/testLab": "./lib/v2/providers/testLab.js", - "./v2/firestore": "./lib/v2/providers/firestore.js" + "./v2/firestore": "./lib/v2/providers/firestore.js", + "./v2/dataconnect": "./lib/v2/providers/dataconnect.js" }, "typesVersions": { "*": { @@ -179,6 +181,9 @@ "firestore": [ "./lib/v2/providers/firestore" ], + "dataconnect": [ + "./lib/v2/providers/dataconnect" + ], "v2": [ "lib/v2" ], @@ -238,6 +243,9 @@ ], "v2/firestore": [ "lib/v2/providers/firestore" + ], + "v2/dataconnect": [ + "lib/v2/providers/dataconnect" ] } }, diff --git a/spec/common/params.spec.ts b/spec/common/params.spec.ts index 595a5758f..9887c743e 100644 --- a/spec/common/params.spec.ts +++ b/spec/common/params.spec.ts @@ -20,7 +20,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE ignoreUnusedWarning OR OTHER DEALINGS IN THE // SOFTWARE. -import { Extract, ParamsOf, Split } from "../../src/common/params"; +import { VarName, ParamsOf, Split } from "../../src/common/params"; import { expectNever, expectType } from "./metaprogramming"; describe("Params namespace", () => { @@ -56,21 +56,21 @@ describe("Params namespace", () => { }); }); - describe("Extract", () => { + describe("VarName", () => { it("extracts nothing from strings without params", () => { - expectNever>(); + expectNever>(); }); it("extracts {segment} captures", () => { - expectType>("uid"); + expectType>("uid"); }); it("extracts {segment=*} captures", () => { - expectType>("uid"); + expectType>("uid"); }); it("extracts {segment=**} captures", () => { - expectType>("uid"); + expectType>("uid"); }); }); diff --git a/spec/v2/providers/dataconnect.spec.ts b/spec/v2/providers/dataconnect.spec.ts new file mode 100644 index 000000000..9f99ce700 --- /dev/null +++ b/spec/v2/providers/dataconnect.spec.ts @@ -0,0 +1,447 @@ +// The MIT License (MIT) +// +// Copyright (c) 2025 Firebase +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import { expect } from "chai"; +import * as dataconnect from "../../../src/v2/providers/dataconnect"; +import { CloudEvent } from "../../../src/v2"; +import { onInit } from "../../../src/v2/core"; + +const expectedEndpointBase = { + platform: "gcfv2", + availableMemoryMb: {}, + concurrency: {}, + ingressSettings: {}, + maxInstances: {}, + minInstances: {}, + serviceAccountEmail: {}, + timeoutSeconds: {}, + vpc: {}, + labels: {}, +}; + +function makeExpectedEndpoint(eventType: string, eventFilters, eventFilterPathPatterns) { + return { + ...expectedEndpointBase, + eventTrigger: { + eventType, + eventFilters, + eventFilterPathPatterns, + retry: false, + }, + }; +} + +describe("dataconnect", () => { + describe("onMutationExecuted", () => { + it("should create a func", () => { + const expectedEndpoint = makeExpectedEndpoint( + dataconnect.mutationExecutedEventType, + { + service: "my-service", + connector: "my-connector", + operation: "my-operation", + }, + {} + ); + + const func = dataconnect.onMutationExecuted( + "services/my-service/connectors/my-connector/operations/my-operation", + () => true + ); + expect(func.__endpoint).to.deep.eq(expectedEndpoint); + }); + + it("should create a func using param opts", () => { + const expectedEndpoint = makeExpectedEndpoint( + dataconnect.mutationExecutedEventType, + { + service: "my-service", + connector: "my-connector", + operation: "my-operation", + }, + {} + ); + + const func = dataconnect.onMutationExecuted( + { + service: "my-service", + connector: "my-connector", + operation: "my-operation", + }, + () => true + ); + expect(func.__endpoint).to.deep.eq(expectedEndpoint); + }); + + it("should create a func with a service path pattern", () => { + const expectedEndpoint = makeExpectedEndpoint( + dataconnect.mutationExecutedEventType, + { + connector: "my-connector", + operation: "my-operation", + }, + { + service: "{service}", + } + ); + + const func = dataconnect.onMutationExecuted( + "services/{service}/connectors/my-connector/operations/my-operation", + () => true + ); + expect(func.__endpoint).to.deep.eq(expectedEndpoint); + }); + + it("should create a func using param opts with a service path pattern", () => { + const expectedEndpoint = makeExpectedEndpoint( + dataconnect.mutationExecutedEventType, + { + connector: "my-connector", + operation: "my-operation", + }, + { + service: "{service}", + } + ); + + const func = dataconnect.onMutationExecuted( + { + service: "{service}", + connector: "my-connector", + operation: "my-operation", + }, + () => true + ); + expect(func.__endpoint).to.deep.eq(expectedEndpoint); + }); + + it("should create a func with a connector path pattern", () => { + const expectedEndpoint = makeExpectedEndpoint( + dataconnect.mutationExecutedEventType, + { + service: "my-service", + operation: "my-operation", + }, + { + connector: "{connector}", + } + ); + + const func = dataconnect.onMutationExecuted( + "services/my-service/connectors/{connector}/operations/my-operation", + () => true + ); + expect(func.__endpoint).to.deep.eq(expectedEndpoint); + }); + + it("should create a func using param opts with a connector path pattern", () => { + const expectedEndpoint = makeExpectedEndpoint( + dataconnect.mutationExecutedEventType, + { + service: "my-service", + operation: "my-operation", + }, + { + connector: "{connector}", + } + ); + + const func = dataconnect.onMutationExecuted( + { + service: "my-service", + connector: "{connector}", + operation: "my-operation", + }, + () => true + ); + expect(func.__endpoint).to.deep.eq(expectedEndpoint); + }); + + it("should create a func with an operation path pattern", () => { + const expectedEndpoint = makeExpectedEndpoint( + dataconnect.mutationExecutedEventType, + { + service: "my-service", + connector: "my-connector", + }, + { + operation: "{operation}", + } + ); + + const func = dataconnect.onMutationExecuted( + "services/my-service/connectors/my-connector/operations/{operation}", + () => true + ); + expect(func.__endpoint).to.deep.eq(expectedEndpoint); + }); + + it("should create a func using param opts with an operation path pattern", () => { + const expectedEndpoint = makeExpectedEndpoint( + dataconnect.mutationExecutedEventType, + { + service: "my-service", + connector: "my-connector", + }, + { + operation: "{operation}", + } + ); + + const func = dataconnect.onMutationExecuted( + { + service: "my-service", + connector: "my-connector", + operation: "{operation}", + }, + () => true + ); + expect(func.__endpoint).to.deep.eq(expectedEndpoint); + }); + + it("should create a func with path patterns", () => { + const expectedEndpoint = makeExpectedEndpoint( + dataconnect.mutationExecutedEventType, + {}, + { + service: "{service}", + connector: "{connector}", + operation: "{operation}", + } + ); + + const func = dataconnect.onMutationExecuted( + "services/{service}/connectors/{connector}/operations/{operation}", + () => true + ); + expect(func.__endpoint).to.deep.eq(expectedEndpoint); + }); + + it("should create a func using param opts with path patterns", () => { + const expectedEndpoint = makeExpectedEndpoint( + dataconnect.mutationExecutedEventType, + {}, + { + service: "{service}", + connector: "{connector}", + operation: "{operation}", + } + ); + + const func = dataconnect.onMutationExecuted( + { + service: "{service}", + connector: "{connector}", + operation: "{operation}", + }, + () => true + ); + expect(func.__endpoint).to.deep.eq(expectedEndpoint); + }); + + it("should create a func with a service wildcard", () => { + const expectedEndpoint = makeExpectedEndpoint( + dataconnect.mutationExecutedEventType, + { + connector: "my-connector", + operation: "my-operation", + }, + { + service: "*", + } + ); + + const func = dataconnect.onMutationExecuted( + "services/*/connectors/my-connector/operations/my-operation", + () => true + ); + expect(func.__endpoint).to.deep.eq(expectedEndpoint); + }); + + it("should create a func using param opts with a service wildcard", () => { + const expectedEndpoint = makeExpectedEndpoint( + dataconnect.mutationExecutedEventType, + { + connector: "my-connector", + operation: "my-operation", + }, + { + service: "*", + } + ); + + const func = dataconnect.onMutationExecuted( + { + service: "*", + connector: "my-connector", + operation: "my-operation", + }, + () => true + ); + expect(func.__endpoint).to.deep.eq(expectedEndpoint); + }); + + it("should create a func with a connector wildcard", () => { + const expectedEndpoint = makeExpectedEndpoint( + dataconnect.mutationExecutedEventType, + { + service: "my-service", + operation: "my-operation", + }, + { + connector: "*", + } + ); + + const func = dataconnect.onMutationExecuted( + "services/my-service/connectors/*/operations/my-operation", + () => true + ); + expect(func.__endpoint).to.deep.eq(expectedEndpoint); + }); + + it("should create a func using param opts with a connector wildcard", () => { + const expectedEndpoint = makeExpectedEndpoint( + dataconnect.mutationExecutedEventType, + { + service: "my-service", + operation: "my-operation", + }, + { + connector: "*", + } + ); + + const func = dataconnect.onMutationExecuted( + { + service: "my-service", + connector: "*", + operation: "my-operation", + }, + () => true + ); + expect(func.__endpoint).to.deep.eq(expectedEndpoint); + }); + + it("should create a func with an operation wildcard", () => { + const expectedEndpoint = makeExpectedEndpoint( + dataconnect.mutationExecutedEventType, + { + service: "my-service", + connector: "my-connector", + }, + { + operation: "*", + } + ); + + const func = dataconnect.onMutationExecuted( + "services/my-service/connectors/my-connector/operations/*", + () => true + ); + expect(func.__endpoint).to.deep.eq(expectedEndpoint); + }); + + it("should create a func using param opts with an operation wildcard", () => { + const expectedEndpoint = makeExpectedEndpoint( + dataconnect.mutationExecutedEventType, + { + service: "my-service", + connector: "my-connector", + }, + { + operation: "*", + } + ); + + const func = dataconnect.onMutationExecuted( + { + service: "my-service", + connector: "my-connector", + operation: "*", + }, + () => true + ); + expect(func.__endpoint).to.deep.eq(expectedEndpoint); + }); + + it("should create a func with wildcards", () => { + const expectedEndpoint = makeExpectedEndpoint( + dataconnect.mutationExecutedEventType, + {}, + { + service: "*", + connector: "*", + operation: "*", + } + ); + + const func = dataconnect.onMutationExecuted( + "services/*/connectors/*/operations/*", + () => true + ); + expect(func.__endpoint).to.deep.eq(expectedEndpoint); + }); + + it("should create a func using param opts with wildcards", () => { + const expectedEndpoint = makeExpectedEndpoint( + dataconnect.mutationExecutedEventType, + {}, + { + service: "*", + connector: "*", + operation: "*", + } + ); + + const func = dataconnect.onMutationExecuted( + { + service: "*", + connector: "*", + operation: "*", + }, + () => true + ); + expect(func.__endpoint).to.deep.eq(expectedEndpoint); + }); + + it("calls init function", async () => { + const event: CloudEvent = { + specversion: "1.0", + id: "id", + source: "google.firebase.dataconnect.connector.v1.mutationExecuted", + type: "type", + time: "time", + data: "data", + }; + + let hello; + onInit(() => (hello = "world")); + expect(hello).to.be.undefined; + await dataconnect.onMutationExecuted( + "services/*/connectors/*/operations/*", + () => null + )(event); + expect(hello).to.equal("world"); + }); + }); +}); diff --git a/src/common/params.ts b/src/common/params.ts index e0b0b8537..8ff3f30a1 100644 --- a/src/common/params.ts +++ b/src/common/params.ts @@ -60,11 +60,11 @@ export type NullSafe = S extends null * A type that extracts parameter name enclosed in bracket as string. * Ignore wildcard matches * - * For example, Extract<"{uid}"> is "uid". - * For example, Extract<"{uid=*}"> is "uid". - * For example, Extract<"{uid=**}"> is "uid". + * For example, VarName<"{uid}"> is "uid". + * For example, VarName<"{uid=*}"> is "uid". + * For example, VarName<"{uid=**}"> is "uid". */ -export type Extract = Part extends `{${infer Param}=**}` +export type VarName = Part extends `{${infer Param}=**}` ? Param : Part extends `{${infer Param}=*}` ? Param @@ -73,7 +73,7 @@ export type Extract = Part extends `{${infer Param}=**}` : never; /** - * A type that maps all parameter capture gropus into keys of a record. + * A type that maps all parameter capture groups into keys of a record. * For example, ParamsOf<"users/{uid}"> is { uid: string } * ParamsOf<"users/{uid}/logs/{log}"> is { uid: string; log: string } * ParamsOf<"some/static/data"> is {} @@ -90,7 +90,7 @@ export type ParamsOf> = // N.B. I'm not sure why PathPattern isn't detected to not be an // Expression per the check above. Since we have the check above // The Exclude call should be safe. - [Key in Extract< + [Key in VarName< Split>>, "/">[number] >]: string; }; diff --git a/src/v2/index.ts b/src/v2/index.ts index 23fc424b8..ccee302e5 100644 --- a/src/v2/index.ts +++ b/src/v2/index.ts @@ -41,6 +41,7 @@ import * as tasks from "./providers/tasks"; import * as remoteConfig from "./providers/remoteConfig"; import * as testLab from "./providers/testLab"; import * as firestore from "./providers/firestore"; +import * as dataconnect from "./providers/dataconnect"; export { alerts, @@ -56,6 +57,7 @@ export { remoteConfig, testLab, firestore, + dataconnect, }; export { diff --git a/src/v2/providers/dataconnect.ts b/src/v2/providers/dataconnect.ts new file mode 100644 index 000000000..54fe71b13 --- /dev/null +++ b/src/v2/providers/dataconnect.ts @@ -0,0 +1,281 @@ +// The MIT License (MIT) +// +// Copyright (c) 2025 Firebase +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import { CloudEvent, CloudFunction } from "../core"; +import { ParamsOf } from "../../common/params"; +import { EventHandlerOptions, getGlobalOptions, optionsToEndpoint } from "../options"; +import { normalizePath } from "../../common/utilities/path"; +import { wrapTraceContext } from "../trace"; +import { withInit } from "../../common/onInit"; +import { initV2Endpoint, ManifestEndpoint } from "../../runtime/manifest"; +import { PathPattern } from "../../common/utilities/path-pattern"; + +/** @internal */ +export const mutationExecutedEventType = + "google.firebase.dataconnect.connector.v1.mutationExecuted"; + +/** @hidden */ +export interface SourceLocation { + line: number; + column: number; +} + +/** @hidden */ +export interface GraphqlErrorExtensions { + file: string; + code: string; + debugDetails: string; +} + +/** @hidden */ +export interface GraphqlError { + message: string; + locations: Array; + path: Array; + extensions: GraphqlErrorExtensions; +} + +/** @hidden */ +export interface RawMutation { + data: R; + variables: V; + errors: Array; +} + +/** @hidden */ +export interface MutationEventData { + ["@type"]: "type.googleapis.com/google.events.firebase.dataconnect.v1.MutationEventData"; + payload: RawMutation; +} + +/** @hidden */ +export interface RawDataConnectEvent extends CloudEvent { + project: string; + location: string; + service: string; + schema: string; + connector: string; + operation: string; +} + +/** OperationOptions extend EventHandlerOptions with a provided service, connector, and operation. */ +export interface OperationOptions extends EventHandlerOptions { + /** Firebase Data Connect service ID */ + service: string; + /** Firebase Data Connect connector ID */ + connector: string; + /** Name of the operation */ + operation: string; +} + +export interface DataConnectEvent> extends CloudEvent { + /** The location of the Firebase Data Connect instance */ + location: string; + /** The project identifier */ + project: string; + /** + * An object containing the values of the path patterns. + * Only named capture groups will be populated - {key}, {key=*}, {key=**}. + */ + params: Params; +} + +/** + * Event handler that triggers when a mutation is executed in Firebase Data Connect. + * + * @param mutation - The mutation path to trigger on. + * @param handler - Event handler which is run every time a mutation is executed. + */ +export function onMutationExecuted< + Mutation extends string, + Variables = unknown, + ResponseData = unknown +>( + mutation: Mutation, + handler: ( + event: DataConnectEvent, ParamsOf> + ) => unknown | Promise +): CloudFunction, ParamsOf>>; + +/** + * Event handler that triggers when a mutation is executed in Firebase Data Connect. + * + * @param opts - Options that can be set on an individual event-handling function. + * @param handler - Event handler which is run every time a mutation is executed. + */ +export function onMutationExecuted< + Mutation extends string, + Variables = unknown, + ResponseData = unknown +>( + opts: OperationOptions, + handler: ( + event: DataConnectEvent, ParamsOf> + ) => unknown | Promise +): CloudFunction, ParamsOf>>; + +/** + * Event handler that triggers when a mutation is executed in Firebase Data Connect. + * + * @param mutationOrOpts - Options or string mutation path. + * @param handler - Event handler which is run every time a mutation is executed. + */ +export function onMutationExecuted< + Mutation extends string, + Variables = unknown, + ResponseData = unknown +>( + mutationOrOpts: Mutation | OperationOptions, + handler: ( + event: DataConnectEvent, ParamsOf> + ) => unknown | Promise +): CloudFunction, ParamsOf>> { + return onOperation(mutationExecutedEventType, mutationOrOpts, handler); +} + +function getOpts(mutationOrOpts: string | OperationOptions) { + const operationRegex = new RegExp("services/([^/]+)/connectors/([^/]+)/operations/([^/]+)"); + + let service: string; + let connector: string; + let operation: string; + let opts: EventHandlerOptions; + if (typeof mutationOrOpts === "string") { + const path = normalizePath(mutationOrOpts); + const match = path.match(operationRegex); + if (!match) { + throw new Error(`Invalid operation path: ${path}`); + } + + service = match[1]; + connector = match[2]; + operation = match[3]; + opts = {}; + } else { + service = mutationOrOpts.service; + connector = mutationOrOpts.connector; + operation = mutationOrOpts.operation; + opts = { ...mutationOrOpts }; + + delete (opts as any).service; + delete (opts as any).connector; + delete (opts as any).operation; + } + + return { + service, + connector, + operation, + opts, + }; +} + +function makeEndpoint( + eventType: string, + opts: EventHandlerOptions, + service: PathPattern, + connector: PathPattern, + operation: PathPattern +): ManifestEndpoint { + const baseOpts = optionsToEndpoint(getGlobalOptions()); + const specificOpts = optionsToEndpoint(opts); + + const eventFilters: Record = {}; + const eventFilterPathPatterns: Record = {}; + + service.hasWildcards() + ? (eventFilterPathPatterns.service = service.getValue()) + : (eventFilters.service = service.getValue()); + connector.hasWildcards() + ? (eventFilterPathPatterns.connector = connector.getValue()) + : (eventFilters.connector = connector.getValue()); + operation.hasWildcards() + ? (eventFilterPathPatterns.operation = operation.getValue()) + : (eventFilters.operation = operation.getValue()); + + return { + ...initV2Endpoint(getGlobalOptions(), opts), + platform: "gcfv2", + ...baseOpts, + ...specificOpts, + labels: { + ...baseOpts?.labels, + ...specificOpts?.labels, + }, + eventTrigger: { + eventType, + eventFilters, + eventFilterPathPatterns, + retry: opts.retry ?? false, + }, + }; +} + +function makeParams( + event: RawDataConnectEvent>, + service: PathPattern, + connector: PathPattern, + operation: PathPattern +) { + return { + ...service.extractMatches(event.service), + ...connector.extractMatches(event.connector), + ...operation.extractMatches(event.operation), + }; +} + +function onOperation( + eventType: string, + mutationOrOpts: string | OperationOptions, + handler: (event: DataConnectEvent>) => any | Promise +): CloudFunction>> { + const { service, connector, operation, opts } = getOpts(mutationOrOpts); + + const servicePattern = new PathPattern(service); + const connectorPattern = new PathPattern(connector); + const operationPattern = new PathPattern(operation); + + // wrap the handler + const func = (raw: CloudEvent) => { + const event = raw as RawDataConnectEvent>; + const params = makeParams(event, servicePattern, connectorPattern, operationPattern); + + const dataConnectEvent: DataConnectEvent> = { + ...event, + params, + }; + + return wrapTraceContext(withInit(handler))(dataConnectEvent); + }; + + func.run = handler; + + func.__endpoint = makeEndpoint( + eventType, + opts, + servicePattern, + connectorPattern, + operationPattern + ); + + return func; +} diff --git a/v2/dataconnect.js b/v2/dataconnect.js new file mode 100644 index 000000000..3e4b26904 --- /dev/null +++ b/v2/dataconnect.js @@ -0,0 +1,26 @@ +// The MIT License (MIT) +// +// Copyright (c) 2025 Firebase +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// This file is not part of the firebase-functions SDK. It is used to silence the +// imports eslint plugin until it can understand import paths defined by node +// package exports. +// For more information, see github.com/import-js/eslint-plugin-import/issues/1810