From 68927e46c69c785cdbb7b8a008c962b34c5ef732 Mon Sep 17 00:00:00 2001 From: Cole Rogers Date: Tue, 21 Jun 2022 10:39:02 -0400 Subject: [PATCH 1/7] moving change into v2 core --- spec/v2/core.spec.ts | 121 +++++++++++++++++++++++++++++++++++ src/v2/core.ts | 96 +++++++++++++++++++++++++++ src/v2/providers/database.ts | 3 +- 3 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 spec/v2/core.spec.ts diff --git a/spec/v2/core.spec.ts b/spec/v2/core.spec.ts new file mode 100644 index 000000000..a9eb76928 --- /dev/null +++ b/spec/v2/core.spec.ts @@ -0,0 +1,121 @@ +// The MIT License (MIT) +// +// Copyright (c) 2022 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 { Change } from '../../src/v2/core'; + +describe('Change', () => { + describe('applyFieldMask', () => { + const after = { + foo: 'bar', + num: 2, + obj: { + a: 1, + b: 2, + }, + }; + + it('should handle deleted values', () => { + const sparseBefore = { baz: 'qux' }; + const fieldMask = 'baz'; + expect( + Change.applyFieldMask(sparseBefore, after, fieldMask) + ).to.deep.equal({ + foo: 'bar', + num: 2, + obj: { + a: 1, + b: 2, + }, + baz: 'qux', + }); + }); + + it('should handle created values', () => { + const sparseBefore = {}; + const fieldMask = 'num,obj.a'; + expect( + Change.applyFieldMask(sparseBefore, after, fieldMask) + ).to.deep.equal({ + foo: 'bar', + obj: { + b: 2, + }, + }); + }); + + it('should handle mutated values', () => { + const sparseBefore = { + num: 3, + obj: { + a: 3, + }, + }; + const fieldMask = 'num,obj.a'; + expect( + Change.applyFieldMask(sparseBefore, after, fieldMask) + ).to.deep.equal({ + foo: 'bar', + num: 3, + obj: { + a: 3, + b: 2, + }, + }); + }); + }); + + describe('fromJSON', () => { + it('should create a Change object with a `before` and `after`', () => { + const created = Change.fromJSON({ + before: { foo: 'bar' }, + after: { foo: 'faz' }, + }); + expect(created instanceof Change).to.equal(true); + expect(created.before).to.deep.equal({ foo: 'bar' }); + expect(created.after).to.deep.equal({ foo: 'faz' }); + }); + + it('should apply the customizer function to `before` and `after`', () => { + function customizer(input: any) { + input.another = 'value'; + // _.set(input, 'another', 'value'); + return input as T; + } + const created = Change.fromJSON( + { + before: { foo: 'bar' }, + after: { foo: 'faz' }, + }, + customizer + ); + expect(created.before).to.deep.equal({ + foo: 'bar', + another: 'value', + }); + expect(created.after).to.deep.equal({ + foo: 'faz', + another: 'value', + }); + }); + }); +}); diff --git a/src/v2/core.ts b/src/v2/core.ts index 5e71d2458..bb6c18394 100644 --- a/src/v2/core.ts +++ b/src/v2/core.ts @@ -112,3 +112,99 @@ export interface CloudFunction> { */ run(event: EventType): any | Promise; } + +/** + * The Functions interface for events that change state, such as + * Realtime Database or Cloud Firestore `onWrite` and `onUpdate`. + * + * For more information about the format used to construct `Change` objects, see + * [`cloud-functions.ChangeJson`](/docs/reference/functions/cloud_functions_.changejson). + * + */ +export class Change { + constructor(public before: T, public after: T) {} +} + +/** + * `ChangeJson` is the JSON format used to construct a Change object. + */ +export interface ChangeJson { + /** + * Key-value pairs representing state of data after the change. + */ + after?: any; + /** + * Key-value pairs representing state of data before the change. If + * `fieldMask` is set, then only fields that changed are present in `before`. + */ + before?: any; + /** + * @hidden + * Comma-separated string that represents names of fields that changed. + */ + fieldMask?: string; +} + +export namespace Change { + /** @hidden */ + function reinterpretCast(x: any) { + return x as T; + } + + /** + * @hidden + * Factory method for creating a Change from a `before` object and an `after` + * object. + */ + export function fromObjects(before: T, after: T) { + return new Change(before, after); + } + + /** + * @hidden + * Factory method for creating a Change from a JSON and an optional customizer + * function to be applied to both the `before` and the `after` fields. + */ + export function fromJSON( + json: ChangeJson, + customizer: (x: any) => T = reinterpretCast + ): Change { + let before = { ...json.before }; + if (json.fieldMask) { + before = applyFieldMask(before, json.after, json.fieldMask); + } + + return Change.fromObjects( + customizer(before || {}), + customizer(json.after || {}) + ); + } + + /** @hidden */ + export function applyFieldMask( + sparseBefore: any, + after: any, + fieldMask: string + ) { + const before = { ...after }; + const masks = fieldMask.split(','); + + for (const mask of masks) { + const parts = mask.split('.'); + const head = parts[0]; + const tail = parts.slice(1).join('.'); + if (parts.length > 1) { + before[head] = applyFieldMask(sparseBefore?.[head], after[head], tail); + continue; + } + const val = sparseBefore?.[head]; + if (typeof val === 'undefined') { + delete before[mask]; + } else { + before[mask] = val; + } + } + + return before; + } +} diff --git a/src/v2/providers/database.ts b/src/v2/providers/database.ts index e0b410422..a7aa9acb9 100644 --- a/src/v2/providers/database.ts +++ b/src/v2/providers/database.ts @@ -21,13 +21,12 @@ // SOFTWARE. import { apps } from '../../apps'; -import { Change } from '../../cloud-functions'; import { DataSnapshot } from '../../common/providers/database'; import { ManifestEndpoint } from '../../runtime/manifest'; import { normalizePath } from '../../utilities/path'; import { PathPattern } from '../../utilities/path-pattern'; import { applyChange } from '../../utils'; -import { CloudEvent, CloudFunction } from '../core'; +import { Change, CloudEvent, CloudFunction } from '../core'; import * as options from '../options'; export { DataSnapshot }; From 797efe1567dbf8e77d914f52e578512ad29844de Mon Sep 17 00:00:00 2001 From: Cole Rogers Date: Tue, 21 Jun 2022 19:41:43 -0400 Subject: [PATCH 2/7] moving Change to common and fixing imports --- spec/{v2 => common}/core.spec.ts | 2 +- spec/v1/cloud-functions.spec.ts | 97 ------------------------- src/cloud-functions.ts | 89 ----------------------- src/common/core.ts | 117 +++++++++++++++++++++++++++++++ src/providers/database.ts | 2 +- src/providers/firestore.ts | 2 +- src/v2/core.ts | 96 ------------------------- src/v2/providers/database.ts | 3 +- 8 files changed, 122 insertions(+), 286 deletions(-) rename spec/{v2 => common}/core.spec.ts (98%) create mode 100644 src/common/core.ts diff --git a/spec/v2/core.spec.ts b/spec/common/core.spec.ts similarity index 98% rename from spec/v2/core.spec.ts rename to spec/common/core.spec.ts index a9eb76928..0ddabdfa5 100644 --- a/spec/v2/core.spec.ts +++ b/spec/common/core.spec.ts @@ -21,7 +21,7 @@ // SOFTWARE. import { expect } from 'chai'; -import { Change } from '../../src/v2/core'; +import { Change } from '../../src/common/core'; describe('Change', () => { describe('applyFieldMask', () => { diff --git a/spec/v1/cloud-functions.spec.ts b/spec/v1/cloud-functions.spec.ts index fec1bd580..336c00282 100644 --- a/spec/v1/cloud-functions.spec.ts +++ b/spec/v1/cloud-functions.spec.ts @@ -24,7 +24,6 @@ import { expect } from 'chai'; import * as _ from 'lodash'; import { - Change, Event, EventContext, makeCloudFunction, @@ -354,99 +353,3 @@ describe('makeAuth and makeAuthType', () => { }); }); }); - -describe('Change', () => { - describe('applyFieldMask', () => { - const after = { - foo: 'bar', - num: 2, - obj: { - a: 1, - b: 2, - }, - }; - - it('should handle deleted values', () => { - const sparseBefore = { baz: 'qux' }; - const fieldMask = 'baz'; - expect( - Change.applyFieldMask(sparseBefore, after, fieldMask) - ).to.deep.equal({ - foo: 'bar', - num: 2, - obj: { - a: 1, - b: 2, - }, - baz: 'qux', - }); - }); - - it('should handle created values', () => { - const sparseBefore = {}; - const fieldMask = 'num,obj.a'; - expect( - Change.applyFieldMask(sparseBefore, after, fieldMask) - ).to.deep.equal({ - foo: 'bar', - obj: { - b: 2, - }, - }); - }); - - it('should handle mutated values', () => { - const sparseBefore = { - num: 3, - obj: { - a: 3, - }, - }; - const fieldMask = 'num,obj.a'; - expect( - Change.applyFieldMask(sparseBefore, after, fieldMask) - ).to.deep.equal({ - foo: 'bar', - num: 3, - obj: { - a: 3, - b: 2, - }, - }); - }); - }); - - describe('fromJSON', () => { - it('should create a Change object with a `before` and `after`', () => { - const created = Change.fromJSON({ - before: { foo: 'bar' }, - after: { foo: 'faz' }, - }); - expect(created instanceof Change).to.equal(true); - expect(created.before).to.deep.equal({ foo: 'bar' }); - expect(created.after).to.deep.equal({ foo: 'faz' }); - }); - - it('should apply the customizer function to `before` and `after`', () => { - function customizer(input: any) { - _.set(input, 'another', 'value'); - return input as T; - } - const created = Change.fromJSON( - { - before: { foo: 'bar' }, - after: { foo: 'faz' }, - }, - customizer - ); - expect(created.before).to.deep.equal({ - foo: 'bar', - another: 'value', - }); - expect(created.after).to.deep.equal({ - foo: 'faz', - another: 'value', - }); - }); - }); -}); diff --git a/src/cloud-functions.ts b/src/cloud-functions.ts index a362230fb..f866faa22 100644 --- a/src/cloud-functions.ts +++ b/src/cloud-functions.ts @@ -155,95 +155,6 @@ export interface EventContext { timestamp: string; } -/** - * The Functions interface for events that change state, such as - * Realtime Database or Cloud Firestore `onWrite` and `onUpdate`. - * - * For more information about the format used to construct `Change` objects, see - * [`cloud-functions.ChangeJson`](/docs/reference/functions/cloud_functions_.changejson). - * - */ -export class Change { - constructor(public before: T, public after: T) {} -} - -/** - * `ChangeJson` is the JSON format used to construct a Change object. - */ -export interface ChangeJson { - /** - * Key-value pairs representing state of data after the change. - */ - after?: any; - /** - * Key-value pairs representing state of data before the change. If - * `fieldMask` is set, then only fields that changed are present in `before`. - */ - before?: any; - /** - * @hidden - * Comma-separated string that represents names of fields that changed. - */ - fieldMask?: string; -} - -export namespace Change { - /** @hidden */ - function reinterpretCast(x: any) { - return x as T; - } - - /** - * @hidden - * Factory method for creating a Change from a `before` object and an `after` - * object. - */ - export function fromObjects(before: T, after: T) { - return new Change(before, after); - } - - /** - * @hidden - * Factory method for creating a Change from a JSON and an optional customizer - * function to be applied to both the `before` and the `after` fields. - */ - export function fromJSON( - json: ChangeJson, - customizer: (x: any) => T = reinterpretCast - ): Change { - let before = { ...json.before }; - if (json.fieldMask) { - before = applyFieldMask(before, json.after, json.fieldMask); - } - - return Change.fromObjects( - customizer(before || {}), - customizer(json.after || {}) - ); - } - - /** @hidden */ - export function applyFieldMask( - sparseBefore: any, - after: any, - fieldMask: string - ) { - const before = { ...after }; - const masks = fieldMask.split(','); - - masks.forEach((mask) => { - const val = _.get(sparseBefore, mask); - if (typeof val === 'undefined') { - _.unset(before, mask); - } else { - _.set(before, mask, val); - } - }); - - return before; - } -} - /** * Resource is a standard format for defining a resource * (google.rpc.context.AttributeContext.Resource). In Cloud Functions, it is the diff --git a/src/common/core.ts b/src/common/core.ts new file mode 100644 index 000000000..fe58deff0 --- /dev/null +++ b/src/common/core.ts @@ -0,0 +1,117 @@ +// The MIT License (MIT) +// +// Copyright (c) 2022 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. + +/** + * The Functions interface for events that change state, such as + * Realtime Database or Cloud Firestore `onWrite` and `onUpdate`. + * + * For more information about the format used to construct `Change` objects, see + * [`cloud-functions.ChangeJson`](/docs/reference/functions/cloud_functions_.changejson). + * + */ +export class Change { + constructor(public before: T, public after: T) {} +} + +/** + * `ChangeJson` is the JSON format used to construct a Change object. + */ +export interface ChangeJson { + /** + * Key-value pairs representing state of data after the change. + */ + after?: any; + /** + * Key-value pairs representing state of data before the change. If + * `fieldMask` is set, then only fields that changed are present in `before`. + */ + before?: any; + /** + * @hidden + * Comma-separated string that represents names of fields that changed. + */ + fieldMask?: string; +} + +export namespace Change { + /** @hidden */ + function reinterpretCast(x: any) { + return x as T; + } + + /** + * @hidden + * Factory method for creating a Change from a `before` object and an `after` + * object. + */ + export function fromObjects(before: T, after: T) { + return new Change(before, after); + } + + /** + * @hidden + * Factory method for creating a Change from a JSON and an optional customizer + * function to be applied to both the `before` and the `after` fields. + */ + export function fromJSON( + json: ChangeJson, + customizer: (x: any) => T = reinterpretCast + ): Change { + let before = { ...json.before }; + if (json.fieldMask) { + before = applyFieldMask(before, json.after, json.fieldMask); + } + + return Change.fromObjects( + customizer(before || {}), + customizer(json.after || {}) + ); + } + + /** @hidden */ + export function applyFieldMask( + sparseBefore: any, + after: any, + fieldMask: string + ) { + const before = { ...after }; + const masks = fieldMask.split(','); + + for (const mask of masks) { + const parts = mask.split('.'); + const head = parts[0]; + const tail = parts.slice(1).join('.'); + if (parts.length > 1) { + before[head] = applyFieldMask(sparseBefore?.[head], after[head], tail); + continue; + } + const val = sparseBefore?.[head]; + if (typeof val === 'undefined') { + delete before[mask]; + } else { + before[mask] = val; + } + } + + return before; + } +} diff --git a/src/providers/database.ts b/src/providers/database.ts index 5b7dc59f5..8a39de273 100644 --- a/src/providers/database.ts +++ b/src/providers/database.ts @@ -22,12 +22,12 @@ import { apps } from '../apps'; import { - Change, CloudFunction, Event, EventContext, makeCloudFunction, } from '../cloud-functions'; +import { Change } from '../common/core'; import { DataSnapshot } from '../common/providers/database'; import { firebaseConfig } from '../config'; import { DeploymentOptions } from '../function-configuration'; diff --git a/src/providers/firestore.ts b/src/providers/firestore.ts index 620040d2b..b0d1f1a95 100644 --- a/src/providers/firestore.ts +++ b/src/providers/firestore.ts @@ -26,12 +26,12 @@ import * as _ from 'lodash'; import { posix } from 'path'; import { apps } from '../apps'; import { - Change, CloudFunction, Event, EventContext, makeCloudFunction, } from '../cloud-functions'; +import { Change } from '../common/core'; import { dateToTimestampProto } from '../encoder'; import { DeploymentOptions } from '../function-configuration'; import * as logger from '../logger'; diff --git a/src/v2/core.ts b/src/v2/core.ts index bb6c18394..5e71d2458 100644 --- a/src/v2/core.ts +++ b/src/v2/core.ts @@ -112,99 +112,3 @@ export interface CloudFunction> { */ run(event: EventType): any | Promise; } - -/** - * The Functions interface for events that change state, such as - * Realtime Database or Cloud Firestore `onWrite` and `onUpdate`. - * - * For more information about the format used to construct `Change` objects, see - * [`cloud-functions.ChangeJson`](/docs/reference/functions/cloud_functions_.changejson). - * - */ -export class Change { - constructor(public before: T, public after: T) {} -} - -/** - * `ChangeJson` is the JSON format used to construct a Change object. - */ -export interface ChangeJson { - /** - * Key-value pairs representing state of data after the change. - */ - after?: any; - /** - * Key-value pairs representing state of data before the change. If - * `fieldMask` is set, then only fields that changed are present in `before`. - */ - before?: any; - /** - * @hidden - * Comma-separated string that represents names of fields that changed. - */ - fieldMask?: string; -} - -export namespace Change { - /** @hidden */ - function reinterpretCast(x: any) { - return x as T; - } - - /** - * @hidden - * Factory method for creating a Change from a `before` object and an `after` - * object. - */ - export function fromObjects(before: T, after: T) { - return new Change(before, after); - } - - /** - * @hidden - * Factory method for creating a Change from a JSON and an optional customizer - * function to be applied to both the `before` and the `after` fields. - */ - export function fromJSON( - json: ChangeJson, - customizer: (x: any) => T = reinterpretCast - ): Change { - let before = { ...json.before }; - if (json.fieldMask) { - before = applyFieldMask(before, json.after, json.fieldMask); - } - - return Change.fromObjects( - customizer(before || {}), - customizer(json.after || {}) - ); - } - - /** @hidden */ - export function applyFieldMask( - sparseBefore: any, - after: any, - fieldMask: string - ) { - const before = { ...after }; - const masks = fieldMask.split(','); - - for (const mask of masks) { - const parts = mask.split('.'); - const head = parts[0]; - const tail = parts.slice(1).join('.'); - if (parts.length > 1) { - before[head] = applyFieldMask(sparseBefore?.[head], after[head], tail); - continue; - } - const val = sparseBefore?.[head]; - if (typeof val === 'undefined') { - delete before[mask]; - } else { - before[mask] = val; - } - } - - return before; - } -} diff --git a/src/v2/providers/database.ts b/src/v2/providers/database.ts index a7aa9acb9..8ffaf0f71 100644 --- a/src/v2/providers/database.ts +++ b/src/v2/providers/database.ts @@ -21,12 +21,13 @@ // SOFTWARE. import { apps } from '../../apps'; +import { Change } from '../../common/core'; import { DataSnapshot } from '../../common/providers/database'; import { ManifestEndpoint } from '../../runtime/manifest'; import { normalizePath } from '../../utilities/path'; import { PathPattern } from '../../utilities/path-pattern'; import { applyChange } from '../../utils'; -import { Change, CloudEvent, CloudFunction } from '../core'; +import { CloudEvent, CloudFunction } from '../core'; import * as options from '../options'; export { DataSnapshot }; From 941049440ced3ab72d08330010220dcc61e899bd Mon Sep 17 00:00:00 2001 From: Cole Rogers Date: Tue, 21 Jun 2022 19:47:02 -0400 Subject: [PATCH 3/7] fix tsdoc --- src/common/core.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/core.ts b/src/common/core.ts index fe58deff0..fd36db68e 100644 --- a/src/common/core.ts +++ b/src/common/core.ts @@ -25,7 +25,7 @@ * Realtime Database or Cloud Firestore `onWrite` and `onUpdate`. * * For more information about the format used to construct `Change` objects, see - * [`cloud-functions.ChangeJson`](/docs/reference/functions/cloud_functions_.changejson). + * {@link ChangeJson} below. * */ export class Change { From 14540ec750d951832c06f982335382334ee52c56 Mon Sep 17 00:00:00 2001 From: Cole Rogers Date: Wed, 22 Jun 2022 10:04:50 -0400 Subject: [PATCH 4/7] rexporting Change from v1&v2 --- src/providers/database.ts | 2 +- src/v2/providers/database.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/providers/database.ts b/src/providers/database.ts index 8a39de273..1a871eb35 100644 --- a/src/providers/database.ts +++ b/src/providers/database.ts @@ -34,7 +34,7 @@ import { DeploymentOptions } from '../function-configuration'; import { normalizePath } from '../utilities/path'; import { applyChange } from '../utils'; -export { DataSnapshot }; +export { DataSnapshot, Change }; /** @hidden */ export const provider = 'google.firebase.database'; diff --git a/src/v2/providers/database.ts b/src/v2/providers/database.ts index 8ffaf0f71..a61798897 100644 --- a/src/v2/providers/database.ts +++ b/src/v2/providers/database.ts @@ -30,7 +30,7 @@ import { applyChange } from '../../utils'; import { CloudEvent, CloudFunction } from '../core'; import * as options from '../options'; -export { DataSnapshot }; +export { DataSnapshot, Change }; /** @internal */ export const writtenEventType = 'google.firebase.database.ref.v1.written'; From 79cc151a9e2119ad8dbf9082ce41a99555304d96 Mon Sep 17 00:00:00 2001 From: Cole Rogers Date: Thu, 23 Jun 2022 13:40:22 -0400 Subject: [PATCH 5/7] remove unused functions from Change namespace --- src/common/core.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/common/core.ts b/src/common/core.ts index fd36db68e..adcbf8230 100644 --- a/src/common/core.ts +++ b/src/common/core.ts @@ -53,11 +53,6 @@ export interface ChangeJson { } export namespace Change { - /** @hidden */ - function reinterpretCast(x: any) { - return x as T; - } - /** * @hidden * Factory method for creating a Change from a `before` object and an `after` From b13ec0663175dfe62461ed4bc770753a94d286de Mon Sep 17 00:00:00 2001 From: Cole Rogers Date: Thu, 23 Jun 2022 13:55:10 -0400 Subject: [PATCH 6/7] fixing argument --- src/common/core.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/core.ts b/src/common/core.ts index adcbf8230..de9c3f805 100644 --- a/src/common/core.ts +++ b/src/common/core.ts @@ -69,7 +69,7 @@ export namespace Change { */ export function fromJSON( json: ChangeJson, - customizer: (x: any) => T = reinterpretCast + customizer: (x: any) => T = (x) => x as T ): Change { let before = { ...json.before }; if (json.fieldMask) { From a70544e331da3dfd5d458298aaee5d7fda23217a Mon Sep 17 00:00:00 2001 From: Cole Rogers Date: Mon, 27 Jun 2022 13:52:57 -0400 Subject: [PATCH 7/7] rename interface to onValue* and rework Change --- spec/common/{core.spec.ts => change.spec.ts} | 15 ++- spec/v2/providers/database.spec.ts | 24 ++-- src/common/{core.ts => change.ts} | 83 ++++++------- src/providers/database.ts | 4 +- src/providers/firestore.ts | 2 +- src/v2/core.ts | 3 + src/v2/providers/database.ts | 122 ++++++++++++++++--- 7 files changed, 173 insertions(+), 80 deletions(-) rename spec/common/{core.spec.ts => change.spec.ts} (87%) rename src/common/{core.ts => change.ts} (77%) diff --git a/spec/common/core.spec.ts b/spec/common/change.spec.ts similarity index 87% rename from spec/common/core.spec.ts rename to spec/common/change.spec.ts index 0ddabdfa5..258fc8463 100644 --- a/spec/common/core.spec.ts +++ b/spec/common/change.spec.ts @@ -21,7 +21,7 @@ // SOFTWARE. import { expect } from 'chai'; -import { Change } from '../../src/common/core'; +import * as change from '../../src/common/change'; describe('Change', () => { describe('applyFieldMask', () => { @@ -38,7 +38,7 @@ describe('Change', () => { const sparseBefore = { baz: 'qux' }; const fieldMask = 'baz'; expect( - Change.applyFieldMask(sparseBefore, after, fieldMask) + change.applyFieldMask(sparseBefore, after, fieldMask) ).to.deep.equal({ foo: 'bar', num: 2, @@ -54,7 +54,7 @@ describe('Change', () => { const sparseBefore = {}; const fieldMask = 'num,obj.a'; expect( - Change.applyFieldMask(sparseBefore, after, fieldMask) + change.applyFieldMask(sparseBefore, after, fieldMask) ).to.deep.equal({ foo: 'bar', obj: { @@ -72,7 +72,7 @@ describe('Change', () => { }; const fieldMask = 'num,obj.a'; expect( - Change.applyFieldMask(sparseBefore, after, fieldMask) + change.applyFieldMask(sparseBefore, after, fieldMask) ).to.deep.equal({ foo: 'bar', num: 3, @@ -86,11 +86,11 @@ describe('Change', () => { describe('fromJSON', () => { it('should create a Change object with a `before` and `after`', () => { - const created = Change.fromJSON({ + const created = change.Change.fromJSON({ before: { foo: 'bar' }, after: { foo: 'faz' }, }); - expect(created instanceof Change).to.equal(true); + expect(created instanceof change.Change).to.equal(true); expect(created.before).to.deep.equal({ foo: 'bar' }); expect(created.after).to.deep.equal({ foo: 'faz' }); }); @@ -98,10 +98,9 @@ describe('Change', () => { it('should apply the customizer function to `before` and `after`', () => { function customizer(input: any) { input.another = 'value'; - // _.set(input, 'another', 'value'); return input as T; } - const created = Change.fromJSON( + const created = change.Change.fromJSON( { before: { foo: 'bar' }, after: { foo: 'faz' }, diff --git a/spec/v2/providers/database.spec.ts b/spec/v2/providers/database.spec.ts index 58d4285ab..f700c58d3 100644 --- a/spec/v2/providers/database.spec.ts +++ b/spec/v2/providers/database.spec.ts @@ -363,9 +363,9 @@ describe('database', () => { }); }); - describe('onRefWritten', () => { + describe('onValueWritten', () => { it('should create a function with a reference', () => { - const func = database.onRefWritten('/foo/{bar}/', (event) => 2); + const func = database.onValueWritten('/foo/{bar}/', (event) => 2); expect(func.__endpoint).to.deep.equal({ platform: 'gcfv2', @@ -383,7 +383,7 @@ describe('database', () => { }); it('should create a function with opts', () => { - const func = database.onRefWritten( + const func = database.onValueWritten( { ref: '/foo/{path=**}/{bar}/', instance: 'my-instance', @@ -414,9 +414,9 @@ describe('database', () => { }); }); - describe('onRefCreated', () => { + describe('onValueCreated', () => { it('should create a function with a reference', () => { - const func = database.onRefCreated('/foo/{bar}/', (event) => 2); + const func = database.onValueCreated('/foo/{bar}/', (event) => 2); expect(func.__endpoint).to.deep.equal({ platform: 'gcfv2', @@ -434,7 +434,7 @@ describe('database', () => { }); it('should create a function with opts', () => { - const func = database.onRefCreated( + const func = database.onValueCreated( { ref: '/foo/{path=**}/{bar}/', instance: 'my-instance', @@ -465,9 +465,9 @@ describe('database', () => { }); }); - describe('onRefUpdated', () => { + describe('onValueUpdated', () => { it('should create a function with a reference', () => { - const func = database.onRefUpdated('/foo/{bar}/', (event) => 2); + const func = database.onValueUpdated('/foo/{bar}/', (event) => 2); expect(func.__endpoint).to.deep.equal({ platform: 'gcfv2', @@ -485,7 +485,7 @@ describe('database', () => { }); it('should create a function with opts', () => { - const func = database.onRefUpdated( + const func = database.onValueUpdated( { ref: '/foo/{path=**}/{bar}/', instance: 'my-instance', @@ -516,9 +516,9 @@ describe('database', () => { }); }); - describe('onRefDeleted', () => { + describe('onValueDeleted', () => { it('should create a function with a reference', () => { - const func = database.onRefDeleted('/foo/{bar}/', (event) => 2); + const func = database.onValueDeleted('/foo/{bar}/', (event) => 2); expect(func.__endpoint).to.deep.equal({ platform: 'gcfv2', @@ -536,7 +536,7 @@ describe('database', () => { }); it('should create a function with opts', () => { - const func = database.onRefDeleted( + const func = database.onValueDeleted( { ref: '/foo/{path=**}/{bar}/', instance: 'my-instance', diff --git a/src/common/core.ts b/src/common/change.ts similarity index 77% rename from src/common/core.ts rename to src/common/change.ts index de9c3f805..c0180e0a0 100644 --- a/src/common/core.ts +++ b/src/common/change.ts @@ -20,18 +20,6 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -/** - * The Functions interface for events that change state, such as - * Realtime Database or Cloud Firestore `onWrite` and `onUpdate`. - * - * For more information about the format used to construct `Change` objects, see - * {@link ChangeJson} below. - * - */ -export class Change { - constructor(public before: T, public after: T) {} -} - /** * `ChangeJson` is the JSON format used to construct a Change object. */ @@ -52,13 +40,49 @@ export interface ChangeJson { fieldMask?: string; } -export namespace Change { +/** @hidden */ +export function applyFieldMask( + sparseBefore: any, + after: any, + fieldMask: string +) { + const before = { ...after }; + const masks = fieldMask.split(','); + + for (const mask of masks) { + const parts = mask.split('.'); + const head = parts[0]; + const tail = parts.slice(1).join('.'); + if (parts.length > 1) { + before[head] = applyFieldMask(sparseBefore?.[head], after[head], tail); + continue; + } + const val = sparseBefore?.[head]; + if (typeof val === 'undefined') { + delete before[mask]; + } else { + before[mask] = val; + } + } + + return before; +} + +/** + * The Functions interface for events that change state, such as + * Realtime Database or Cloud Firestore `onWrite` and `onUpdate`. + * + * For more information about the format used to construct `Change` objects, see + * {@link ChangeJson} below. + * + */ +export class Change { /** * @hidden * Factory method for creating a Change from a `before` object and an `after` * object. */ - export function fromObjects(before: T, after: T) { + static fromObjects(before: T, after: T) { return new Change(before, after); } @@ -67,7 +91,7 @@ export namespace Change { * Factory method for creating a Change from a JSON and an optional customizer * function to be applied to both the `before` and the `after` fields. */ - export function fromJSON( + static fromJSON( json: ChangeJson, customizer: (x: any) => T = (x) => x as T ): Change { @@ -81,32 +105,5 @@ export namespace Change { customizer(json.after || {}) ); } - - /** @hidden */ - export function applyFieldMask( - sparseBefore: any, - after: any, - fieldMask: string - ) { - const before = { ...after }; - const masks = fieldMask.split(','); - - for (const mask of masks) { - const parts = mask.split('.'); - const head = parts[0]; - const tail = parts.slice(1).join('.'); - if (parts.length > 1) { - before[head] = applyFieldMask(sparseBefore?.[head], after[head], tail); - continue; - } - const val = sparseBefore?.[head]; - if (typeof val === 'undefined') { - delete before[mask]; - } else { - before[mask] = val; - } - } - - return before; - } + constructor(public before: T, public after: T) {} } diff --git a/src/providers/database.ts b/src/providers/database.ts index 1a871eb35..39feaf0e8 100644 --- a/src/providers/database.ts +++ b/src/providers/database.ts @@ -27,14 +27,14 @@ import { EventContext, makeCloudFunction, } from '../cloud-functions'; -import { Change } from '../common/core'; +import { Change } from '../common/change'; import { DataSnapshot } from '../common/providers/database'; import { firebaseConfig } from '../config'; import { DeploymentOptions } from '../function-configuration'; import { normalizePath } from '../utilities/path'; import { applyChange } from '../utils'; -export { DataSnapshot, Change }; +export { DataSnapshot }; /** @hidden */ export const provider = 'google.firebase.database'; diff --git a/src/providers/firestore.ts b/src/providers/firestore.ts index b0d1f1a95..73d8eed13 100644 --- a/src/providers/firestore.ts +++ b/src/providers/firestore.ts @@ -31,7 +31,7 @@ import { EventContext, makeCloudFunction, } from '../cloud-functions'; -import { Change } from '../common/core'; +import { Change } from '../common/change'; import { dateToTimestampProto } from '../encoder'; import { DeploymentOptions } from '../function-configuration'; import * as logger from '../logger'; diff --git a/src/v2/core.ts b/src/v2/core.ts index 5e71d2458..7bd88fc8d 100644 --- a/src/v2/core.ts +++ b/src/v2/core.ts @@ -25,8 +25,11 @@ * @packageDocumentation */ +import { Change } from '../common/change'; import { ManifestEndpoint } from '../runtime/manifest'; +export { Change }; + /** @internal */ export interface TriggerAnnotation { platform?: string; diff --git a/src/v2/providers/database.ts b/src/v2/providers/database.ts index a61798897..1ab09f666 100644 --- a/src/v2/providers/database.ts +++ b/src/v2/providers/database.ts @@ -21,7 +21,7 @@ // SOFTWARE. import { apps } from '../../apps'; -import { Change } from '../../common/core'; +import { Change } from '../../common/change'; import { DataSnapshot } from '../../common/providers/database'; import { ManifestEndpoint } from '../../runtime/manifest'; import { normalizePath } from '../../utilities/path'; @@ -30,7 +30,7 @@ import { applyChange } from '../../utils'; import { CloudEvent, CloudFunction } from '../core'; import * as options from '../options'; -export { DataSnapshot, Change }; +export { DataSnapshot }; /** @internal */ export const writtenEventType = 'google.firebase.database.ref.v1.written'; @@ -84,12 +84,106 @@ export interface ReferenceOptions extends options.EventHandlerOptions { * Examples: '/foo/bar', '/foo/{bar}' */ ref: string; + /** * Specify the handler to trigger on a database instance(s). * If present, this value can either be a single instance or a pattern. * Examples: 'my-instance-1', '{instance}' */ instance?: string; + + /** + * Region where functions should be deployed. + */ + region?: options.SupportedRegion | string; + + /** + * Amount of memory to allocate to a function. + * A value of null restores the defaults of 256MB. + */ + memory?: options.MemoryOption | null; + + /** + * Timeout for the function in sections, possible values are 0 to 540. + * HTTPS functions can specify a higher timeout. + * A value of null restores the default of 60s + * The minimum timeout for a gen 2 function is 1s. The maximum timeout for a + * function depends on the type of function: Event handling functions have a + * maximum timeout of 540s (9 minutes). HTTPS and callable functions have a + * maximum timeout of 36,00s (1 hour). Task queue functions have a maximum + * timeout of 1,800s (30 minutes) + */ + timeoutSeconds?: number | null; + + /** + * Min number of actual instances to be running at a given time. + * Instances will be billed for memory allocation and 10% of CPU allocation + * while idle. + * A value of null restores the default min instances. + */ + minInstances?: number | null; + + /** + * Max number of instances to be running in parallel. + * A value of null restores the default max instances. + */ + maxInstances?: number | null; + + /** + * Number of requests a function can serve at once. + * Can only be applied to functions running on Cloud Functions v2. + * A value of null restores the default concurrency (80 when CPU >= 1, 1 otherwise). + * Concurrency cannot be set to any value other than 1 if `cpu` is less than 1. + * The maximum value for concurrency is 1,000. + */ + concurrency?: number | null; + + /** + * Fractional number of CPUs to allocate to a function. + * Defaults to 1 for functions with <= 2GB RAM and increases for larger memory sizes. + * This is different from the defaults when using the gcloud utility and is different from + * the fixed amount assigned in Google Cloud Functions generation 1. + * To revert to the CPU amounts used in gcloud or in Cloud Functions generation 1, set this + * to the value "gcf_gen1" + */ + cpu?: number | 'gcf_gen1'; + + /** + * Connect cloud function to specified VPC connector. + * A value of null removes the VPC connector + */ + vpcConnector?: string | null; + + /** + * Egress settings for VPC connector. + * A value of null turns off VPC connector egress settings + */ + vpcConnectorEgressSettings?: options.VpcEgressSetting | null; + + /** + * Specific service account for the function to run as. + * A value of null restores the default service account. + */ + serviceAccount?: string | null; + + /** + * Ingress settings which control where this function can be called from. + * A value of null turns off ingress settings. + */ + ingressSettings?: options.IngressSetting | null; + + /** + * User labels to set on the function. + */ + labels?: Record; + + /* + * Secrets to bind to a function. + */ + secrets?: string[]; + + /** Whether failed executions should be delivered again. */ + retry?: boolean; } /** @@ -98,7 +192,7 @@ export interface ReferenceOptions extends options.EventHandlerOptions { * @param reference - The database reference path to trigger on. * @param handler - Event handler which is run every time a Realtime Database create, update, or delete occurs. */ -export function onRefWritten( +export function onValueWritten( reference: string, handler: (event: DatabaseEvent>) => any | Promise ): CloudFunction>>; @@ -109,7 +203,7 @@ export function onRefWritten( * @param opts - Options that can be set on an individual event-handling function. * @param handler - Event handler which is run every time a Realtime Database create, update, or delete occurs. */ -export function onRefWritten( +export function onValueWritten( opts: ReferenceOptions, handler: (event: DatabaseEvent>) => any | Promise ): CloudFunction>>; @@ -120,7 +214,7 @@ export function onRefWritten( * @param referenceOrOpts - Options or a string reference. * @param handler - Event handler which is run every time a Realtime Database create, update, or delete occurs. */ -export function onRefWritten( +export function onValueWritten( referenceOrOpts: string | ReferenceOptions, handler: (event: DatabaseEvent>) => any | Promise ): CloudFunction>> { @@ -133,7 +227,7 @@ export function onRefWritten( * @param reference - The database reference path to trigger on. * @param handler - Event handler which is run every time a Realtime Database create occurs. */ -export function onRefCreated( +export function onValueCreated( reference: string, handler: (event: DatabaseEvent) => any | Promise ): CloudFunction>; @@ -144,7 +238,7 @@ export function onRefCreated( * @param opts - Options that can be set on an individual event-handling function. * @param handler - Event handler which is run every time a Realtime Database create occurs. */ -export function onRefCreated( +export function onValueCreated( opts: ReferenceOptions, handler: (event: DatabaseEvent) => any | Promise ): CloudFunction>; @@ -155,7 +249,7 @@ export function onRefCreated( * @param referenceOrOpts - Options or a string reference. * @param handler - Event handler which is run every time a Realtime Database create occurs. */ -export function onRefCreated( +export function onValueCreated( referenceOrOpts: string | ReferenceOptions, handler: (event: DatabaseEvent) => any | Promise ): CloudFunction> { @@ -168,7 +262,7 @@ export function onRefCreated( * @param reference - The database reference path to trigger on. * @param handler - Event handler which is run every time a Realtime Database update occurs. */ -export function onRefUpdated( +export function onValueUpdated( reference: string, handler: (event: DatabaseEvent>) => any | Promise ): CloudFunction>>; @@ -179,7 +273,7 @@ export function onRefUpdated( * @param opts - Options that can be set on an individual event-handling function. * @param handler - Event handler which is run every time a Realtime Database update occurs. */ -export function onRefUpdated( +export function onValueUpdated( opts: ReferenceOptions, handler: (event: DatabaseEvent>) => any | Promise ): CloudFunction>>; @@ -190,7 +284,7 @@ export function onRefUpdated( * @param referenceOrOpts - Options or a string reference. * @param handler - Event handler which is run every time a Realtime Database update occurs. */ -export function onRefUpdated( +export function onValueUpdated( referenceOrOpts: string | ReferenceOptions, handler: (event: DatabaseEvent>) => any | Promise ): CloudFunction>> { @@ -203,7 +297,7 @@ export function onRefUpdated( * @param reference - The database reference path to trigger on. * @param handler - Event handler which is run every time a Realtime Database deletion occurs. */ -export function onRefDeleted( +export function onValueDeleted( reference: string, handler: (event: DatabaseEvent) => any | Promise ): CloudFunction>; @@ -214,7 +308,7 @@ export function onRefDeleted( * @param opts - Options that can be set on an individual event-handling function. * @param handler - Event handler which is run every time a Realtime Database deletion occurs. */ -export function onRefDeleted( +export function onValueDeleted( opts: ReferenceOptions, handler: (event: DatabaseEvent) => any | Promise ): CloudFunction>; @@ -225,7 +319,7 @@ export function onRefDeleted( * @param referenceOrOpts - Options or a string reference. * @param handler - Event handler which is run every time a Realtime Database deletion occurs. */ -export function onRefDeleted( +export function onValueDeleted( referenceOrOpts: string | ReferenceOptions, handler: (event: DatabaseEvent) => any | Promise ): CloudFunction> {