Skip to content

Commit

Permalink
Parameterize db info for firestore events
Browse files Browse the repository at this point in the history
  • Loading branch information
inlined committed Mar 13, 2024
1 parent c260c2a commit 978296f
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 26 deletions.
111 changes: 109 additions & 2 deletions spec/v2/providers/firestore.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { Timestamp } from "firebase-admin/firestore";
import * as firestore from "../../../src/v2/providers/firestore";
import { PathPattern } from "../../../src/common/utilities/path-pattern";
import { onInit } from "../../../src/v2/core";
import * as params from "../../../src/params";

/** static-complied protobuf */
const DocumentEventData = google.events.cloud.firestore.v1.DocumentEventData;
Expand Down Expand Up @@ -148,6 +149,20 @@ const writtenData = {
const writtenProto = DocumentEventData.create(writtenData);

describe("firestore", () => {
let docParam: params.Expression<string>;
let nsParam: params.Expression<string>;
let dbParam: params.Expression<string>;

before(() => {
docParam = params.defineString("DOCUMENT");
nsParam = params.defineString("NAMESPACE");
dbParam = params.defineString("DATABASE");
});

after(() => {
params.clearParams();
});

describe("onDocumentWritten", () => {
it("should create a func", () => {
const expectedEp = makeExpectedEp(
Expand Down Expand Up @@ -194,6 +209,29 @@ describe("firestore", () => {
expect(func.__endpoint).to.deep.eq(expectedEp);
});

it("should create a func with param opts", () => {
const expectedEp = makeExpectedEp(
firestore.writtenEventType,
{
database: dbParam,
namespace: nsParam,
},
{
document: docParam,
}
);

const func = firestore.onDocumentWritten(
{
database: dbParam,
namespace: nsParam,
document: docParam,
},
() => true
);
expect(func.__endpoint).to.deep.eq(expectedEp);
});

it("calls init function", async () => {
const event: firestore.RawFirestoreEvent = {
...eventBase,
Expand Down Expand Up @@ -258,6 +296,29 @@ describe("firestore", () => {
expect(func.__endpoint).to.deep.eq(expectedEp);
});

it("should create a func with param opts", () => {
const expectedEp = makeExpectedEp(
firestore.createdEventType,
{
database: dbParam,
namespace: nsParam,
},
{
document: docParam,
}
);

const func = firestore.onDocumentCreated(
{
database: dbParam,
namespace: nsParam,
document: docParam,
},
() => true
);
expect(func.__endpoint).to.deep.eq(expectedEp);
});

it("calls init function", async () => {
const event: firestore.RawFirestoreEvent = {
...eventBase,
Expand Down Expand Up @@ -322,6 +383,29 @@ describe("firestore", () => {
expect(func.__endpoint).to.deep.eq(expectedEp);
});

it("should create a func with param opts", () => {
const expectedEp = makeExpectedEp(
firestore.updatedEventType,
{
database: dbParam,
namespace: nsParam,
},
{
document: docParam,
}
);

const func = firestore.onDocumentUpdated(
{
database: dbParam,
namespace: nsParam,
document: docParam,
},
() => true
);
expect(func.__endpoint).to.deep.eq(expectedEp);
});

it("calls init function", async () => {
const event: firestore.RawFirestoreEvent = {
...eventBase,
Expand Down Expand Up @@ -386,6 +470,29 @@ describe("firestore", () => {
expect(func.__endpoint).to.deep.eq(expectedEp);
});

it("should create a func with param opts", () => {
const expectedEp = makeExpectedEp(
firestore.deletedEventType,
{
database: dbParam,
namespace: nsParam,
},
{
document: docParam,
}
);

const func = firestore.onDocumentDeleted(
{
database: dbParam,
namespace: nsParam,
document: docParam,
},
() => true
);
expect(func.__endpoint).to.deep.eq(expectedEp);
});

it("calls init function", async () => {
const event: firestore.RawFirestoreEvent = {
...eventBase,
Expand Down Expand Up @@ -663,7 +770,7 @@ describe("firestore", () => {
const ep = firestore.makeEndpoint(
firestore.createdEventType,
{ region: "us-central1" },
new PathPattern("foo/{bar}"),
"foo/{bar}",
"my-db",
"my-ns"
);
Expand All @@ -686,7 +793,7 @@ describe("firestore", () => {
const ep = firestore.makeEndpoint(
firestore.createdEventType,
{ region: "us-central1" },
new PathPattern("foo/fGRodw71mHutZ4wGDuT8"),
"foo/fGRodw71mHutZ4wGDuT8",
"my-db",
"my-ns"
);
Expand Down
15 changes: 12 additions & 3 deletions src/common/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import { Expression } from "../params";

/**
* A type that splits literal string S with delimiter D.
*
Expand Down Expand Up @@ -78,10 +80,17 @@ export type Extract<Part extends string> = Part extends `{${infer Param}=**}`
*
* For flexibility reasons, ParamsOf<string> is Record<string, string>
*/
export type ParamsOf<PathPattern extends string> =
export type ParamsOf<PathPattern extends string | Expression<string>> =
// if we have lost type information, revert back to an untyped dictionary
string extends PathPattern
PathPattern extends Expression<string>
? Record<string, string>
: string extends PathPattern
? Record<string, string>
: {
[Key in Extract<Split<NullSafe<PathPattern>, "/">[number]>]: string;
// N.B. I'm not sure why PathPattern isn't detected to not be an
// Expression<string> per the check above. Since we have the check above
// The Exclude call should be safe.
[Key in Extract<
Split<NullSafe<Exclude<PathPattern, Expression<string>>>, "/">[number]
>]: string;
};
52 changes: 31 additions & 21 deletions src/v2/providers/firestore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
} from "../../common/providers/firestore";
import { wrapTraceContext } from "../trace";
import { withInit } from "../../common/onInit";
import { Expression } from "../../params";

export { Change };

Expand Down Expand Up @@ -106,11 +107,11 @@ export interface FirestoreEvent<T, Params = Record<string, string>> extends Clou
/** DocumentOptions extend EventHandlerOptions with provided document and optional database and namespace. */
export interface DocumentOptions<Document extends string = string> extends EventHandlerOptions {
/** The document path */
document: Document;
document: Document | Expression<string>;
/** The Firestore database */
database?: string;
database?: string | Expression<string>;
/** The Firestore namespace */
namespace?: string;
namespace?: string | Expression<string>;
}

/**
Expand Down Expand Up @@ -278,17 +279,20 @@ export function onDocumentDeleted<Document extends string>(

/** @internal */
export function getOpts(documentOrOpts: string | DocumentOptions) {
let document: string;
let database: string;
let namespace: string;
let document: string | Expression<string>;
let database: string | Expression<string>;
let namespace: string | Expression<string>;
let opts: EventHandlerOptions;
if (typeof documentOrOpts === "string") {
document = normalizePath(documentOrOpts);
database = "(default)";
namespace = "(default)";
opts = {};
} else {
document = normalizePath(documentOrOpts.document);
document =
typeof documentOrOpts.document === "string"
? normalizePath(documentOrOpts.document)
: documentOrOpts.document;
database = documentOrOpts.database || "(default)";
namespace = documentOrOpts.namespace || "(default)";
opts = { ...documentOrOpts };
Expand Down Expand Up @@ -398,21 +402,25 @@ export function makeChangedFirestoreEvent<Params>(
export function makeEndpoint(
eventType: string,
opts: EventHandlerOptions,
document: PathPattern,
database: string,
namespace: string
document: string | Expression<string>,
database: string | Expression<string>,
namespace: string | Expression<string>
): ManifestEndpoint {
const baseOpts = optionsToEndpoint(getGlobalOptions());
const specificOpts = optionsToEndpoint(opts);

const eventFilters: Record<string, string> = {
const eventFilters: Record<string, string | Expression<string>> = {
database,
namespace,
};
const eventFilterPathPatterns: Record<string, string> = {};
document.hasWildcards()
? (eventFilterPathPatterns.document = document.getValue())
: (eventFilters.document = document.getValue());
const eventFilterPathPatterns: Record<string, string | Expression<string>> = {};
const maybePattern =
typeof document === "string" ? new PathPattern(document).hasWildcards() : true;
if (maybePattern) {
eventFilterPathPatterns.document = document;
} else {
eventFilters.document = document;
}

return {
...initV2Endpoint(getGlobalOptions(), opts),
Expand Down Expand Up @@ -440,19 +448,20 @@ export function onOperation<Document extends string>(
): CloudFunction<FirestoreEvent<QueryDocumentSnapshot, ParamsOf<Document>>> {
const { document, database, namespace, opts } = getOpts(documentOrOpts);

const documentPattern = new PathPattern(document);

// wrap the handler
const func = (raw: CloudEvent<unknown>) => {
const event = raw as RawFirestoreEvent;
const documentPattern = new PathPattern(
typeof document === "string" ? document : document.value()
);
const params = makeParams(event.document, documentPattern) as unknown as ParamsOf<Document>;
const firestoreEvent = makeFirestoreEvent(eventType, event, params);
return wrapTraceContext(withInit(handler))(firestoreEvent);
};

func.run = handler;

func.__endpoint = makeEndpoint(eventType, opts, documentPattern, database, namespace);
func.__endpoint = makeEndpoint(eventType, opts, document, database, namespace);

return func;
}
Expand All @@ -467,19 +476,20 @@ export function onChangedOperation<Document extends string>(
): CloudFunction<FirestoreEvent<Change<QueryDocumentSnapshot>, ParamsOf<Document>>> {
const { document, database, namespace, opts } = getOpts(documentOrOpts);

const documentPattern = new PathPattern(document);

// wrap the handler
const func = (raw: CloudEvent<unknown>) => {
const event = raw as RawFirestoreEvent;
const documentPattern = new PathPattern(
typeof document === "string" ? document : document.value()
);
const params = makeParams(event.document, documentPattern) as unknown as ParamsOf<Document>;
const firestoreEvent = makeChangedFirestoreEvent(event, params);
return wrapTraceContext(withInit(handler))(firestoreEvent);
};

func.run = handler;

func.__endpoint = makeEndpoint(eventType, opts, documentPattern, database, namespace);
func.__endpoint = makeEndpoint(eventType, opts, document, database, namespace);

return func;
}

0 comments on commit 978296f

Please sign in to comment.