From 36a50cb4f0f6411a93643fbbf12c3b125f30363c Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Fri, 15 Sep 2023 10:22:02 -0400 Subject: [PATCH 1/6] Implement vector field type --- dev/src/field-value.ts | 47 ++++++++++++++++++++++ dev/src/serializer.ts | 50 ++++++++++++++++++++--- dev/system-test/firestore.ts | 11 ++++++ dev/test/document.ts | 77 ++++++++++++++++++++++++++++++++++++ 4 files changed, 179 insertions(+), 6 deletions(-) diff --git a/dev/src/field-value.ts b/dev/src/field-value.ts index eb054067c..531643558 100644 --- a/dev/src/field-value.ts +++ b/dev/src/field-value.ts @@ -30,6 +30,49 @@ import { import api = proto.google.firestore.v1; +export class VectorValue { + private readonly values: number[]; + constructor(values: number[] | undefined) { + this.values = values || []; + } + + public toArray(): number[] { + return this.values; + } + + /** + * @private + */ + toProto(serializer: Serializer): api.IValue { + return serializer.encodeVector({ + arrayValue: { + values: this.values.map(value => { + return { + doubleValue: value, + }; + }), + }, + }); + } + + /** + * @private + */ + static fromProto(valueArray: api.IValue): VectorValue { + const values = valueArray.arrayValue?.values?.map(v => { + return v.doubleValue!; + }); + return new VectorValue(values); + } + + /** + * @private + */ + isEqual(other: VectorValue): boolean { + return this.values === other.values; + } +} + /** * Sentinel values that can be used when writing documents with set(), create() * or update(). @@ -40,6 +83,10 @@ export class FieldValue implements firestore.FieldValue { /** @private */ constructor() {} + static vector(values?: number[]): VectorValue { + return new VectorValue(values); + } + /** * Returns a sentinel for use with update() or set() with {merge:true} to mark * a field for deletion. diff --git a/dev/src/serializer.ts b/dev/src/serializer.ts index dae07e8a3..1c56ec9b9 100644 --- a/dev/src/serializer.ts +++ b/dev/src/serializer.ts @@ -19,7 +19,7 @@ import {DocumentData} from '@google-cloud/firestore'; import * as proto from '../protos/firestore_v1_proto_api'; import {detectValueType} from './convert'; -import {DeleteTransform, FieldTransform} from './field-value'; +import {DeleteTransform, FieldTransform, VectorValue} from './field-value'; import {GeoPoint} from './geo-point'; import {DocumentReference, Firestore} from './index'; import {FieldPath, QualifiedResourcePath} from './path'; @@ -38,6 +38,10 @@ import api = proto.google.firestore.v1; */ const MAX_DEPTH = 20; +const RESERVED_MAP_KEY = '__type__'; +const RESERVED_MAP_KEY_VECTOR_VALUE = '__vector__'; +const RESERVED_VECTOR_MAP_VECTORS_KEY = 'value'; + /** * An interface for Firestore types that can be serialized to Protobuf. * @@ -168,6 +172,10 @@ export class Serializer { }; } + if (val instanceof VectorValue) { + return val.toProto(this); + } + if (isObject(val)) { const toProto = val['toProto']; if (typeof toProto === 'function') { @@ -217,6 +225,22 @@ export class Serializer { throw new Error(`Cannot encode value: ${val}`); } + /** + * @private + */ + encodeVector(vectorValue: api.IValue): api.IValue { + return { + mapValue: { + fields: { + [RESERVED_MAP_KEY]: { + stringValue: RESERVED_MAP_KEY_VECTOR_VALUE, + }, + [RESERVED_VECTOR_MAP_VECTORS_KEY]: vectorValue, + }, + }, + }; + } + /** * Decodes a single Firestore 'Value' Protobuf. * @@ -263,15 +287,27 @@ export class Serializer { return null; } case 'mapValue': { - const obj: DocumentData = {}; const fields = proto.mapValue!.fields; if (fields) { - for (const prop of Object.keys(fields)) { - obj[prop] = this.decodeValue(fields[prop]); + const props = Object.keys(fields); + if ( + props.indexOf(RESERVED_MAP_KEY) !== -1 && + this.decodeValue(fields[RESERVED_MAP_KEY]) === + RESERVED_MAP_KEY_VECTOR_VALUE + ) { + return VectorValue.fromProto( + fields[RESERVED_VECTOR_MAP_VECTORS_KEY] + ); + } else { + const obj: DocumentData = {}; + for (const prop of Object.keys(fields)) { + obj[prop] = this.decodeValue(fields[prop]); + } + return obj; } + } else { + return {}; } - - return obj; } case 'geoPointValue': { return GeoPoint.fromProto(proto.geoPointValue!); @@ -367,6 +403,8 @@ export function validateUserInput( 'If you want to ignore undefined values, enable `ignoreUndefinedProperties`.' ); } + } else if (value instanceof VectorValue) { + // OK } else if (value instanceof DeleteTransform) { if (inArray) { throw new Error( diff --git a/dev/system-test/firestore.ts b/dev/system-test/firestore.ts index 12a43e478..1ba0853be 100644 --- a/dev/system-test/firestore.ts +++ b/dev/system-test/firestore.ts @@ -1015,6 +1015,17 @@ describe('DocumentReference class', () => { return promise; }); + it.only('can write and read vector embeddings', async () => { + const ref = randomCol.doc(); + await ref.create({ + vectorEmpty: FieldValue.vector([1, 3]), + vector1: FieldValue.vector([1, 2, 3.99]), + }); + const snap1 = await ref.get(); + expect(snap1.get('vectorEmpty')).to.deep.equal(FieldValue.vector()); + // expect(snap1.get('vector1')).to.deep.equal(FieldValue.vector([1, 2, 3.99])); + }); + describe('watch', () => { const currentDeferred = new DeferredPromise(); diff --git a/dev/test/document.ts b/dev/test/document.ts index 07bf1effc..37d279990 100644 --- a/dev/test/document.ts +++ b/dev/test/document.ts @@ -470,6 +470,43 @@ describe('serialize document', () => { return ref.set({ref}); }); }); + + it('is able to translate FirestoreVector to internal representation with set', () => { + const overrides: ApiOverride = { + commit: request => { + requestEquals( + request, + set({ + document: document('documentId', 'embedding1', { + mapValue: { + fields: { + __type__: { + stringValue: '__vector__', + }, + value: { + arrayValue: { + values: [ + {doubleValue: 0}, + {doubleValue: 1}, + {doubleValue: 2}, + ], + }, + }, + }, + }, + }), + }) + ); + return response(writeResult(1)); + }, + }; + + return createInstance(overrides).then(firestore => { + return firestore.doc('collectionId/documentId').set({ + embedding1: FieldValue.vector([0, 1, 2]), + }); + }); + }); }); describe('deserialize document', () => { @@ -598,6 +635,46 @@ describe('deserialize document', () => { }); }); + it('deserializes FirestoreVector', () => { + const overrides: ApiOverride = { + batchGetDocuments: () => { + return stream( + found( + document('documentId', 'embedding', { + mapValue: { + fields: { + __type__: { + stringValue: '__vector__', + }, + value: { + arrayValue: { + values: [ + {doubleValue: -41.0}, + {doubleValue: 0}, + {doubleValue: 42}, + ], + }, + }, + }, + }, + }) + ) + ); + }, + }; + + return createInstance(overrides).then(firestore => { + return firestore + .doc('collectionId/documentId') + .get() + .then(res => { + expect(res.get('embedding')).to.deep.equal( + FieldValue.vector([-41.0, 0, 42]) + ); + }); + }); + }); + it("doesn't deserialize unsupported types", () => { const overrides: ApiOverride = { batchGetDocuments: () => { From 3551acb8c7dacf19047c544feaa4d6ac4df5192e Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Tue, 19 Sep 2023 16:58:42 -0400 Subject: [PATCH 2/6] test --- dev/system-test/firestore.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dev/system-test/firestore.ts b/dev/system-test/firestore.ts index d411fd055..835a3e1cd 100644 --- a/dev/system-test/firestore.ts +++ b/dev/system-test/firestore.ts @@ -1015,15 +1015,15 @@ describe('DocumentReference class', () => { return promise; }); - it.only('can write and read vector embeddings', async () => { + it('can write and read vector embeddings', async () => { const ref = randomCol.doc(); await ref.create({ - vectorEmpty: FieldValue.vector([1, 3]), + vectorEmpty: FieldValue.vector(), vector1: FieldValue.vector([1, 2, 3.99]), }); const snap1 = await ref.get(); expect(snap1.get('vectorEmpty')).to.deep.equal(FieldValue.vector()); - // expect(snap1.get('vector1')).to.deep.equal(FieldValue.vector([1, 2, 3.99])); + expect(snap1.get('vector1')).to.deep.equal(FieldValue.vector([1, 2, 3.99])); }); describe('watch', () => { From 6042c4dfcf71c4fe672d509aece2a517678f5d43 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Wed, 29 Nov 2023 11:42:24 -0500 Subject: [PATCH 3/6] Added extra logs and vector type test --- dev/src/index.ts | 1 + dev/system-test/firestore.ts | 21 ++++++++++++++++++++- dev/test/types.ts | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/dev/src/index.ts b/dev/src/index.ts index 47fe60eb0..91ce7a3ee 100644 --- a/dev/src/index.ts +++ b/dev/src/index.ts @@ -625,6 +625,7 @@ export class Firestore implements firestore.Firestore { 'Initialized Firestore GAPIC Client (useFallback: %s)', useFallback ); + logger("client", "init", `${JSON.stringify((client as any)._opts.servicePath)}`); return client; }, /* clientDestructor= */ client => client.close() diff --git a/dev/system-test/firestore.ts b/dev/system-test/firestore.ts index 007041845..1f6870004 100644 --- a/dev/system-test/firestore.ts +++ b/dev/system-test/firestore.ts @@ -62,6 +62,8 @@ use(chaiAsPromised); const version = require('../../package.json').version; +setLogFunction(console.log); + class DeferredPromise { resolve: Function; reject: Function; @@ -104,6 +106,8 @@ function getTestRoot(settings: Settings = {}): CollectionReference { internalSettings.databaseId = process.env.FIRESTORE_NAMED_DATABASE; } + settings.host = "test-firestore.sandbox.googleapis.com"; + const firestore = new Firestore({ ...internalSettings, ...settings, // caller settings take precedent over internal settings @@ -1018,15 +1022,30 @@ describe('DocumentReference class', () => { return promise; }); - it('can write and read vector embeddings', async () => { + it.only('can write and read vector embeddings', async () => { const ref = randomCol.doc(); await ref.create({ vectorEmpty: FieldValue.vector(), vector1: FieldValue.vector([1, 2, 3.99]), }); + await ref.set({ + vectorEmpty: FieldValue.vector(), + vector1: FieldValue.vector([1, 2, 3.99]), + vector2: FieldValue.vector([0, 0, 0]), + }) + await ref.update({ + vector3: FieldValue.vector([-1, -200, -999]), + }) + // ref.onSnapshot(snap1 => { + // expect(snap1.get('vectorEmpty')).to.deep.equal(FieldValue.vector()); + // expect(snap1.get('vector1')).to.deep.equal(FieldValue.vector([1, 2, 3.99])); + // }); + const snap1 = await ref.get(); expect(snap1.get('vectorEmpty')).to.deep.equal(FieldValue.vector()); expect(snap1.get('vector1')).to.deep.equal(FieldValue.vector([1, 2, 3.99])); + expect(snap1.get('vector2')).to.deep.equal(FieldValue.vector([0, 0, 0])); + expect(snap1.get('vector3')).to.deep.equal(FieldValue.vector([-1, -200, -999])); }); describe('watch', () => { diff --git a/dev/test/types.ts b/dev/test/types.ts index 0d150c6c8..8db0d132a 100644 --- a/dev/test/types.ts +++ b/dev/test/types.ts @@ -120,7 +120,7 @@ describe('FirestoreTypeConverter', () => { await newDocRef.set({stringProperty: 'foo', numberProperty: 42}); await newDocRef.update({a: 'newFoo', b: 43}); const snapshot = await newDocRef.get(); - const data: MyModelType = snapshot.data()!; + const data = snapshot.data()!; expect(data.stringProperty).to.equal('newFoo'); expect(data.numberProperty).to.equal(43); } From 764800b1af74fade448ed6da56acc247e5cad430 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Tue, 5 Dec 2023 11:29:36 -0500 Subject: [PATCH 4/6] Add vector watch test --- dev/system-test/firestore.ts | 99 +++++++++++++++++++++++++++++++++--- 1 file changed, 91 insertions(+), 8 deletions(-) diff --git a/dev/system-test/firestore.ts b/dev/system-test/firestore.ts index 1f6870004..96fff599e 100644 --- a/dev/system-test/firestore.ts +++ b/dev/system-test/firestore.ts @@ -106,7 +106,7 @@ function getTestRoot(settings: Settings = {}): CollectionReference { internalSettings.databaseId = process.env.FIRESTORE_NAMED_DATABASE; } - settings.host = "test-firestore.sandbox.googleapis.com"; + settings.host = 'test-firestore.sandbox.googleapis.com'; const firestore = new Firestore({ ...internalSettings, @@ -1032,20 +1032,18 @@ describe('DocumentReference class', () => { vectorEmpty: FieldValue.vector(), vector1: FieldValue.vector([1, 2, 3.99]), vector2: FieldValue.vector([0, 0, 0]), - }) + }); await ref.update({ vector3: FieldValue.vector([-1, -200, -999]), - }) - // ref.onSnapshot(snap1 => { - // expect(snap1.get('vectorEmpty')).to.deep.equal(FieldValue.vector()); - // expect(snap1.get('vector1')).to.deep.equal(FieldValue.vector([1, 2, 3.99])); - // }); + }); const snap1 = await ref.get(); expect(snap1.get('vectorEmpty')).to.deep.equal(FieldValue.vector()); expect(snap1.get('vector1')).to.deep.equal(FieldValue.vector([1, 2, 3.99])); expect(snap1.get('vector2')).to.deep.equal(FieldValue.vector([0, 0, 0])); - expect(snap1.get('vector3')).to.deep.equal(FieldValue.vector([-1, -200, -999])); + expect(snap1.get('vector3')).to.deep.equal( + FieldValue.vector([-1, -200, -999]) + ); }); describe('watch', () => { @@ -1341,6 +1339,91 @@ describe('DocumentReference class', () => { const result2 = await ref2.get(); expect(result2.data()).to.deep.equal([1, 2, 3]); }); + + it.only('can listen to documents with vectors', async () => { + const ref = randomCol.doc(); + const initialDeferred = new Deferred(); + const createDeferred = new Deferred(); + const setDeferred = new Deferred(); + const updateDeferred = new Deferred(); + const deleteDeferred = new Deferred(); + + const expected = [ + initialDeferred, + createDeferred, + setDeferred, + updateDeferred, + deleteDeferred, + ]; + let idx = 0; + let document: DocumentSnapshot | null = null; + + const unlisten = randomCol + .where('purpose', '==', 'vector tests') + .onSnapshot(snap => { + expected[idx].resolve(); + idx += 1; + if (snap.docs.length > 0) { + document = snap.docs[0]; + } else { + document = null; + } + }); + + await initialDeferred.promise; + expect(document).to.be.null; + + await ref.create({ + purpose: 'vector tests', + vectorEmpty: FieldValue.vector(), + vector1: FieldValue.vector([1, 2, 3.99]), + }); + + await createDeferred.promise; + expect(document).to.be.not.null; + expect(document!.get('vectorEmpty')).to.deep.equal(FieldValue.vector()); + expect(document!.get('vector1')).to.deep.equal( + FieldValue.vector([1, 2, 3.99]) + ); + + await ref.set({ + purpose: 'vector tests', + vectorEmpty: FieldValue.vector(), + vector1: FieldValue.vector([1, 2, 3.99]), + vector2: FieldValue.vector([0, 0, 0]), + }); + await setDeferred.promise; + expect(document).to.be.not.null; + expect(document!.get('vectorEmpty')).to.deep.equal(FieldValue.vector()); + expect(document!.get('vector1')).to.deep.equal( + FieldValue.vector([1, 2, 3.99]) + ); + expect(document!.get('vector2')).to.deep.equal( + FieldValue.vector([0, 0, 0]) + ); + + await ref.update({ + vector3: FieldValue.vector([-1, -200, -999]), + }); + await updateDeferred.promise; + expect(document).to.be.not.null; + expect(document!.get('vectorEmpty')).to.deep.equal(FieldValue.vector()); + expect(document!.get('vector1')).to.deep.equal( + FieldValue.vector([1, 2, 3.99]) + ); + expect(document!.get('vector2')).to.deep.equal( + FieldValue.vector([0, 0, 0]) + ); + expect(document!.get('vector3')).to.deep.equal( + FieldValue.vector([-1, -200, -999]) + ); + + await ref.delete(); + await deleteDeferred.promise; + expect(document).to.be.null; + + unlisten(); + }); }); describe('Query class', () => { From 93ae1d7ebeee228ee57525d06868e2f7fbfdc9ee Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Wed, 17 Jan 2024 11:38:36 -0500 Subject: [PATCH 5/6] Add vectorvalue to dts. --- dev/src/field-value.ts | 2 +- dev/src/index.ts | 6 +++++- types/firestore.d.ts | 22 ++++++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/dev/src/field-value.ts b/dev/src/field-value.ts index 531643558..b1cd7d35d 100644 --- a/dev/src/field-value.ts +++ b/dev/src/field-value.ts @@ -30,7 +30,7 @@ import { import api = proto.google.firestore.v1; -export class VectorValue { +export class VectorValue implements firestore.VectorValue { private readonly values: number[]; constructor(values: number[] | undefined) { this.values = values || []; diff --git a/dev/src/index.ts b/dev/src/index.ts index 91ce7a3ee..9dc217d98 100644 --- a/dev/src/index.ts +++ b/dev/src/index.ts @@ -625,7 +625,11 @@ export class Firestore implements firestore.Firestore { 'Initialized Firestore GAPIC Client (useFallback: %s)', useFallback ); - logger("client", "init", `${JSON.stringify((client as any)._opts.servicePath)}`); + logger( + 'client', + 'init', + `${JSON.stringify((client as any)._opts.servicePath)}` + ); return client; }, /* clientDestructor= */ client => client.close() diff --git a/types/firestore.d.ts b/types/firestore.d.ts index 18d240a44..942be0198 100644 --- a/types/firestore.d.ts +++ b/types/firestore.d.ts @@ -2357,6 +2357,23 @@ declare namespace FirebaseFirestore { ): boolean; } + /** + * Represent a vector type in Firestore documents. + */ + export class VectorValue { + private constructor(values: number[] | undefined); + + /** + * Returns the raw number array form of the vector. + */ + toArray(): number[]; + + /** + * Returns true if the two `VectorValue` has the same raw number arrays, returns false otherwise. + */ + isEqual(other: VectorValue): boolean; + } + /** * Sentinel values that can be used when writing document fields with set(), * create() or update(). @@ -2427,6 +2444,11 @@ declare namespace FirebaseFirestore { */ static arrayRemove(...elements: any[]): FieldValue; + /** + * @return A new `VectorValue` constructed with the given array of number. + */ + static vector(values?: number[]): VectorValue; + /** * Returns true if this `FieldValue` is equal to the provided one. * From 2f1b9646b7b45668e4b2ff837764ada4d2f9e56c Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Tue, 23 Jan 2024 11:03:44 -0500 Subject: [PATCH 6/6] Address Mark's comments. --- dev/src/field-value.ts | 19 ++++++------------- dev/src/index.ts | 5 ----- dev/src/serializer.ts | 19 +++++++++++++------ dev/system-test/firestore.ts | 4 ---- types/firestore.d.ts | 4 ++-- 5 files changed, 21 insertions(+), 30 deletions(-) diff --git a/dev/src/field-value.ts b/dev/src/field-value.ts index b1cd7d35d..dc1960f99 100644 --- a/dev/src/field-value.ts +++ b/dev/src/field-value.ts @@ -31,28 +31,21 @@ import { import api = proto.google.firestore.v1; export class VectorValue implements firestore.VectorValue { - private readonly values: number[]; + private readonly _values: number[]; constructor(values: number[] | undefined) { - this.values = values || []; + // Making a copy of the parameter. + this._values = (values || []).map(n => n); } public toArray(): number[] { - return this.values; + return this._values.map(n => n); } /** * @private */ toProto(serializer: Serializer): api.IValue { - return serializer.encodeVector({ - arrayValue: { - values: this.values.map(value => { - return { - doubleValue: value, - }; - }), - }, - }); + return serializer.encodeVector(this._values); } /** @@ -69,7 +62,7 @@ export class VectorValue implements firestore.VectorValue { * @private */ isEqual(other: VectorValue): boolean { - return this.values === other.values; + return this._values === other._values; } } diff --git a/dev/src/index.ts b/dev/src/index.ts index 9dc217d98..47fe60eb0 100644 --- a/dev/src/index.ts +++ b/dev/src/index.ts @@ -625,11 +625,6 @@ export class Firestore implements firestore.Firestore { 'Initialized Firestore GAPIC Client (useFallback: %s)', useFallback ); - logger( - 'client', - 'init', - `${JSON.stringify((client as any)._opts.servicePath)}` - ); return client; }, /* clientDestructor= */ client => client.close() diff --git a/dev/src/serializer.ts b/dev/src/serializer.ts index 1c56ec9b9..bb172ff07 100644 --- a/dev/src/serializer.ts +++ b/dev/src/serializer.ts @@ -40,7 +40,7 @@ const MAX_DEPTH = 20; const RESERVED_MAP_KEY = '__type__'; const RESERVED_MAP_KEY_VECTOR_VALUE = '__vector__'; -const RESERVED_VECTOR_MAP_VECTORS_KEY = 'value'; +const VECTOR_MAP_VECTORS_KEY = 'value'; /** * An interface for Firestore types that can be serialized to Protobuf. @@ -228,14 +228,23 @@ export class Serializer { /** * @private */ - encodeVector(vectorValue: api.IValue): api.IValue { + encodeVector(rawVector: number[]): api.IValue { + // A Firestore Vector is a map with reserved key/value pairs. return { mapValue: { fields: { [RESERVED_MAP_KEY]: { stringValue: RESERVED_MAP_KEY_VECTOR_VALUE, }, - [RESERVED_VECTOR_MAP_VECTORS_KEY]: vectorValue, + [VECTOR_MAP_VECTORS_KEY]: { + arrayValue: { + values: rawVector.map(value => { + return { + doubleValue: value, + }; + }), + }, + }, }, }, }; @@ -295,9 +304,7 @@ export class Serializer { this.decodeValue(fields[RESERVED_MAP_KEY]) === RESERVED_MAP_KEY_VECTOR_VALUE ) { - return VectorValue.fromProto( - fields[RESERVED_VECTOR_MAP_VECTORS_KEY] - ); + return VectorValue.fromProto(fields[VECTOR_MAP_VECTORS_KEY]); } else { const obj: DocumentData = {}; for (const prop of Object.keys(fields)) { diff --git a/dev/system-test/firestore.ts b/dev/system-test/firestore.ts index 96fff599e..ee30d0311 100644 --- a/dev/system-test/firestore.ts +++ b/dev/system-test/firestore.ts @@ -62,8 +62,6 @@ use(chaiAsPromised); const version = require('../../package.json').version; -setLogFunction(console.log); - class DeferredPromise { resolve: Function; reject: Function; @@ -106,8 +104,6 @@ function getTestRoot(settings: Settings = {}): CollectionReference { internalSettings.databaseId = process.env.FIRESTORE_NAMED_DATABASE; } - settings.host = 'test-firestore.sandbox.googleapis.com'; - const firestore = new Firestore({ ...internalSettings, ...settings, // caller settings take precedent over internal settings diff --git a/types/firestore.d.ts b/types/firestore.d.ts index 942be0198..1d6679c2c 100644 --- a/types/firestore.d.ts +++ b/types/firestore.d.ts @@ -2364,7 +2364,7 @@ declare namespace FirebaseFirestore { private constructor(values: number[] | undefined); /** - * Returns the raw number array form of the vector. + * Returns a copy of the raw number array form of the vector. */ toArray(): number[]; @@ -2445,7 +2445,7 @@ declare namespace FirebaseFirestore { static arrayRemove(...elements: any[]): FieldValue; /** - * @return A new `VectorValue` constructed with the given array of number. + * @return A new `VectorValue` constructed with a copy of the given array of number. */ static vector(values?: number[]): VectorValue;