diff --git a/.changeset/nice-eyes-tan.md b/.changeset/nice-eyes-tan.md new file mode 100644 index 00000000000..e2a25bdbc63 --- /dev/null +++ b/.changeset/nice-eyes-tan.md @@ -0,0 +1,6 @@ +--- +"@firebase/firestore": minor +"firebase": minor +--- + +Add support for reading and writing Firestore vectors. diff --git a/common/api-review/firestore-lite.api.md b/common/api-review/firestore-lite.api.md index 440fa488c1c..4a9ef4c0171 100644 --- a/common/api-review/firestore-lite.api.md +++ b/common/api-review/firestore-lite.api.md @@ -460,6 +460,16 @@ export function updateDoc(refere // @public export function updateDoc(reference: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): Promise; +// @public +export function vector(values?: number[]): VectorValue; + +// @public +export class VectorValue { + /* Excluded from this release type: __constructor */ + isEqual(other: VectorValue): boolean; + toArray(): number[]; +} + // @public export function where(fieldPath: string | FieldPath, opStr: WhereFilterOp, value: unknown): QueryFieldFilterConstraint; diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index f79ef52442e..34b56b97f21 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -745,6 +745,16 @@ export function updateDoc(refere // @public export function updateDoc(reference: DocumentReference, field: string | FieldPath, value: unknown, ...moreFieldsAndValues: unknown[]): Promise; +// @public +export function vector(values?: number[]): VectorValue; + +// @public +export class VectorValue { + /* Excluded from this release type: __constructor */ + isEqual(other: VectorValue): boolean; + toArray(): number[]; +} + // @public export function waitForPendingWrites(firestore: Firestore): Promise; diff --git a/docs-devsite/_toc.yaml b/docs-devsite/_toc.yaml deleted file mode 100644 index 7412d572013..00000000000 --- a/docs-devsite/_toc.yaml +++ /dev/null @@ -1,555 +0,0 @@ -toc: - - title: firebase - path: /docs/reference/js/index - - title: analytics - path: /docs/reference/js/analytics.md - section: - - title: Analytics - path: /docs/reference/js/analytics.analytics.md - - title: AnalyticsCallOptions - path: /docs/reference/js/analytics.analyticscalloptions.md - - title: AnalyticsSettings - path: /docs/reference/js/analytics.analyticssettings.md - - title: ConsentSettings - path: /docs/reference/js/analytics.consentsettings.md - - title: ControlParams - path: /docs/reference/js/analytics.controlparams.md - - title: CustomParams - path: /docs/reference/js/analytics.customparams.md - - title: EventParams - path: /docs/reference/js/analytics.eventparams.md - - title: GtagConfigParams - path: /docs/reference/js/analytics.gtagconfigparams.md - - title: Item - path: /docs/reference/js/analytics.item.md - - title: Promotion - path: /docs/reference/js/analytics.promotion.md - - title: SettingsOptions - path: /docs/reference/js/analytics.settingsoptions.md - - title: app - path: /docs/reference/js/app.md - section: - - title: FirebaseApp - path: /docs/reference/js/app.firebaseapp.md - - title: FirebaseAppSettings - path: /docs/reference/js/app.firebaseappsettings.md - - title: FirebaseOptions - path: /docs/reference/js/app.firebaseoptions.md - - title: FirebaseServerApp - path: /docs/reference/js/app.firebaseserverapp.md - - title: FirebaseServerAppSettings - path: /docs/reference/js/app.firebaseserverappsettings.md - - title: app-check - path: /docs/reference/js/app-check.md - section: - - title: AppCheck - path: /docs/reference/js/app-check.appcheck.md - - title: AppCheckOptions - path: /docs/reference/js/app-check.appcheckoptions.md - - title: AppCheckToken - path: /docs/reference/js/app-check.appchecktoken.md - - title: AppCheckTokenResult - path: /docs/reference/js/app-check.appchecktokenresult.md - - title: CustomProvider - path: /docs/reference/js/app-check.customprovider.md - - title: CustomProviderOptions - path: /docs/reference/js/app-check.customprovideroptions.md - - title: ReCaptchaEnterpriseProvider - path: /docs/reference/js/app-check.recaptchaenterpriseprovider.md - - title: ReCaptchaV3Provider - path: /docs/reference/js/app-check.recaptchav3provider.md - - title: auth - path: /docs/reference/js/auth.md - section: - - title: ActionCodeInfo - path: /docs/reference/js/auth.actioncodeinfo.md - - title: ActionCodeSettings - path: /docs/reference/js/auth.actioncodesettings.md - - title: ActionCodeURL - path: /docs/reference/js/auth.actioncodeurl.md - - title: AdditionalUserInfo - path: /docs/reference/js/auth.additionaluserinfo.md - - title: ApplicationVerifier - path: /docs/reference/js/auth.applicationverifier.md - - title: Auth - path: /docs/reference/js/auth.auth.md - - title: AuthCredential - path: /docs/reference/js/auth.authcredential.md - - title: AuthError - path: /docs/reference/js/auth.autherror.md - - title: AuthErrorMap - path: /docs/reference/js/auth.autherrormap.md - - title: AuthProvider - path: /docs/reference/js/auth.authprovider.md - - title: AuthSettings - path: /docs/reference/js/auth.authsettings.md - - title: Config - path: /docs/reference/js/auth.config.md - - title: ConfirmationResult - path: /docs/reference/js/auth.confirmationresult.md - - title: Dependencies - path: /docs/reference/js/auth.dependencies.md - - title: EmailAuthCredential - path: /docs/reference/js/auth.emailauthcredential.md - - title: EmailAuthProvider - path: /docs/reference/js/auth.emailauthprovider.md - - title: EmulatorConfig - path: /docs/reference/js/auth.emulatorconfig.md - - title: FacebookAuthProvider - path: /docs/reference/js/auth.facebookauthprovider.md - - title: GithubAuthProvider - path: /docs/reference/js/auth.githubauthprovider.md - - title: GoogleAuthProvider - path: /docs/reference/js/auth.googleauthprovider.md - - title: IdTokenResult - path: /docs/reference/js/auth.idtokenresult.md - - title: MultiFactorAssertion - path: /docs/reference/js/auth.multifactorassertion.md - - title: MultiFactorError - path: /docs/reference/js/auth.multifactorerror.md - - title: MultiFactorInfo - path: /docs/reference/js/auth.multifactorinfo.md - - title: MultiFactorResolver - path: /docs/reference/js/auth.multifactorresolver.md - - title: MultiFactorSession - path: /docs/reference/js/auth.multifactorsession.md - - title: MultiFactorUser - path: /docs/reference/js/auth.multifactoruser.md - - title: OAuthCredential - path: /docs/reference/js/auth.oauthcredential.md - - title: OAuthCredentialOptions - path: /docs/reference/js/auth.oauthcredentialoptions.md - - title: OAuthProvider - path: /docs/reference/js/auth.oauthprovider.md - - title: ParsedToken - path: /docs/reference/js/auth.parsedtoken.md - - title: PasswordPolicy - path: /docs/reference/js/auth.passwordpolicy.md - - title: PasswordValidationStatus - path: /docs/reference/js/auth.passwordvalidationstatus.md - - title: Persistence - path: /docs/reference/js/auth.persistence.md - - title: PhoneAuthCredential - path: /docs/reference/js/auth.phoneauthcredential.md - - title: PhoneAuthProvider - path: /docs/reference/js/auth.phoneauthprovider.md - - title: PhoneMultiFactorAssertion - path: /docs/reference/js/auth.phonemultifactorassertion.md - - title: PhoneMultiFactorEnrollInfoOptions - path: /docs/reference/js/auth.phonemultifactorenrollinfooptions.md - - title: PhoneMultiFactorGenerator - path: /docs/reference/js/auth.phonemultifactorgenerator.md - - title: PhoneMultiFactorInfo - path: /docs/reference/js/auth.phonemultifactorinfo.md - - title: PhoneMultiFactorSignInInfoOptions - path: /docs/reference/js/auth.phonemultifactorsignininfooptions.md - - title: PhoneSingleFactorInfoOptions - path: /docs/reference/js/auth.phonesinglefactorinfooptions.md - - title: PopupRedirectResolver - path: /docs/reference/js/auth.popupredirectresolver.md - - title: ReactNativeAsyncStorage - path: /docs/reference/js/auth.reactnativeasyncstorage.md - - title: RecaptchaParameters - path: /docs/reference/js/auth.recaptchaparameters.md - - title: RecaptchaVerifier - path: /docs/reference/js/auth.recaptchaverifier.md - - title: SAMLAuthProvider - path: /docs/reference/js/auth.samlauthprovider.md - - title: TotpMultiFactorAssertion - path: /docs/reference/js/auth.totpmultifactorassertion.md - - title: TotpMultiFactorGenerator - path: /docs/reference/js/auth.totpmultifactorgenerator.md - - title: TotpMultiFactorInfo - path: /docs/reference/js/auth.totpmultifactorinfo.md - - title: TotpSecret - path: /docs/reference/js/auth.totpsecret.md - - title: TwitterAuthProvider - path: /docs/reference/js/auth.twitterauthprovider.md - - title: User - path: /docs/reference/js/auth.user.md - - title: UserCredential - path: /docs/reference/js/auth.usercredential.md - - title: UserInfo - path: /docs/reference/js/auth.userinfo.md - - title: UserMetadata - path: /docs/reference/js/auth.usermetadata.md - - title: database - path: /docs/reference/js/database.md - section: - - title: Database - path: /docs/reference/js/database.database.md - - title: DatabaseReference - path: /docs/reference/js/database.databasereference.md - - title: DataSnapshot - path: /docs/reference/js/database.datasnapshot.md - - title: IteratedDataSnapshot - path: /docs/reference/js/database.iterateddatasnapshot.md - - title: ListenOptions - path: /docs/reference/js/database.listenoptions.md - - title: OnDisconnect - path: /docs/reference/js/database.ondisconnect.md - - title: Query - path: /docs/reference/js/database.query.md - - title: QueryConstraint - path: /docs/reference/js/database.queryconstraint.md - - title: ThenableReference - path: /docs/reference/js/database.thenablereference.md - - title: TransactionOptions - path: /docs/reference/js/database.transactionoptions.md - - title: TransactionResult - path: /docs/reference/js/database.transactionresult.md - - title: firestore - path: /docs/reference/js/firestore_.md - section: - - title: AggregateField - path: /docs/reference/js/firestore_.aggregatefield.md - - title: AggregateQuerySnapshot - path: /docs/reference/js/firestore_.aggregatequerysnapshot.md - - title: AggregateSpec - path: /docs/reference/js/firestore_.aggregatespec.md - - title: Bytes - path: /docs/reference/js/firestore_.bytes.md - - title: CollectionReference - path: /docs/reference/js/firestore_.collectionreference.md - - title: DocumentChange - path: /docs/reference/js/firestore_.documentchange.md - - title: DocumentData - path: /docs/reference/js/firestore_.documentdata.md - - title: DocumentReference - path: /docs/reference/js/firestore_.documentreference.md - - title: DocumentSnapshot - path: /docs/reference/js/firestore_.documentsnapshot.md - - title: ExperimentalLongPollingOptions - path: /docs/reference/js/firestore_.experimentallongpollingoptions.md - - title: FieldPath - path: /docs/reference/js/firestore_.fieldpath.md - - title: FieldValue - path: /docs/reference/js/firestore_.fieldvalue.md - - title: Firestore - path: /docs/reference/js/firestore_.firestore.md - - title: FirestoreDataConverter - path: /docs/reference/js/firestore_.firestoredataconverter.md - - title: FirestoreError - path: /docs/reference/js/firestore_.firestoreerror.md - - title: FirestoreSettings - path: /docs/reference/js/firestore_.firestoresettings.md - - title: GeoPoint - path: /docs/reference/js/firestore_.geopoint.md - - title: Index - path: /docs/reference/js/firestore_.index.md - - title: IndexConfiguration - path: /docs/reference/js/firestore_.indexconfiguration.md - - title: IndexField - path: /docs/reference/js/firestore_.indexfield.md - - title: LoadBundleTask - path: /docs/reference/js/firestore_.loadbundletask.md - - title: LoadBundleTaskProgress - path: /docs/reference/js/firestore_.loadbundletaskprogress.md - - title: MemoryCacheSettings - path: /docs/reference/js/firestore_.memorycachesettings.md - - title: MemoryEagerGarbageCollector - path: /docs/reference/js/firestore_.memoryeagergarbagecollector.md - - title: MemoryLocalCache - path: /docs/reference/js/firestore_.memorylocalcache.md - - title: MemoryLruGarbageCollector - path: /docs/reference/js/firestore_.memorylrugarbagecollector.md - - title: PersistenceSettings - path: /docs/reference/js/firestore_.persistencesettings.md - - title: PersistentCacheIndexManager - path: /docs/reference/js/firestore_.persistentcacheindexmanager.md - - title: PersistentCacheSettings - path: /docs/reference/js/firestore_.persistentcachesettings.md - - title: PersistentLocalCache - path: /docs/reference/js/firestore_.persistentlocalcache.md - - title: PersistentMultipleTabManager - path: /docs/reference/js/firestore_.persistentmultipletabmanager.md - - title: PersistentSingleTabManager - path: /docs/reference/js/firestore_.persistentsingletabmanager.md - - title: PersistentSingleTabManagerSettings - path: /docs/reference/js/firestore_.persistentsingletabmanagersettings.md - - title: Query - path: /docs/reference/js/firestore_.query.md - - title: QueryCompositeFilterConstraint - path: /docs/reference/js/firestore_.querycompositefilterconstraint.md - - title: QueryConstraint - path: /docs/reference/js/firestore_.queryconstraint.md - - title: QueryDocumentSnapshot - path: /docs/reference/js/firestore_.querydocumentsnapshot.md - - title: QueryEndAtConstraint - path: /docs/reference/js/firestore_.queryendatconstraint.md - - title: QueryFieldFilterConstraint - path: /docs/reference/js/firestore_.queryfieldfilterconstraint.md - - title: QueryLimitConstraint - path: /docs/reference/js/firestore_.querylimitconstraint.md - - title: QueryOrderByConstraint - path: /docs/reference/js/firestore_.queryorderbyconstraint.md - - title: QuerySnapshot - path: /docs/reference/js/firestore_.querysnapshot.md - - title: QueryStartAtConstraint - path: /docs/reference/js/firestore_.querystartatconstraint.md - - title: SnapshotListenOptions - path: /docs/reference/js/firestore_.snapshotlistenoptions.md - - title: SnapshotMetadata - path: /docs/reference/js/firestore_.snapshotmetadata.md - - title: SnapshotOptions - path: /docs/reference/js/firestore_.snapshotoptions.md - - title: Timestamp - path: /docs/reference/js/firestore_.timestamp.md - - title: Transaction - path: /docs/reference/js/firestore_.transaction.md - - title: TransactionOptions - path: /docs/reference/js/firestore_.transactionoptions.md - - title: Unsubscribe - path: /docs/reference/js/firestore_.unsubscribe.md - - title: WriteBatch - path: /docs/reference/js/firestore_.writebatch.md - - title: firestore/lite - path: /docs/reference/js/firestore_lite.md - section: - - title: AggregateField - path: /docs/reference/js/firestore_lite.aggregatefield.md - - title: AggregateQuerySnapshot - path: /docs/reference/js/firestore_lite.aggregatequerysnapshot.md - - title: AggregateSpec - path: /docs/reference/js/firestore_lite.aggregatespec.md - - title: Bytes - path: /docs/reference/js/firestore_lite.bytes.md - - title: CollectionReference - path: /docs/reference/js/firestore_lite.collectionreference.md - - title: DocumentData - path: /docs/reference/js/firestore_lite.documentdata.md - - title: DocumentReference - path: /docs/reference/js/firestore_lite.documentreference.md - - title: DocumentSnapshot - path: /docs/reference/js/firestore_lite.documentsnapshot.md - - title: FieldPath - path: /docs/reference/js/firestore_lite.fieldpath.md - - title: FieldValue - path: /docs/reference/js/firestore_lite.fieldvalue.md - - title: Firestore - path: /docs/reference/js/firestore_lite.firestore.md - - title: FirestoreDataConverter - path: /docs/reference/js/firestore_lite.firestoredataconverter.md - - title: FirestoreError - path: /docs/reference/js/firestore_lite.firestoreerror.md - - title: GeoPoint - path: /docs/reference/js/firestore_lite.geopoint.md - - title: Query - path: /docs/reference/js/firestore_lite.query.md - - title: QueryCompositeFilterConstraint - path: /docs/reference/js/firestore_lite.querycompositefilterconstraint.md - - title: QueryConstraint - path: /docs/reference/js/firestore_lite.queryconstraint.md - - title: QueryDocumentSnapshot - path: /docs/reference/js/firestore_lite.querydocumentsnapshot.md - - title: QueryEndAtConstraint - path: /docs/reference/js/firestore_lite.queryendatconstraint.md - - title: QueryFieldFilterConstraint - path: /docs/reference/js/firestore_lite.queryfieldfilterconstraint.md - - title: QueryLimitConstraint - path: /docs/reference/js/firestore_lite.querylimitconstraint.md - - title: QueryOrderByConstraint - path: /docs/reference/js/firestore_lite.queryorderbyconstraint.md - - title: QuerySnapshot - path: /docs/reference/js/firestore_lite.querysnapshot.md - - title: QueryStartAtConstraint - path: /docs/reference/js/firestore_lite.querystartatconstraint.md - - title: Settings - path: /docs/reference/js/firestore_lite.settings.md - - title: Timestamp - path: /docs/reference/js/firestore_lite.timestamp.md - - title: Transaction - path: /docs/reference/js/firestore_lite.transaction.md - - title: TransactionOptions - path: /docs/reference/js/firestore_lite.transactionoptions.md - - title: WriteBatch - path: /docs/reference/js/firestore_lite.writebatch.md - - title: functions - path: /docs/reference/js/functions.md - section: - - title: Functions - path: /docs/reference/js/functions.functions.md - - title: FunctionsError - path: /docs/reference/js/functions.functionserror.md - - title: HttpsCallableOptions - path: /docs/reference/js/functions.httpscallableoptions.md - - title: HttpsCallableResult - path: /docs/reference/js/functions.httpscallableresult.md - - title: installations - path: /docs/reference/js/installations.md - section: - - title: Installations - path: /docs/reference/js/installations.installations.md - - title: messaging - path: /docs/reference/js/messaging_.md - section: - - title: FcmOptions - path: /docs/reference/js/messaging_.fcmoptions.md - - title: GetTokenOptions - path: /docs/reference/js/messaging_.gettokenoptions.md - - title: MessagePayload - path: /docs/reference/js/messaging_.messagepayload.md - - title: Messaging - path: /docs/reference/js/messaging_.messaging.md - - title: NotificationPayload - path: /docs/reference/js/messaging_.notificationpayload.md - - title: messaging/sw - path: /docs/reference/js/messaging_sw.md - section: - - title: FcmOptions - path: /docs/reference/js/messaging_sw.fcmoptions.md - - title: GetTokenOptions - path: /docs/reference/js/messaging_sw.gettokenoptions.md - - title: MessagePayload - path: /docs/reference/js/messaging_sw.messagepayload.md - - title: Messaging - path: /docs/reference/js/messaging_sw.messaging.md - - title: NotificationPayload - path: /docs/reference/js/messaging_sw.notificationpayload.md - - title: performance - path: /docs/reference/js/performance.md - section: - - title: FirebasePerformance - path: /docs/reference/js/performance.firebaseperformance.md - - title: PerformanceSettings - path: /docs/reference/js/performance.performancesettings.md - - title: PerformanceTrace - path: /docs/reference/js/performance.performancetrace.md - - title: remote-config - path: /docs/reference/js/remote-config.md - section: - - title: RemoteConfig - path: /docs/reference/js/remote-config.remoteconfig.md - - title: RemoteConfigSettings - path: /docs/reference/js/remote-config.remoteconfigsettings.md - - title: Value - path: /docs/reference/js/remote-config.value.md - - title: storage - path: /docs/reference/js/storage.md - section: - - title: FirebaseStorage - path: /docs/reference/js/storage.firebasestorage.md - - title: FullMetadata - path: /docs/reference/js/storage.fullmetadata.md - - title: ListOptions - path: /docs/reference/js/storage.listoptions.md - - title: ListResult - path: /docs/reference/js/storage.listresult.md - - title: SettableMetadata - path: /docs/reference/js/storage.settablemetadata.md - - title: StorageError - path: /docs/reference/js/storage.storageerror.md - - title: StorageObserver - path: /docs/reference/js/storage.storageobserver.md - - title: StorageReference - path: /docs/reference/js/storage.storagereference.md - - title: UploadMetadata - path: /docs/reference/js/storage.uploadmetadata.md - - title: UploadResult - path: /docs/reference/js/storage.uploadresult.md - - title: UploadTask - path: /docs/reference/js/storage.uploadtask.md - - title: UploadTaskSnapshot - path: /docs/reference/js/storage.uploadtasksnapshot.md - - title: vertexai-preview - path: /docs/reference/js/vertexai-preview.md - section: - - title: BaseParams - path: /docs/reference/js/vertexai-preview.baseparams.md - - title: ChatSession - path: /docs/reference/js/vertexai-preview.chatsession.md - - title: Citation - path: /docs/reference/js/vertexai-preview.citation.md - - title: CitationMetadata - path: /docs/reference/js/vertexai-preview.citationmetadata.md - - title: Content - path: /docs/reference/js/vertexai-preview.content.md - - title: CountTokensRequest - path: /docs/reference/js/vertexai-preview.counttokensrequest.md - - title: CountTokensResponse - path: /docs/reference/js/vertexai-preview.counttokensresponse.md - - title: CustomErrorData - path: /docs/reference/js/vertexai-preview.customerrordata.md - - title: Date_2 - path: /docs/reference/js/vertexai-preview.date_2.md - - title: EnhancedGenerateContentResponse - path: /docs/reference/js/vertexai-preview.enhancedgeneratecontentresponse.md - - title: ErrorDetails - path: /docs/reference/js/vertexai-preview.errordetails.md - - title: FileData - path: /docs/reference/js/vertexai-preview.filedata.md - - title: FileDataPart - path: /docs/reference/js/vertexai-preview.filedatapart.md - - title: FunctionCall - path: /docs/reference/js/vertexai-preview.functioncall.md - - title: FunctionCallingConfig - path: /docs/reference/js/vertexai-preview.functioncallingconfig.md - - title: FunctionCallPart - path: /docs/reference/js/vertexai-preview.functioncallpart.md - - title: FunctionDeclaration - path: /docs/reference/js/vertexai-preview.functiondeclaration.md - - title: FunctionDeclarationSchema - path: /docs/reference/js/vertexai-preview.functiondeclarationschema.md - - title: FunctionDeclarationSchemaProperty - path: >- - /docs/reference/js/vertexai-preview.functiondeclarationschemaproperty.md - - title: FunctionDeclarationsTool - path: /docs/reference/js/vertexai-preview.functiondeclarationstool.md - - title: FunctionResponse - path: /docs/reference/js/vertexai-preview.functionresponse.md - - title: FunctionResponsePart - path: /docs/reference/js/vertexai-preview.functionresponsepart.md - - title: GenerateContentCandidate - path: /docs/reference/js/vertexai-preview.generatecontentcandidate.md - - title: GenerateContentRequest - path: /docs/reference/js/vertexai-preview.generatecontentrequest.md - - title: GenerateContentResponse - path: /docs/reference/js/vertexai-preview.generatecontentresponse.md - - title: GenerateContentResult - path: /docs/reference/js/vertexai-preview.generatecontentresult.md - - title: GenerateContentStreamResult - path: /docs/reference/js/vertexai-preview.generatecontentstreamresult.md - - title: GenerationConfig - path: /docs/reference/js/vertexai-preview.generationconfig.md - - title: GenerativeContentBlob - path: /docs/reference/js/vertexai-preview.generativecontentblob.md - - title: GenerativeModel - path: /docs/reference/js/vertexai-preview.generativemodel.md - - title: GroundingAttribution - path: /docs/reference/js/vertexai-preview.groundingattribution.md - - title: GroundingMetadata - path: /docs/reference/js/vertexai-preview.groundingmetadata.md - - title: InlineDataPart - path: /docs/reference/js/vertexai-preview.inlinedatapart.md - - title: ModelParams - path: /docs/reference/js/vertexai-preview.modelparams.md - - title: PromptFeedback - path: /docs/reference/js/vertexai-preview.promptfeedback.md - - title: RequestOptions - path: /docs/reference/js/vertexai-preview.requestoptions.md - - title: RetrievedContextAttribution - path: /docs/reference/js/vertexai-preview.retrievedcontextattribution.md - - title: SafetyRating - path: /docs/reference/js/vertexai-preview.safetyrating.md - - title: SafetySetting - path: /docs/reference/js/vertexai-preview.safetysetting.md - - title: Segment - path: /docs/reference/js/vertexai-preview.segment.md - - title: StartChatParams - path: /docs/reference/js/vertexai-preview.startchatparams.md - - title: TextPart - path: /docs/reference/js/vertexai-preview.textpart.md - - title: ToolConfig - path: /docs/reference/js/vertexai-preview.toolconfig.md - - title: UsageMetadata - path: /docs/reference/js/vertexai-preview.usagemetadata.md - - title: VertexAI - path: /docs/reference/js/vertexai-preview.vertexai.md - - title: VertexAIError - path: /docs/reference/js/vertexai-preview.vertexaierror.md - - title: VertexAIOptions - path: /docs/reference/js/vertexai-preview.vertexaioptions.md - - title: VideoMetadata - path: /docs/reference/js/vertexai-preview.videometadata.md - - title: WebAttribution - path: /docs/reference/js/vertexai-preview.webattribution.md diff --git a/docs-devsite/firestore_.md b/docs-devsite/firestore_.md index 145793a3f04..74e960c833b 100644 --- a/docs-devsite/firestore_.md +++ b/docs-devsite/firestore_.md @@ -124,6 +124,8 @@ https://github.com/firebase/firebase-js-sdk | [endBefore(snapshot)](./firestore_.md#endbefore_9a4477f) | Creates a [QueryEndAtConstraint](./firestore_.queryendatconstraint.md#queryendatconstraint_class) that modifies the result set to end before the provided document (exclusive). The end position is relative to the order of the query. The document must contain all of the fields provided in the orderBy of the query. | | [startAfter(snapshot)](./firestore_.md#startafter_9a4477f) | Creates a [QueryStartAtConstraint](./firestore_.querystartatconstraint.md#querystartatconstraint_class) that modifies the result set to start after the provided document (exclusive). The starting position is relative to the order of the query. The document must contain all of the fields provided in the orderBy of the query. | | [startAt(snapshot)](./firestore_.md#startat_9a4477f) | Creates a [QueryStartAtConstraint](./firestore_.querystartatconstraint.md#querystartatconstraint_class) that modifies the result set to start at the provided document (inclusive). The starting position is relative to the order of the query. The document must contain all of the fields provided in the orderBy of this query. | +| function(values, ...) | +| [vector(values)](./firestore_.md#vector_0dbdaf2) | Creates a new VectorValue constructed with a copy of the given array of numbers. | ## Classes @@ -155,6 +157,7 @@ https://github.com/firebase/firebase-js-sdk | [SnapshotMetadata](./firestore_.snapshotmetadata.md#snapshotmetadata_class) | Metadata about a snapshot, describing the state of the snapshot. | | [Timestamp](./firestore_.timestamp.md#timestamp_class) | A Timestamp represents a point in time independent of any time zone or calendar, represented as seconds and fractions of seconds at nanosecond resolution in UTC Epoch time.It is encoded using the Proleptic Gregorian Calendar which extends the Gregorian calendar backwards to year one. It is encoded assuming all minutes are 60 seconds long, i.e. leap seconds are "smeared" so that no leap second table is needed for interpretation. Range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z.For examples and further specifications, refer to the [Timestamp definition](https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto). | | [Transaction](./firestore_.transaction.md#transaction_class) | A reference to a transaction.The Transaction object passed to a transaction's updateFunction provides the methods to read and write data within the transaction context. See [runTransaction()](./firestore_.md#runtransaction_6f03ec4). | +| [VectorValue](./firestore_.vectorvalue.md#vectorvalue_class) | Represents a vector type in Firestore documents. Create an instance with . VectorValue | | [WriteBatch](./firestore_.writebatch.md#writebatch_class) | A write batch, used to perform multiple writes as a single atomic unit.A WriteBatch object can be acquired by calling [writeBatch()](./firestore_.md#writebatch_231a8e0). It provides methods for adding writes to the write batch. None of the writes will be committed (or visible locally) until [WriteBatch.commit()](./firestore_.writebatch.md#writebatchcommit) is called. | ## Interfaces @@ -2452,6 +2455,30 @@ export declare function startAt( A [QueryStartAtConstraint](./firestore_.querystartatconstraint.md#querystartatconstraint_class) to pass to `query()`. +## function(values, ...) + +### vector(values) {:#vector_0dbdaf2} + +Creates a new `VectorValue` constructed with a copy of the given array of numbers. + +Signature: + +```typescript +export declare function vector(values?: number[]): VectorValue; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| values | number\[\] | Create a VectorValue instance with a copy of this array of numbers. | + +Returns: + +[VectorValue](./firestore_.vectorvalue.md#vectorvalue_class) + +A new `VectorValue` constructed with a copy of the given array of numbers. + ## CACHE\_SIZE\_UNLIMITED Constant used to indicate the LRU garbage collection should be disabled. Set this value as the `cacheSizeBytes` on the settings passed to the [Firestore](./firestore_.firestore.md#firestore_class) instance. diff --git a/docs-devsite/firestore_lite.md b/docs-devsite/firestore_lite.md index 17ef56b501c..da7d304e3d5 100644 --- a/docs-devsite/firestore_lite.md +++ b/docs-devsite/firestore_lite.md @@ -89,6 +89,8 @@ https://github.com/firebase/firebase-js-sdk | [endBefore(snapshot)](./firestore_lite.md#endbefore_9a4477f) | Creates a [QueryEndAtConstraint](./firestore_.queryendatconstraint.md#queryendatconstraint_class) that modifies the result set to end before the provided document (exclusive). The end position is relative to the order of the query. The document must contain all of the fields provided in the orderBy of the query. | | [startAfter(snapshot)](./firestore_lite.md#startafter_9a4477f) | Creates a [QueryStartAtConstraint](./firestore_.querystartatconstraint.md#querystartatconstraint_class) that modifies the result set to start after the provided document (exclusive). The starting position is relative to the order of the query. The document must contain all of the fields provided in the orderBy of the query. | | [startAt(snapshot)](./firestore_lite.md#startat_9a4477f) | Creates a [QueryStartAtConstraint](./firestore_.querystartatconstraint.md#querystartatconstraint_class) that modifies the result set to start at the provided document (inclusive). The starting position is relative to the order of the query. The document must contain all of the fields provided in the orderBy of this query. | +| function(values, ...) | +| [vector(values)](./firestore_lite.md#vector_0dbdaf2) | Creates a new VectorValue constructed with a copy of the given array of numbers. | ## Classes @@ -117,6 +119,7 @@ https://github.com/firebase/firebase-js-sdk | [QueryStartAtConstraint](./firestore_lite.querystartatconstraint.md#querystartatconstraint_class) | A QueryStartAtConstraint is used to exclude documents from the start of a result set returned by a Firestore query. QueryStartAtConstraints are created by invoking [startAt()](./firestore_.md#startat_9a4477f) or [startAfter()](./firestore_.md#startafter_9a4477f) and can then be passed to [query()](./firestore_.md#query_9f7b0f4) to create a new query instance that also contains this QueryStartAtConstraint. | | [Timestamp](./firestore_lite.timestamp.md#timestamp_class) | A Timestamp represents a point in time independent of any time zone or calendar, represented as seconds and fractions of seconds at nanosecond resolution in UTC Epoch time.It is encoded using the Proleptic Gregorian Calendar which extends the Gregorian calendar backwards to year one. It is encoded assuming all minutes are 60 seconds long, i.e. leap seconds are "smeared" so that no leap second table is needed for interpretation. Range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z.For examples and further specifications, refer to the [Timestamp definition](https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto). | | [Transaction](./firestore_lite.transaction.md#transaction_class) | A reference to a transaction.The Transaction object passed to a transaction's updateFunction provides the methods to read and write data within the transaction context. See [runTransaction()](./firestore_.md#runtransaction_6f03ec4). | +| [VectorValue](./firestore_lite.vectorvalue.md#vectorvalue_class) | Represents a vector type in Firestore documents. Create an instance with . VectorValue | | [WriteBatch](./firestore_lite.writebatch.md#writebatch_class) | A write batch, used to perform multiple writes as a single atomic unit.A WriteBatch object can be acquired by calling [writeBatch()](./firestore_.md#writebatch_231a8e0). It provides methods for adding writes to the write batch. None of the writes will be committed (or visible locally) until [WriteBatch.commit()](./firestore_.writebatch.md#writebatchcommit) is called. | ## Interfaces @@ -1568,6 +1571,30 @@ export declare function startAt( A [QueryStartAtConstraint](./firestore_.querystartatconstraint.md#querystartatconstraint_class) to pass to `query()`. +## function(values, ...) + +### vector(values) {:#vector_0dbdaf2} + +Creates a new `VectorValue` constructed with a copy of the given array of numbers. + +Signature: + +```typescript +export declare function vector(values?: number[]): VectorValue; +``` + +#### Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| values | number\[\] | Create a VectorValue instance with a copy of this array of numbers. | + +Returns: + +[VectorValue](./firestore_lite.vectorvalue.md#vectorvalue_class) + +A new `VectorValue` constructed with a copy of the given array of numbers. + ## AddPrefixToKeys Returns a new map where every key is prefixed with the outer key appended to a dot. diff --git a/packages/firestore/lite/index.ts b/packages/firestore/lite/index.ts index d6ebad66cb5..b751f0a8254 100644 --- a/packages/firestore/lite/index.ts +++ b/packages/firestore/lite/index.ts @@ -127,7 +127,8 @@ export { arrayRemove, arrayUnion, serverTimestamp, - deleteField + deleteField, + vector } from '../src/lite-api/field_value_impl'; export { @@ -138,6 +139,8 @@ export { snapshotEqual } from '../src/lite-api/snapshot'; +export { VectorValue } from '../src/lite-api/vector_value'; + export { WriteBatch, writeBatch } from '../src/lite-api/write_batch'; export { TransactionOptions } from '../src/lite-api/transaction_options'; diff --git a/packages/firestore/src/api.ts b/packages/firestore/src/api.ts index 5f5b078fbd6..ea969c6b94c 100644 --- a/packages/firestore/src/api.ts +++ b/packages/firestore/src/api.ts @@ -172,9 +172,12 @@ export { arrayUnion, deleteField, increment, - serverTimestamp + serverTimestamp, + vector } from './api/field_value_impl'; +export { VectorValue } from './lite-api/vector_value'; + export { LogLevelString as LogLevel, setLogLevel } from './util/log'; export { Bytes } from './api/bytes'; diff --git a/packages/firestore/src/api/field_value_impl.ts b/packages/firestore/src/api/field_value_impl.ts index 4689352f840..1b1283a3543 100644 --- a/packages/firestore/src/api/field_value_impl.ts +++ b/packages/firestore/src/api/field_value_impl.ts @@ -20,5 +20,6 @@ export { arrayRemove, arrayUnion, serverTimestamp, - deleteField + deleteField, + vector } from '../lite-api/field_value_impl'; diff --git a/packages/firestore/src/index/firestore_index_value_writer.ts b/packages/firestore/src/index/firestore_index_value_writer.ts index 9d5d86ab640..dfdb3836578 100644 --- a/packages/firestore/src/index/firestore_index_value_writer.ts +++ b/packages/firestore/src/index/firestore_index_value_writer.ts @@ -21,7 +21,11 @@ import { normalizeNumber, normalizeTimestamp } from '../model/normalize'; -import { isMaxValue } from '../model/values'; +import { + isVectorValue, + VECTOR_MAP_VECTORS_KEY, + isMaxValue +} from '../model/values'; import { ArrayValue, MapValue, Value } from '../protos/firestore_proto_api'; import { fail } from '../util/assert'; import { isNegativeZero } from '../util/types'; @@ -41,6 +45,7 @@ const INDEX_TYPE_BLOB = 30; const INDEX_TYPE_REFERENCE = 37; const INDEX_TYPE_GEOPOINT = 45; const INDEX_TYPE_ARRAY = 50; +const INDEX_TYPE_VECTOR = 53; const INDEX_TYPE_MAP = 55; const INDEX_TYPE_REFERENCE_SEGMENT = 60; @@ -121,6 +126,8 @@ export class FirestoreIndexValueWriter { } else if ('mapValue' in indexValue) { if (isMaxValue(indexValue)) { this.writeValueTypeLabel(encoder, Number.MAX_SAFE_INTEGER); + } else if (isVectorValue(indexValue)) { + this.writeIndexVector(indexValue.mapValue!, encoder); } else { this.writeIndexMap(indexValue.mapValue!, encoder); this.writeTruncationMarker(encoder); @@ -160,6 +167,24 @@ export class FirestoreIndexValueWriter { } } + private writeIndexVector( + mapIndexValue: MapValue, + encoder: DirectionalIndexByteEncoder + ): void { + const map = mapIndexValue.fields || {}; + this.writeValueTypeLabel(encoder, INDEX_TYPE_VECTOR); + + // Vectors sort first by length + const key = VECTOR_MAP_VECTORS_KEY; + const length = map[key].arrayValue?.values?.length || 0; + this.writeValueTypeLabel(encoder, INDEX_TYPE_NUMBER); + encoder.writeNumber(normalizeNumber(length)); + + // Vectors then sort by position value + this.writeIndexString(key, encoder); + this.writeIndexValueAux(map[key], encoder); + } + private writeIndexArray( arrayIndexValue: ArrayValue, encoder: DirectionalIndexByteEncoder diff --git a/packages/firestore/src/lite-api/field_value_impl.ts b/packages/firestore/src/lite-api/field_value_impl.ts index b4c65ca6de2..2c910bdace5 100644 --- a/packages/firestore/src/lite-api/field_value_impl.ts +++ b/packages/firestore/src/lite-api/field_value_impl.ts @@ -23,6 +23,7 @@ import { NumericIncrementFieldValueImpl, ServerTimestampFieldValueImpl } from './user_data_reader'; +import { VectorValue } from './vector_value'; /** * Returns a sentinel for use with {@link @firebase/firestore/lite#(updateDoc:1)} or @@ -97,3 +98,14 @@ export function arrayRemove(...elements: unknown[]): FieldValue { export function increment(n: number): FieldValue { return new NumericIncrementFieldValueImpl('increment', n); } + +/** + * Creates a new `VectorValue` constructed with a copy of the given array of numbers. + * + * @param values - Create a `VectorValue` instance with a copy of this array of numbers. + * + * @returns A new `VectorValue` constructed with a copy of the given array of numbers. + */ +export function vector(values?: number[]): VectorValue { + return new VectorValue(values); +} diff --git a/packages/firestore/src/lite-api/user_data_reader.ts b/packages/firestore/src/lite-api/user_data_reader.ts index d9590ee860d..ebd4b49085f 100644 --- a/packages/firestore/src/lite-api/user_data_reader.ts +++ b/packages/firestore/src/lite-api/user_data_reader.ts @@ -41,12 +41,17 @@ import { NumericIncrementTransformOperation, ServerTimestampTransform } from '../model/transform_operation'; +import { + TYPE_KEY, + VECTOR_MAP_VECTORS_KEY, + VECTOR_VALUE_SENTINEL +} from '../model/values'; import { newSerializer } from '../platform/serializer'; import { MapValue as ProtoMapValue, Value as ProtoValue } from '../protos/firestore_proto_api'; -import { toNumber } from '../remote/number_serializer'; +import { toDouble, toNumber } from '../remote/number_serializer'; import { JsonProtoSerializer, toBytes, @@ -69,6 +74,7 @@ import { WithFieldValue } from './reference'; import { Timestamp } from './timestamp'; +import { VectorValue } from './vector_value'; const RESERVED_FIELD_REGEX = /^__.*__$/; @@ -901,6 +907,8 @@ function parseScalarValue( value._key.path ) }; + } else if (value instanceof VectorValue) { + return parseVectorValue(value, context); } else { throw context.createError( `Unsupported field value: ${valueDescription(value)}` @@ -908,6 +916,37 @@ function parseScalarValue( } } +/** + * Creates a new VectorValue proto value (using the internal format). + */ +export function parseVectorValue( + value: VectorValue, + context: ParseContextImpl +): ProtoValue { + const mapValue: ProtoMapValue = { + fields: { + [TYPE_KEY]: { + stringValue: VECTOR_VALUE_SENTINEL + }, + [VECTOR_MAP_VECTORS_KEY]: { + arrayValue: { + values: value.toArray().map(value => { + if (typeof value !== 'number') { + throw context.createError( + 'VectorValues must only contain numeric values.' + ); + } + + return toDouble(context.serializer, value); + }) + } + } + } + }; + + return { mapValue }; +} + /** * Checks whether an object looks like a JSON object that should be converted * into a struct. Normal class/prototype instances are considered to look like @@ -925,7 +964,8 @@ function looksLikeJsonObject(input: unknown): boolean { !(input instanceof GeoPoint) && !(input instanceof Bytes) && !(input instanceof DocumentReference) && - !(input instanceof FieldValue) + !(input instanceof FieldValue) && + !(input instanceof VectorValue) ); } diff --git a/packages/firestore/src/lite-api/user_data_writer.ts b/packages/firestore/src/lite-api/user_data_writer.ts index 5536b5c8b42..e903991cb58 100644 --- a/packages/firestore/src/lite-api/user_data_writer.ts +++ b/packages/firestore/src/lite-api/user_data_writer.ts @@ -30,7 +30,7 @@ import { getPreviousValue } from '../model/server_timestamps'; import { TypeOrder } from '../model/type_order'; -import { typeOrder } from '../model/values'; +import { VECTOR_MAP_VECTORS_KEY, typeOrder } from '../model/values'; import { ApiClientObjectMap, ArrayValue as ProtoArrayValue, @@ -48,6 +48,7 @@ import { forEach } from '../util/obj'; import { GeoPoint } from './geo_point'; import { Timestamp } from './timestamp'; +import { VectorValue } from './vector_value'; export type ServerTimestampBehavior = 'estimate' | 'previous' | 'none'; @@ -85,6 +86,8 @@ export abstract class AbstractUserDataWriter { return this.convertArray(value.arrayValue!, serverTimestampBehavior); case TypeOrder.ObjectValue: return this.convertObject(value.mapValue!, serverTimestampBehavior); + case TypeOrder.VectorValue: + return this.convertVectorValue(value.mapValue!); default: throw fail('Invalid value type: ' + JSON.stringify(value)); } @@ -111,6 +114,19 @@ export abstract class AbstractUserDataWriter { return result; } + /** + * @internal + */ + convertVectorValue(mapValue: ProtoMapValue): VectorValue { + const values = mapValue.fields?.[ + VECTOR_MAP_VECTORS_KEY + ].arrayValue?.values?.map(value => { + return normalizeNumber(value.doubleValue); + }); + + return new VectorValue(values); + } + private convertGeoPoint(value: ProtoLatLng): GeoPoint { return new GeoPoint( normalizeNumber(value.latitude), diff --git a/packages/firestore/src/lite-api/vector_value.ts b/packages/firestore/src/lite-api/vector_value.ts new file mode 100644 index 00000000000..a09f2799fb3 --- /dev/null +++ b/packages/firestore/src/lite-api/vector_value.ts @@ -0,0 +1,51 @@ +/** + * @license + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { isPrimitiveArrayEqual } from '../util/array'; + +/** + * Represents a vector type in Firestore documents. + * Create an instance with {@link FieldValue.vector}. + * + * @class VectorValue + */ +export class VectorValue { + private readonly _values: number[]; + + /** + * @private + * @internal + */ + constructor(values: number[] | undefined) { + // Making a copy of the parameter. + this._values = (values || []).map(n => n); + } + + /** + * Returns a copy of the raw number array form of the vector. + */ + toArray(): number[] { + return this._values.map(n => n); + } + + /** + * Returns `true` if the two VectorValue has the same raw number arrays, returns `false` otherwise. + */ + isEqual(other: VectorValue): boolean { + return isPrimitiveArrayEqual(this._values, other._values); + } +} diff --git a/packages/firestore/src/model/type_order.ts b/packages/firestore/src/model/type_order.ts index df4c6067a31..749b8e8036d 100644 --- a/packages/firestore/src/model/type_order.ts +++ b/packages/firestore/src/model/type_order.ts @@ -35,6 +35,7 @@ export const enum TypeOrder { RefValue = 7, GeoPointValue = 8, ArrayValue = 9, - ObjectValue = 10, + VectorValue = 10, + ObjectValue = 11, MaxValue = 9007199254740991 // Number.MAX_SAFE_INTEGER } diff --git a/packages/firestore/src/model/values.ts b/packages/firestore/src/model/values.ts index 3466c2c1ee6..1977767515e 100644 --- a/packages/firestore/src/model/values.ts +++ b/packages/firestore/src/model/values.ts @@ -21,6 +21,7 @@ import { LatLng, MapValue, Timestamp, + Value as ProtoValue, Value } from '../protos/firestore_proto_api'; import { fail } from '../util/assert'; @@ -41,6 +42,7 @@ import { } from './server_timestamps'; import { TypeOrder } from './type_order'; +export const TYPE_KEY = '__type__'; const MAX_VALUE_TYPE = '__max__'; export const MAX_VALUE: Value = { mapValue: { @@ -50,6 +52,9 @@ export const MAX_VALUE: Value = { } }; +export const VECTOR_VALUE_SENTINEL = '__vector__'; +export const VECTOR_MAP_VECTORS_KEY = 'value'; + export const MIN_VALUE: Value = { nullValue: 'NULL_VALUE' }; @@ -79,6 +84,8 @@ export function typeOrder(value: Value): TypeOrder { return TypeOrder.ServerTimestampValue; } else if (isMaxValue(value)) { return TypeOrder.MaxValue; + } else if (isVectorValue(value)) { + return TypeOrder.VectorValue; } return TypeOrder.ObjectValue; } else { @@ -123,6 +130,7 @@ export function valueEquals(left: Value, right: Value): boolean { right.arrayValue!.values || [], valueEquals ); + case TypeOrder.VectorValue: case TypeOrder.ObjectValue: return objectEquals(left, right); case TypeOrder.MaxValue: @@ -252,6 +260,8 @@ export function valueCompare(left: Value, right: Value): number { return compareGeoPoints(left.geoPointValue!, right.geoPointValue!); case TypeOrder.ArrayValue: return compareArrays(left.arrayValue!, right.arrayValue!); + case TypeOrder.VectorValue: + return compareVectors(left.mapValue!, right.mapValue!); case TypeOrder.ObjectValue: return compareMaps(left.mapValue!, right.mapValue!); default: @@ -349,6 +359,25 @@ function compareArrays(left: ArrayValue, right: ArrayValue): number { return primitiveComparator(leftArray.length, rightArray.length); } +function compareVectors(left: MapValue, right: MapValue): number { + const leftMap = left.fields || {}; + const rightMap = right.fields || {}; + + // The vector is a map, but only vector value is compared. + const leftArrayValue = leftMap[VECTOR_MAP_VECTORS_KEY]?.arrayValue; + const rightArrayValue = rightMap[VECTOR_MAP_VECTORS_KEY]?.arrayValue; + + const lengthCompare = primitiveComparator( + leftArrayValue?.values?.length || 0, + rightArrayValue?.values?.length || 0 + ); + if (lengthCompare !== 0) { + return lengthCompare; + } + + return compareArrays(leftArrayValue!, rightArrayValue!); +} + function compareMaps(left: MapValue, right: MapValue): number { if (left === MAX_VALUE.mapValue && right === MAX_VALUE.mapValue) { return 0; @@ -504,6 +533,7 @@ export function estimateByteSize(value: Value): number { return 16; case TypeOrder.ArrayValue: return estimateArrayByteSize(value.arrayValue!); + case TypeOrder.VectorValue: case TypeOrder.ObjectValue: return estimateMapByteSize(value.mapValue!); default: @@ -589,6 +619,12 @@ export function isMapValue( return !!value && 'mapValue' in value; } +/** Returns true if `value` is a VetorValue. */ +export function isVectorValue(value: ProtoValue | null): boolean { + const type = (value?.mapValue?.fields || {})[TYPE_KEY]?.stringValue; + return type === VECTOR_VALUE_SENTINEL; +} + /** Creates a deep copy of `source`. */ export function deepClone(source: Value): Value { if (source.geoPointValue) { @@ -624,6 +660,17 @@ export function isMaxValue(value: Value): boolean { ); } +export const MIN_VECTOR_VALUE = { + mapValue: { + fields: { + [TYPE_KEY]: { stringValue: VECTOR_VALUE_SENTINEL }, + [VECTOR_MAP_VECTORS_KEY]: { + arrayValue: {} + } + } + } +}; + /** Returns the lowest value for the given value type (inclusive). */ export function valuesGetLowerBound(value: Value): Value { if ('nullValue' in value) { @@ -645,6 +692,9 @@ export function valuesGetLowerBound(value: Value): Value { } else if ('arrayValue' in value) { return { arrayValue: {} }; } else if ('mapValue' in value) { + if (isVectorValue(value)) { + return MIN_VECTOR_VALUE; + } return { mapValue: {} }; } else { return fail('Invalid value type: ' + JSON.stringify(value)); @@ -670,8 +720,11 @@ export function valuesGetUpperBound(value: Value): Value { } else if ('geoPointValue' in value) { return { arrayValue: {} }; } else if ('arrayValue' in value) { - return { mapValue: {} }; + return MIN_VECTOR_VALUE; } else if ('mapValue' in value) { + if (isVectorValue(value)) { + return { mapValue: {} }; + } return MAX_VALUE; } else { return fail('Invalid value type: ' + JSON.stringify(value)); diff --git a/packages/firestore/src/util/array.ts b/packages/firestore/src/util/array.ts index c5ae37b71df..3427abcf67b 100644 --- a/packages/firestore/src/util/array.ts +++ b/packages/firestore/src/util/array.ts @@ -111,3 +111,55 @@ export function diffArrays( onRemove(before[b++]); } } + +/** + * Verifies equality for an array of objects using the `isEqual` interface. + * + * @private + * @internal + * @param left Array of objects supporting `isEqual`. + * @param right Array of objects supporting `isEqual`. + * @return True if arrays are equal. + */ +export function isArrayEqual boolean }>( + left: T[], + right: T[] +): boolean { + if (left.length !== right.length) { + return false; + } + + for (let i = 0; i < left.length; ++i) { + if (!left[i].isEqual(right[i])) { + return false; + } + } + + return true; +} + +/** + * Verifies equality for an array of primitives. + * + * @private + * @internal + * @param left Array of primitives. + * @param right Array of primitives. + * @return True if arrays are equal. + */ +export function isPrimitiveArrayEqual( + left: T[], + right: T[] +): boolean { + if (left.length !== right.length) { + return false; + } + + for (let i = 0; i < left.length; ++i) { + if (left[i] !== right[i]) { + return false; + } + } + + return true; +} diff --git a/packages/firestore/test/integration/api/database.test.ts b/packages/firestore/test/integration/api/database.test.ts index 6418d4a9651..eda3978dd46 100644 --- a/packages/firestore/test/integration/api/database.test.ts +++ b/packages/firestore/test/integration/api/database.test.ts @@ -64,7 +64,10 @@ import { newTestFirestore, SnapshotOptions, newTestApp, - FirestoreError + FirestoreError, + QuerySnapshot, + vector, + getDocsFromServer } from '../util/firebase_export'; import { apiDescribe, @@ -74,7 +77,9 @@ import { withTestDbs, withTestDoc, withTestDocAndInitialData, - withNamedTestDbsOrSkipUnlessUsingEmulator + withNamedTestDbsOrSkipUnlessUsingEmulator, + toDataArray, + checkOnlineAndOfflineResultsMatch } from '../util/helpers'; import { DEFAULT_SETTINGS, DEFAULT_PROJECT_ID } from '../util/settings'; @@ -577,6 +582,229 @@ apiDescribe('Database', persistence => { }); }); + describe('vector embeddings', () => { + it('can write and read vector embeddings', async () => { + return withTestCollection(persistence, {}, async coll => { + const ref = await addDoc(coll, { + vector0: vector([0.0]), + vector1: vector([1, 2, 3.99]) + }); + await setDoc(ref, { + vector0: vector([0.0]), + vector1: vector([1, 2, 3.99]), + vector2: vector([0, 0, 0]) + }); + await updateDoc(ref, { + vector3: vector([-1, -200, -999]) + }); + + const snap1 = await getDoc(ref); + expect(snap1.get('vector0').isEqual(vector([0.0]))).to.be.true; + expect(snap1.get('vector1').isEqual(vector([1, 2, 3.99]))).to.be.true; + expect(snap1.get('vector2').isEqual(vector([0, 0, 0]))).to.be.true; + expect(snap1.get('vector3').isEqual(vector([-1, -200, -999]))).to.be + .true; + }); + }); + + it('can listen to documents with vectors', async () => { + return withTestCollection(persistence, {}, async randomCol => { + const ref = doc(randomCol); + 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 q = query(randomCol, where('purpose', '==', 'vector tests')); + const unlisten = onSnapshot(q, (snap: QuerySnapshot) => { + 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 setDoc(ref, { + purpose: 'vector tests', + vector0: vector([0.0]), + vector1: vector([1, 2, 3.99]) + }); + + await createDeferred.promise; + expect(document).to.be.not.null; + expect(document!.get('vector0').isEqual(vector([0.0]))).to.be.true; + expect(document!.get('vector1').isEqual(vector([1, 2, 3.99]))).to.be + .true; + + await setDoc(ref, { + purpose: 'vector tests', + vector0: vector([0.0]), + vector1: vector([1, 2, 3.99]), + vector2: vector([0, 0, 0]) + }); + await setDeferred.promise; + expect(document).to.be.not.null; + expect(document!.get('vector0').isEqual(vector([0.0]))).to.be.true; + expect(document!.get('vector1').isEqual(vector([1, 2, 3.99]))).to.be + .true; + expect(document!.get('vector2').isEqual(vector([0, 0, 0]))).to.be.true; + + await updateDoc(ref, { + vector3: vector([-1, -200, -999]) + }); + await updateDeferred.promise; + expect(document).to.be.not.null; + expect(document!.get('vector0').isEqual(vector([0.0]))).to.be.true; + expect(document!.get('vector1').isEqual(vector([1, 2, 3.99]))).to.be + .true; + expect(document!.get('vector2').isEqual(vector([0, 0, 0]))).to.be.true; + expect(document!.get('vector3').isEqual(vector([-1, -200, -999]))).to.be + .true; + + await deleteDoc(ref); + await deleteDeferred.promise; + expect(document).to.be.null; + + unlisten(); + }); + }); + + it('SDK orders vector field same way as backend', async () => { + // Test data in the order that we expect the backend to sort it. + const docsInOrder = [ + { embedding: [1, 2, 3, 4, 5, 6] }, + { embedding: [100] }, + { embedding: vector([Number.NEGATIVE_INFINITY]) }, + { embedding: vector([-100]) }, + { embedding: vector([100]) }, + { embedding: vector([Number.POSITIVE_INFINITY]) }, + { embedding: vector([1, 2]) }, + { embedding: vector([2, 2]) }, + { embedding: vector([1, 2, 3]) }, + { embedding: vector([1, 2, 3, 4]) }, + { embedding: vector([1, 2, 3, 4, 5]) }, + { embedding: vector([1, 2, 100, 4, 4]) }, + { embedding: vector([100, 2, 3, 4, 5]) }, + { embedding: { HELLO: 'WORLD' } }, + { embedding: { hello: 'world' } } + ]; + + const docs = docsInOrder.reduce((obj, doc) => { + obj[Math.random().toString()] = doc; + return obj; + }, {} as { [i: string]: DocumentData }); + + return withTestCollection(persistence, docs, async randomCol => { + // We validate that the SDK orders the vector field the same way as the backend + // by comparing the sort order of vector fields from getDocsFromServer and + // onSnapshot. onSnapshot will return sort order of the SDK, + // and getDocsFromServer will return sort order of the backend. + + const orderedQuery = query(randomCol, orderBy('embedding')); + const gotInitialSnapshot = new Deferred(); + + const unsubscribe = onSnapshot( + orderedQuery, + snapshot => { + gotInitialSnapshot.resolve(snapshot); + }, + err => { + gotInitialSnapshot.reject!(err); + } + ); + + const watchSnapshot = await gotInitialSnapshot.promise; + unsubscribe(); + + const getSnapshot = await getDocsFromServer(orderedQuery); + + // Compare the snapshot (including sort order) of a snapshot + // from Query.onSnapshot() to an actual snapshot from Query.get() + expect(toDataArray(watchSnapshot)).to.deep.equal( + toDataArray(getSnapshot) + ); + + // Compare the snapshot (including sort order) of a snapshot + // from Query.onSnapshot() to the expected sort order from + // the backend. + expect(toDataArray(watchSnapshot)).to.deep.equal(docsInOrder); + }); + }); + + // eslint-disable-next-line no-restricted-properties + (persistence.gc === 'lru' ? describe : describe.skip)('From Cache', () => { + it('SDK orders vector field the same way online and offline', async () => { + // Test data in the order that we expect the SDK to sort it. + const docsInOrder = [ + { embedding: [1, 2, 3, 4, 5, 6] }, + { embedding: [100] }, + { embedding: vector([Number.NEGATIVE_INFINITY]) }, + { embedding: vector([-100]) }, + { embedding: vector([100]) }, + { embedding: vector([Number.POSITIVE_INFINITY]) }, + { embedding: vector([1, 2]) }, + { embedding: vector([2, 2]) }, + { embedding: vector([1, 2, 3]) }, + { embedding: vector([1, 2, 3, 4]) }, + { embedding: vector([1, 2, 3, 4, 5]) }, + { embedding: vector([1, 2, 100, 4, 4]) }, + { embedding: vector([100, 2, 3, 4, 5]) }, + { embedding: { HELLO: 'WORLD' } }, + { embedding: { hello: 'world' } } + ]; + + const documentIds: string[] = []; + const docs = docsInOrder.reduce((obj, doc, index) => { + const documentId = index.toString(); + documentIds.push(documentId); + obj[documentId] = doc; + return obj; + }, {} as { [i: string]: DocumentData }); + + return withTestCollection(persistence, docs, async randomCol => { + const orderedQuery = query(randomCol, orderBy('embedding')); + await checkOnlineAndOfflineResultsMatch(orderedQuery, ...documentIds); + + const orderedQueryLessThan = query( + randomCol, + orderBy('embedding'), + where('embedding', '<', vector([1, 2, 100, 4, 4])) + ); + await checkOnlineAndOfflineResultsMatch( + orderedQueryLessThan, + ...documentIds.slice(2, 11) + ); + + const orderedQueryGreaterThan = query( + randomCol, + orderBy('embedding'), + where('embedding', '>', vector([1, 2, 100, 4, 4])) + ); + await checkOnlineAndOfflineResultsMatch( + orderedQueryGreaterThan, + ...documentIds.slice(12, 13) + ); + }); + }); + }); + }); + describe('documents: ', () => { const invalidDocValues = [undefined, null, 0, 'foo', ['a'], new Date()]; for (const val of invalidDocValues) { diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index b8145d3350b..780db5f4f9c 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -42,7 +42,8 @@ import { arrayUnion, deleteField, increment, - serverTimestamp + serverTimestamp, + vector } from '../../src/lite-api/field_value_impl'; import { endAt, @@ -2934,3 +2935,28 @@ describe('Aggregate queries - sum / average', () => { } ); }); + +describe('Vectors', () => { + it('can be read and written using the lite SDK', async () => { + return withTestCollection(async coll => { + const ref = await addDoc(coll, { + vector0: vector([0.0]), + vector1: vector([1, 2, 3.99]) + }); + await setDoc(ref, { + vector0: vector([0.0]), + vector1: vector([1, 2, 3.99]), + vector2: vector([0, 0, 0]) + }); + await updateDoc(ref, { + vector3: vector([-1, -200, -999]) + }); + + const snap1 = await getDoc(ref); + expect(snap1.get('vector0').isEqual(vector([0.0]))).to.be.true; + expect(snap1.get('vector1').isEqual(vector([1, 2, 3.99]))).to.be.true; + expect(snap1.get('vector2').isEqual(vector([0, 0, 0]))).to.be.true; + expect(snap1.get('vector3').isEqual(vector([-1, -200, -999]))).to.be.true; + }); + }); +}); diff --git a/packages/firestore/test/unit/index/firestore_index_value_writer.test.ts b/packages/firestore/test/unit/index/firestore_index_value_writer.test.ts index 8f7cd7c973f..8daa97eb77d 100644 --- a/packages/firestore/test/unit/index/firestore_index_value_writer.test.ts +++ b/packages/firestore/test/unit/index/firestore_index_value_writer.test.ts @@ -169,5 +169,82 @@ describe('Firestore Index Value Writer', () => { compareIndexEncodedValues(value3, value4, IndexKind.DESCENDING) ).to.equal(-1); }); + + it('sorts vector as a different type from array and map, with unique rules', () => { + const vector1 = { + mapValue: { + fields: { + '__type__': { stringValue: '__vector__' }, + 'value': { + arrayValue: { values: [{ doubleValue: 100 }] } + } + } + } + }; + const vector2 = { + mapValue: { + fields: { + '__type__': { stringValue: '__vector__' }, + 'value': { + arrayValue: { values: [{ doubleValue: 1 }, { doubleValue: 2 }] } + } + } + } + }; + const vector3 = { + mapValue: { + fields: { + '__type__': { stringValue: '__vector__' }, + 'value': { + arrayValue: { values: [{ doubleValue: 1 }, { doubleValue: 3 }] } + } + } + } + }; + const map1 = { + mapValue: { + fields: { + 'value': { + arrayValue: { values: [{ doubleValue: 1 }, { doubleValue: 2 }] } + } + } + } + }; + const array1 = { + arrayValue: { values: [{ doubleValue: 1 }, { doubleValue: 2 }] } + }; + + // Array sorts before vector + expect( + compareIndexEncodedValues(array1, vector1, IndexKind.ASCENDING) + ).to.equal(-1); + expect( + compareIndexEncodedValues(array1, vector1, IndexKind.DESCENDING) + ).to.equal(1); + + // Vector sorts before map + expect( + compareIndexEncodedValues(vector3, map1, IndexKind.ASCENDING) + ).to.equal(-1); + expect( + compareIndexEncodedValues(vector3, map1, IndexKind.DESCENDING) + ).to.equal(1); + + // Shorter vectors sort before longer vectors + expect( + compareIndexEncodedValues(vector1, vector2, IndexKind.ASCENDING) + ).to.equal(-1); + expect( + compareIndexEncodedValues(vector1, vector2, IndexKind.DESCENDING) + ).to.equal(1); + + // Vectors of the same length sort by value + expect( + compareIndexEncodedValues(vector2, vector3, IndexKind.ASCENDING) + ).to.equal(-1); + expect( + compareIndexEncodedValues(vector2, vector3, IndexKind.DESCENDING) + ).to.equal(1); + }); }); }); diff --git a/packages/firestore/test/unit/local/index_manager.test.ts b/packages/firestore/test/unit/local/index_manager.test.ts index 5d27fcc257f..2521be99bf5 100644 --- a/packages/firestore/test/unit/local/index_manager.test.ts +++ b/packages/firestore/test/unit/local/index_manager.test.ts @@ -30,6 +30,7 @@ import { queryWithLimit, queryWithStartAt } from '../../../src/core/query'; +import { vector } from '../../../src/lite-api/field_value_impl'; import { Timestamp } from '../../../src/lite-api/timestamp'; import { displayNameForIndexType, @@ -1100,6 +1101,38 @@ describe('IndexedDbIndexManager', async () => { await verifyResults(q, 'coll/val6', 'coll/val3', 'coll/val4', 'coll/val5'); }); + it('can index VectorValue fields', async () => { + await indexManager.addFieldIndex( + fieldIndex('coll', { fields: [['embedding', IndexKind.ASCENDING]] }) + ); + + await addDoc('coll/arr1', { 'embedding': [1, 2, 3] }); + await addDoc('coll/map2', { 'embedding': {} }); + await addDoc('coll/doc3', { 'embedding': vector([4, 5, 6]) }); + await addDoc('coll/doc4', { 'embedding': vector([5]) }); + + let q = queryWithAddedOrderBy(query('coll'), orderBy('embedding')); + await verifyResults(q, 'coll/arr1', 'coll/doc4', 'coll/doc3', 'coll/map2'); + + q = queryWithAddedFilter( + query('coll'), + filter('embedding', '==', vector([4, 5, 6])) + ); + await verifyResults(q, 'coll/doc3'); + + q = queryWithAddedFilter( + query('coll'), + filter('embedding', '>', vector([4, 5, 6])) + ); + await verifyResults(q); + + q = queryWithAddedFilter( + query('coll'), + filter('embedding', '>=', vector([4])) + ); + await verifyResults(q, 'coll/doc4', 'coll/doc3'); + }); + it('support advances queries', async () => { // This test compares local query results with those received from the Java // Server SDK. diff --git a/packages/firestore/test/unit/model/object_value.test.ts b/packages/firestore/test/unit/model/object_value.test.ts index d2f3140a191..9e96056d957 100644 --- a/packages/firestore/test/unit/model/object_value.test.ts +++ b/packages/firestore/test/unit/model/object_value.test.ts @@ -17,6 +17,7 @@ import { expect } from 'chai'; +import { vector } from '../../../src/lite-api/field_value_impl'; import { extractFieldMask, ObjectValue } from '../../../src/model/object_value'; import { TypeOrder } from '../../../src/model/type_order'; import { typeOrder } from '../../../src/model/values'; @@ -24,7 +25,10 @@ import { field, mask, wrap, wrapObject } from '../../util/helpers'; describe('ObjectValue', () => { it('can extract fields', () => { - const objValue = wrapObject({ foo: { a: 1, b: true, c: 'string' } }); + const objValue = wrapObject({ + foo: { a: 1, b: true, c: 'string' }, + embedding: vector([1]) + }); expect(typeOrder(objValue.field(field('foo'))!)).to.equal( TypeOrder.ObjectValue @@ -38,6 +42,9 @@ describe('ObjectValue', () => { expect(typeOrder(objValue.field(field('foo.c'))!)).to.equal( TypeOrder.StringValue ); + expect(typeOrder(objValue.field(field('embedding'))!)).to.equal( + TypeOrder.VectorValue + ); expect(objValue.field(field('foo.a.b'))).to.be.null; expect(objValue.field(field('bar'))).to.be.null; diff --git a/packages/firestore/test/unit/model/values.test.ts b/packages/firestore/test/unit/model/values.test.ts index 03dc7fb3cd3..722d2db6fa5 100644 --- a/packages/firestore/test/unit/model/values.test.ts +++ b/packages/firestore/test/unit/model/values.test.ts @@ -19,6 +19,7 @@ import { expect } from 'chai'; import { GeoPoint, Timestamp } from '../../../src'; import { DatabaseId } from '../../../src/core/database_info'; +import { vector } from '../../../src/lite-api/field_value_impl'; import { serverTimestamp } from '../../../src/model/server_timestamps'; import { canonicalId, @@ -28,7 +29,10 @@ import { refValue, deepClone, valuesGetLowerBound, - valuesGetUpperBound + valuesGetUpperBound, + TYPE_KEY, + VECTOR_VALUE_SENTINEL, + VECTOR_MAP_VECTORS_KEY } from '../../../src/model/values'; import * as api from '../../../src/protos/firestore_proto_api'; import { primitiveComparator } from '../../../src/util/misc'; @@ -86,7 +90,9 @@ describe('Values', () => { [wrap({ bar: 1, foo: 2 }), wrap({ foo: 2, bar: 1 })], [wrap({ bar: 2, foo: 1 })], [wrap({ bar: 1, foo: 1 })], - [wrap({ foo: 1 })] + [wrap({ foo: 1 })], + [wrap(vector([]))], + [wrap(vector([1, 2.3, -4.0]))] ]; expectEqualitySets(values, (v1, v2) => valueEquals(v1, v2)); }); @@ -211,6 +217,11 @@ describe('Values', () => { [wrap(['foo', 2])], [wrap(['foo', '0'])], + // vectors + [wrap(vector([100]))], + [wrap(vector([1, 2, 3]))], + [wrap(vector([1, 3, 2]))], + // objects [wrap({ bar: 0 })], [wrap({ bar: 0, foo: 1 })], @@ -316,6 +327,10 @@ describe('Values', () => { { expectedByteSize: 6, elements: [wrap({ a: 'a', b: 'b' }), wrap({ c: 'c', d: 'd' })] + }, + { + expectedByteSize: 49, + elements: [wrap(vector([1, 2])), wrap(vector([-100, 20000098.123445]))] } ]; @@ -344,7 +359,9 @@ describe('Values', () => { [wrap(['a', 'b']), wrap(['a', 'b', 'c'])], [wrap({ a: 'a', b: 'b' }), wrap({ a: 'a', b: 'bc' })], [wrap({ a: 'a', b: 'b' }), wrap({ a: 'a', bc: 'b' })], - [wrap({ a: 'a', b: 'b' }), wrap({ a: 'a', b: 'b', c: 'c' })] + [wrap({ a: 'a', b: 'b' }), wrap({ a: 'a', b: 'b', c: 'c' })], + [wrap({ a: 'a', b: 'b' }), wrap({ a: 'a', b: 'b', c: 'c' })], + [wrap(vector([2, 3])), wrap(vector([1, 2, 3]))] ]; for (const group of relativeGroups) { @@ -398,6 +415,23 @@ describe('Values', () => { [valuesGetLowerBound({ arrayValue: {} }), wrap([])], [wrap([false])], + // vectors + [ + valuesGetLowerBound({ + mapValue: { + fields: { + [TYPE_KEY]: { stringValue: VECTOR_VALUE_SENTINEL }, + [VECTOR_MAP_VECTORS_KEY]: { + arrayValue: { + values: [{ doubleValue: 1 }] + } + } + } + } + }), + wrap(vector([])) + ], + // objects [valuesGetLowerBound({ mapValue: {} }), wrap({})] ]; @@ -486,6 +520,9 @@ describe('Values', () => { }) ) ).to.equal('{a:1,b:2,c:3}'); + expect(canonicalId(wrap(vector([1, 1.0, -2, 3.14])))).to.equal( + '{__type__:__vector__,value:[1,1,-2,3.14]}' + ); expect( canonicalId(wrap({ 'a': ['b', { 'c': new GeoPoint(30, 60) }] })) ).to.equal('{a:[b,{c:geo(30,60)}]}'); diff --git a/packages/firestore/test/unit/remote/serializer.helper.ts b/packages/firestore/test/unit/remote/serializer.helper.ts index 969bbe7eb5a..d523c8fab83 100644 --- a/packages/firestore/test/unit/remote/serializer.helper.ts +++ b/packages/firestore/test/unit/remote/serializer.helper.ts @@ -52,6 +52,7 @@ import { } from '../../../src/core/query'; import { SnapshotVersion } from '../../../src/core/snapshot_version'; import { Target, targetEquals, TargetImpl } from '../../../src/core/target'; +import { vector } from '../../../src/lite-api/field_value_impl'; import { parseQueryValue } from '../../../src/lite-api/user_data_reader'; import { TargetData, TargetPurpose } from '../../../src/local/target_data'; import { FieldMask } from '../../../src/model/field_mask'; @@ -535,6 +536,35 @@ export function serializerTest( 'projects/test-project/databases/(default)/documents/docs/1' }); }); + + it('converts VectorValue', () => { + const original = vector([1, 2, 3]); + const objValue = wrap(original); + expect(userDataWriter.convertValue(objValue)).to.deep.equal(original); + + const expectedJson: api.Value = { + mapValue: { + fields: { + '__type__': { stringValue: '__vector__' }, + value: { + arrayValue: { + values: [ + { doubleValue: 1 }, + { doubleValue: 2 }, + { doubleValue: 3 } + ] + } + } + } + } + }; + + verifyFieldValueRoundTrip({ + value: original, + valueType: 'mapValue', + jsonValue: expectedJson.mapValue + }); + }); }); describe('toKey', () => { diff --git a/scripts/emulator-testing/emulators/firestore-emulator.ts b/scripts/emulator-testing/emulators/firestore-emulator.ts index 57186af0f64..c8cdf811c02 100644 --- a/scripts/emulator-testing/emulators/firestore-emulator.ts +++ b/scripts/emulator-testing/emulators/firestore-emulator.ts @@ -26,7 +26,7 @@ export class FirestoreEmulator extends Emulator { // Use locked version of emulator for test to be deterministic. // The latest version can be found from firestore emulator doc: // https://firebase.google.com/docs/firestore/security/test-rules-emulator - 'https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.18.2.jar', + 'https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.19.7.jar', port ); this.projectId = projectId;