googleapis / nodejs-firestore

Node.js client for Google Cloud Firestore: a NoSQL document database built for automatic scaling, high performance, and ease of application development.
https://cloud.google.com/firestore/
Apache License 2.0
642 stars 150 forks source link

Make UpdateData type Generic to fix lack of type safety when using withConverter() #1448

Closed JamieCurnow closed 2 years ago

JamieCurnow commented 3 years ago

Is your feature request related to a problem? Please describe. This package works great with Typescript when using the .withConverter method and enforces correct types when performing a get() or set() or onSnapshot(), but fails to enforce types on the update() method.

With TS v4.1 we can now safely enforce that dot-notation strings as object keys are correct, so we can do better than:

export type UpdateData = {[fieldPath: string]: any};

Check it out...

import { firestore } from "firebase-admin"

interface User {
  name: string
  email: string
  address: {
    line1: string
    line2: string
    postcode: string
    verified: boolean
    timeAtAddress: {
      days: string
      months: string
      hours: string
    }
  }
}

// This helper function pipes my types through the converter
const converter = <T>() => ({
  toFirestore: (data: Partial<T>) => data,
  fromFirestore: (snap: FirebaseFirestore.QueryDocumentSnapshot) => snap.data() as T
})

const getUser = async () => {
  const userDoc = await firestore().collection('user').withConverter(converter<User>()).doc('1234').get()
  if (!userDoc.exists) return null
  const userData = userDoc.data()
  console.log(userData.email) // nice!
  console.log(userData.unknownKey)
  // TS Error: Property 'unknownKey' does not exist on type 'User'.
  // Great!
}

const setUser = async () => {
  await firestore().collection('user').withConverter(converter<User>()).doc('1234').set({
    email: '',
    name: ''
  })
  // TS Error: Property 'address' is missing in type '{ email: string; name: string; }' but required in type 'User'
  // Great!
}

const setUserMerged = async () => {
  await firestore().collection('user').withConverter(converter<User>()).doc('1234').set({
    email: '',
    name: '',
    unknownKey: ''
  }, { merge: true })
  // TS Error: Object literal may only specify known properties, and 'unknownKey' does not exist in type 'Partial<User>'.
  // Great!
}

const updateUserUnsafe = async () => {
  await firestore().collection('user').withConverter(converter<User>()).doc('1234').update({
    unknownKey: '', // no error here
    'anything.I.like': true // no type error
  })
}

// Here's how we could do an update with type safety:

type PathImpl<T, K extends keyof T> =
  K extends string
  ? T[K] extends Record<string, any>
  ? T[K] extends ArrayLike<any>
  ? K | `${K}.${PathImpl<T[K], Exclude<keyof T[K], keyof any[]>>}`
  : K | `${K}.${PathImpl<T[K], keyof T[K]>}`
  : K
  : never

type Path<T> = PathImpl<T, keyof T> | keyof T

type PathValue<T, P extends Path<T>> =
  P extends `${infer K}.${infer Rest}`
  ? K extends keyof T
  ? Rest extends Path<T[K]>
  ? PathValue<T[K], Rest>
  : never
  : never
  : P extends keyof T
  ? T[P]
  : never

type CustomUpdateData<T extends object> = Partial<{
  [TKey in Path<T>]: PathValue<T, TKey>
}>

const updateUserSafe = async () => {
  // Here I make the object first which provides some safety
  const updatesObject: CustomUpdateData<User> = {
    name: '',
    'address.timeAtAddress.days': '',
    'some.unknown.path': false,
    // Object literal may only specify known properties, and ''some.unknown.path'' does not exist in type 'Partial<CustomUpdateData<User>>'
    'address.verified': 'Oops! Wrong value type!'
    // Type 'string' is not assignable to type 'boolean'
  }

  await firestore().collection('user').withConverter(converter<User>()).doc('1234').update(updatesObject)
}

Describe the solution you'd like I propose that the helper types for creating a type that is able to handle object paths should be added to FirebaseFirestore.UpdateData. I think that a solution could look like this:

// @google-cloud/firestore/types/firestore.d.ts

type PathImpl<T, K extends keyof T> =
  K extends string
  ? T[K] extends Record<string, any>
  ? T[K] extends ArrayLike<any>
  ? K | `${K}.${PathImpl<T[K], Exclude<keyof T[K], keyof any[]>>}`
  : K | `${K}.${PathImpl<T[K], keyof T[K]>}`
  : K
  : never

type Path<T> = PathImpl<T, keyof T> | keyof T

type PathValue<T, P extends Path<T>> =
  P extends `${infer K}.${infer Rest}`
  ? K extends keyof T
  ? Rest extends Path<T[K]>
  ? PathValue<T[K], Rest>
  : never
  : never
  : P extends keyof T
  ? T[P]
  : never

declare namespace FirebaseFirestore {
  export type DocumentData = { [field: string]: any }

  export type UpdateData<T = DocumentData> = Partial<{
    [TKey in Path<T>]: PathValue<T, TKey>
  }>

  // ...

  export class DocumentReference<T = DocumentData> {
    // ...
    update(data: UpdateData<T>, precondition?: Precondition): Promise<WriteResult>
  }
}

Which would allow type safety like this:

const updateSafe = async () => {
  await firestore().collection('user').withConverter(converter<User>()).doc('1234').update({
    name: '',
    'address.line1': '',
    'some.unknown.path': false,
    // Object literal may only specify known properties, and ''some.unknown.path'' does not exist in type 'Partial<CustomUpdateData<User>>'
    'address.verified': 'Oops! Wrong value type!'
    // Type 'string' is not assignable to type 'boolean'
  })
}

Additional context Proposed solution in action:

Screenshot 2021-03-12 at 12 42 17
JamieCurnow commented 3 years ago

This would require typescript to be updated from 3.8.3 to at least 4.1.0 which will probably be a block.

schmidt-sebastian commented 3 years ago

Thanks for suggesting this. We are considering this for our next breaking change release (based on a similar suggestion here: https://github.com/firebase/firebase-js-sdk/issues/4277)

rgant commented 2 years ago

I think maybe this is causing a problem conflict between firebase-admin@10.0.1 and @google-cloud/firestore@5.0.1.

node_modules/@google-cloud/firestore/types/firestore.d.ts:23:1 - error TS6200: Definitions of the following identifiers conflict with those in another file: DocumentData, UpdateData, Firestore, GeoPoint, Transaction, BulkWriter, BulkWriterError, WriteBatch, SetOptions, WriteResult, DocumentReference, DocumentSnapshot, QueryDocumentSnapshot, OrderByDirection, WhereFilterOp, Query, QuerySnapshot, DocumentChangeType, CollectionReference, CollectionGroup, QueryPartition, FieldValue, FieldPath, Timestamp, BundleBuilder, v1beta1, v1, OK, CANCELLED, UNKNOWN, INVALID_ARGUMENT, DEADLINE_EXCEEDED, NOT_FOUND, ALREADY_EXISTS, PERMISSION_DENIED, RESOURCE_EXHAUSTED, FAILED_PRECONDITION, ABORTED, OUT_OF_RANGE, UNIMPLEMENTED, INTERNAL, UNAVAILABLE, DATA_LOSS, UNAUTHENTICATED, FirebaseFirestore

23 declare namespace FirebaseFirestore {
   ~~~~~~~

  node_modules/firebase-admin/node_modules/@google-cloud/firestore/types/firestore.d.ts:23:1
    23 declare namespace FirebaseFirestore {
       ~~~~~~~
    Conflicts are in this file.

node_modules/@google-cloud/firestore/types/firestore.d.ts:71:25 - error TS2315: Type 'UpdateData' is not generic.

71     ? {[K in keyof T]?: UpdateData<T[K]> | FieldValue} & NestedUpdateFields<T>
                           ~~~~~~~~~~~~~~~~

node_modules/@google-cloud/firestore/types/firestore.d.ts:104:28 - error TS2315: Type 'UpdateData' is not generic.

104         AddPrefixToKeys<K, UpdateData<V>>
                               ~~~~~~~~~~~~~

node_modules/@google-cloud/firestore/types/firestore.d.ts:282:5 - error TS2374: Duplicate index signature for type 'string'.

282     [key: string]: any; // Accept other properties, such as GRPC settings.
        ~~~~~~~~~~~~~~~~~~~

node_modules/@google-cloud/firestore/types/firestore.d.ts:644:13 - error TS2315: Type 'UpdateData' is not generic.

644       data: UpdateData<T>,
                ~~~~~~~~~~~~~

node_modules/@google-cloud/firestore/types/firestore.d.ts:789:13 - error TS2315: Type 'UpdateData' is not generic.

789       data: UpdateData<T>,
                ~~~~~~~~~~~~~

node_modules/@google-cloud/firestore/types/firestore.d.ts:999:13 - error TS2315: Type 'UpdateData' is not generic.

999       data: UpdateData<T>,
                ~~~~~~~~~~~~~

node_modules/@google-cloud/firestore/types/firestore.d.ts:1209:13 - error TS2315: Type 'UpdateData' is not generic.

1209       data: UpdateData<T>,
                 ~~~~~~~~~~~~~

node_modules/firebase-admin/node_modules/@google-cloud/firestore/types/firestore.d.ts:23:1 - error TS6200: Definitions of the following identifiers conflict with those in another file: DocumentData, UpdateData, Firestore, GeoPoint, Transaction, BulkWriter, BulkWriterError, WriteBatch, SetOptions, WriteResult, DocumentReference, DocumentSnapshot, QueryDocumentSnapshot, OrderByDirection, WhereFilterOp, Query, QuerySnapshot, DocumentChangeType, CollectionReference, CollectionGroup, QueryPartition, FieldValue, FieldPath, Timestamp, BundleBuilder, v1beta1, v1, OK, CANCELLED, UNKNOWN, INVALID_ARGUMENT, DEADLINE_EXCEEDED, NOT_FOUND, ALREADY_EXISTS, PERMISSION_DENIED, RESOURCE_EXHAUSTED, FAILED_PRECONDITION, ABORTED, OUT_OF_RANGE, UNIMPLEMENTED, INTERNAL, UNAVAILABLE, DATA_LOSS, UNAUTHENTICATED, FirebaseFirestore

23 declare namespace FirebaseFirestore {
   ~~~~~~~

  node_modules/@google-cloud/firestore/types/firestore.d.ts:23:1
    23 declare namespace FirebaseFirestore {
       ~~~~~~~
    Conflicts are in this file.

node_modules/firebase-admin/node_modules/@google-cloud/firestore/types/firestore.d.ts:174:5 - error TS2374: Duplicate index signature for type 'string'.

174     [key: string]: any; // Accept other properties, such as GRPC settings.
        ~~~~~~~~~~~~~~~~~~~

Found 10 errors.

Seems like the TypeScript version for this package is already updated to support this.

It always surprises me that all the different node Firebase packages have different implementations of the types for Firebase...

schmidt-sebastian commented 2 years ago

@rgant Your error suggests that you are using two different versions of Firestore. Can you confirm whether your build is using more than one version?

Unrelated to this, since we published 5.x, this issue can now be closed. We have updated our converter code to enforce type safety for update() calls.

rgant commented 2 years ago

I believe these are the latest versions of both tools:

firebase-admin@10.0.1 - For Firebase Functions to access Firestore. (https://firebase.google.com/docs/functions/get-started)

@google-cloud/firestore@5.0.1 - To setup a scheduled backup of Firestore. (https://firebase.google.com/docs/firestore/solutions/schedule-export)

Both as per the documentation best I can tell. Seems odd these two tools aren't in sync with the types.

Any advice where I should post this issue? To my eye, @google-cloud/firestore@5 types are less useful compared to firebase-admin@10.

schmidt-sebastian commented 2 years ago

Can you post the output of "npm list"?

rgant commented 2 years ago

Click to expand:

`npm list` ``` learnlux-cloud-firestore@1.0.5 /Users/rgant/Programming/LearnLux/firestore-data ├─┬ @firebase/rules-unit-testing@2.0.1 │ └── node-fetch@2.6.2 ├─┬ @google-cloud/firestore@5.0.1 │ ├── fast-deep-equal@3.1.3 │ ├── functional-red-black-tree@1.0.1 │ ├─┬ google-gax@2.28.1 │ │ ├─┬ @grpc/grpc-js@1.4.5 │ │ │ ├── @grpc/proto-loader@0.6.7 deduped │ │ │ └── @types/node@14.18.2 deduped │ │ ├─┬ @grpc/proto-loader@0.6.7 │ │ │ ├── @types/long@4.0.1 deduped │ │ │ ├── lodash.camelcase@4.3.0 │ │ │ ├── long@4.0.0 deduped │ │ │ ├── protobufjs@6.11.2 deduped │ │ │ └─┬ yargs@16.2.0 │ │ │ ├─┬ cliui@7.0.4 │ │ │ │ ├── string-width@4.2.3 deduped │ │ │ │ ├── strip-ansi@6.0.1 deduped │ │ │ │ └─┬ wrap-ansi@7.0.0 │ │ │ │ ├── ansi-styles@4.3.0 deduped │ │ │ │ ├── string-width@4.2.3 deduped │ │ │ │ └── strip-ansi@6.0.1 deduped │ │ │ ├── escalade@3.1.1 │ │ │ ├── get-caller-file@2.0.5 │ │ │ ├── require-directory@2.1.1 │ │ │ ├─┬ string-width@4.2.3 │ │ │ │ ├── emoji-regex@8.0.0 │ │ │ │ ├── is-fullwidth-code-point@3.0.0 │ │ │ │ └── strip-ansi@6.0.1 deduped │ │ │ ├── y18n@5.0.8 │ │ │ └── yargs-parser@20.2.9 │ │ ├── @types/long@4.0.1 │ │ ├─┬ abort-controller@3.0.0 │ │ │ └── event-target-shim@5.0.1 │ │ ├─┬ duplexify@4.1.2 │ │ │ ├─┬ end-of-stream@1.4.4 │ │ │ │ └── once@1.4.0 deduped │ │ │ ├── inherits@2.0.4 deduped │ │ │ ├─┬ readable-stream@3.6.0 │ │ │ │ ├── inherits@2.0.4 deduped │ │ │ │ ├─┬ string_decoder@1.3.0 │ │ │ │ │ └── safe-buffer@5.2.1 deduped │ │ │ │ └── util-deprecate@1.0.2 │ │ │ └── stream-shift@1.0.1 │ │ ├── fast-text-encoding@1.0.3 │ │ ├─┬ google-auth-library@7.11.0 │ │ │ ├── arrify@2.0.1 deduped │ │ │ ├── base64-js@1.5.1 │ │ │ ├─┬ ecdsa-sig-formatter@1.0.11 │ │ │ │ └── safe-buffer@5.2.1 deduped │ │ │ ├── fast-text-encoding@1.0.3 deduped │ │ │ ├─┬ gaxios@4.3.2 │ │ │ │ ├── abort-controller@3.0.0 deduped │ │ │ │ ├── extend@3.0.2 deduped │ │ │ │ ├─┬ https-proxy-agent@5.0.0 │ │ │ │ │ ├─┬ agent-base@6.0.2 │ │ │ │ │ │ └── debug@4.3.3 deduped │ │ │ │ │ └── debug@4.3.3 deduped │ │ │ │ ├── is-stream@2.0.1 │ │ │ │ └── node-fetch@2.6.6 deduped │ │ │ ├─┬ gcp-metadata@4.3.1 │ │ │ │ ├── gaxios@4.3.2 deduped │ │ │ │ └─┬ json-bigint@1.0.0 │ │ │ │ └── bignumber.js@9.0.2 │ │ │ ├─┬ gtoken@5.3.1 │ │ │ │ ├── gaxios@4.3.2 deduped │ │ │ │ ├─┬ google-p12-pem@3.1.2 │ │ │ │ │ └── node-forge@0.10.0 deduped │ │ │ │ └── jws@4.0.0 deduped │ │ │ ├─┬ jws@4.0.0 │ │ │ │ ├─┬ jwa@2.0.0 │ │ │ │ │ ├── buffer-equal-constant-time@1.0.1 deduped │ │ │ │ │ ├── ecdsa-sig-formatter@1.0.11 deduped │ │ │ │ │ └── safe-buffer@5.2.1 deduped │ │ │ │ └── safe-buffer@5.2.1 deduped │ │ │ └── lru-cache@6.0.0 deduped │ │ ├── is-stream-ended@0.1.4 │ │ ├── node-fetch@2.6.6 deduped │ │ ├── object-hash@2.2.0 │ │ ├─┬ proto3-json-serializer@0.1.6 │ │ │ └── protobufjs@6.11.2 deduped │ │ ├── protobufjs@6.11.2 deduped │ │ └─┬ retry-request@4.2.2 │ │ ├── debug@4.3.3 deduped │ │ └── extend@3.0.2 deduped │ └─┬ protobufjs@6.11.2 │ ├── @protobufjs/aspromise@1.1.2 │ ├── @protobufjs/base64@1.1.2 │ ├── @protobufjs/codegen@2.0.4 │ ├── @protobufjs/eventemitter@1.1.0 │ ├─┬ @protobufjs/fetch@1.1.0 │ │ ├── @protobufjs/aspromise@1.1.2 deduped │ │ └── @protobufjs/inquire@1.1.0 deduped │ ├── @protobufjs/float@1.0.2 │ ├── @protobufjs/inquire@1.1.0 │ ├── @protobufjs/path@1.1.2 │ ├── @protobufjs/pool@1.1.0 │ ├── @protobufjs/utf8@1.1.0 │ ├── @types/long@4.0.1 deduped │ ├── @types/node@14.18.2 deduped │ └── long@4.0.0 ├── @types/jasmine@3.10.2 ├── @types/node@14.18.2 ├─┬ @types/node-fetch@2.5.12 │ ├── @types/node@14.18.2 deduped │ └─┬ form-data@3.0.1 │ ├── asynckit@0.4.0 │ ├─┬ combined-stream@1.0.8 │ │ └── delayed-stream@1.0.0 │ └─┬ mime-types@2.1.34 │ └── mime-db@1.51.0 ├─┬ @typescript-eslint/eslint-plugin@5.8.0 │ ├─┬ @typescript-eslint/experimental-utils@5.8.0 │ │ ├── @types/json-schema@7.0.9 │ │ ├── @typescript-eslint/scope-manager@5.8.0 deduped │ │ ├── @typescript-eslint/types@5.8.0 deduped │ │ ├── @typescript-eslint/typescript-estree@5.8.0 deduped │ │ ├─┬ eslint-scope@5.1.1 │ │ │ ├── esrecurse@4.3.0 deduped │ │ │ └── estraverse@4.3.0 │ │ └── eslint-utils@3.0.0 deduped │ ├─┬ @typescript-eslint/scope-manager@5.8.0 │ │ ├── @typescript-eslint/types@5.8.0 deduped │ │ └─┬ @typescript-eslint/visitor-keys@5.8.0 │ │ ├── @typescript-eslint/types@5.8.0 deduped │ │ └── eslint-visitor-keys@3.1.0 deduped │ ├─┬ debug@4.3.3 │ │ └── ms@2.1.2 │ ├── functional-red-black-tree@1.0.1 deduped │ ├── ignore@5.2.0 │ ├── regexpp@3.2.0 │ ├─┬ semver@7.3.5 │ │ └─┬ lru-cache@6.0.0 │ │ └── yallist@4.0.0 │ └─┬ tsutils@3.21.0 │ └── tslib@1.14.1 ├─┬ @typescript-eslint/eslint-plugin-tslint@5.8.0 │ ├── @typescript-eslint/experimental-utils@5.8.0 deduped │ └── lodash@4.17.21 ├─┬ @typescript-eslint/parser@5.8.0 │ ├── @typescript-eslint/scope-manager@5.8.0 deduped │ ├── @typescript-eslint/types@5.8.0 │ ├─┬ @typescript-eslint/typescript-estree@5.8.0 │ │ ├── @typescript-eslint/types@5.8.0 deduped │ │ ├── @typescript-eslint/visitor-keys@5.8.0 deduped │ │ ├── debug@4.3.3 deduped │ │ ├─┬ globby@11.0.4 │ │ │ ├── array-union@2.1.0 │ │ │ ├─┬ dir-glob@3.0.1 │ │ │ │ └── path-type@4.0.0 │ │ │ ├─┬ fast-glob@3.2.7 │ │ │ │ ├── @nodelib/fs.stat@2.0.5 │ │ │ │ ├─┬ @nodelib/fs.walk@1.2.8 │ │ │ │ │ ├─┬ @nodelib/fs.scandir@2.1.5 │ │ │ │ │ │ ├── @nodelib/fs.stat@2.0.5 deduped │ │ │ │ │ │ └─┬ run-parallel@1.2.0 │ │ │ │ │ │ └── queue-microtask@1.2.3 │ │ │ │ │ └─┬ fastq@1.13.0 │ │ │ │ │ └── reusify@1.0.4 │ │ │ │ ├─┬ glob-parent@5.1.2 │ │ │ │ │ └── is-glob@4.0.3 deduped │ │ │ │ ├── merge2@1.4.1 deduped │ │ │ │ └─┬ micromatch@4.0.4 │ │ │ │ ├─┬ braces@3.0.2 │ │ │ │ │ └─┬ fill-range@7.0.1 │ │ │ │ │ └─┬ to-regex-range@5.0.1 │ │ │ │ │ └── is-number@7.0.0 │ │ │ │ └── picomatch@2.3.0 │ │ │ ├── ignore@5.2.0 deduped │ │ │ ├── merge2@1.4.1 │ │ │ └── slash@3.0.0 │ │ ├── is-glob@4.0.3 deduped │ │ ├─┬ semver@7.3.5 │ │ │ └── lru-cache@6.0.0 deduped │ │ └── tsutils@3.21.0 deduped │ └── debug@4.3.3 deduped ├─┬ eslint@8.5.0 │ ├─┬ @eslint/eslintrc@1.0.5 │ │ ├── ajv@6.12.6 deduped │ │ ├── debug@4.3.3 deduped │ │ ├── espree@9.2.0 deduped │ │ ├── globals@13.12.0 deduped │ │ ├── ignore@4.0.6 │ │ ├── import-fresh@3.3.0 deduped │ │ ├── js-yaml@4.1.0 deduped │ │ ├── minimatch@3.0.4 deduped │ │ └── strip-json-comments@3.1.1 deduped │ ├─┬ @humanwhocodes/config-array@0.9.2 │ │ ├── @humanwhocodes/object-schema@1.2.1 │ │ ├── debug@4.3.3 deduped │ │ └── minimatch@3.0.4 deduped │ ├─┬ ajv@6.12.6 │ │ ├── fast-deep-equal@3.1.3 deduped │ │ ├── fast-json-stable-stringify@2.1.0 │ │ ├── json-schema-traverse@0.4.1 │ │ └─┬ uri-js@4.4.1 │ │ └── punycode@2.1.1 │ ├─┬ chalk@4.1.2 │ │ ├─┬ ansi-styles@4.3.0 │ │ │ └─┬ color-convert@2.0.1 │ │ │ └── color-name@1.1.4 │ │ └─┬ supports-color@7.2.0 │ │ └── has-flag@4.0.0 │ ├─┬ cross-spawn@7.0.3 │ │ ├── path-key@3.1.1 │ │ ├─┬ shebang-command@2.0.0 │ │ │ └── shebang-regex@3.0.0 │ │ └─┬ which@2.0.2 │ │ └── isexe@2.0.0 │ ├── debug@4.3.3 deduped │ ├─┬ doctrine@3.0.0 │ │ └── esutils@2.0.3 deduped │ ├─┬ enquirer@2.3.6 │ │ └── ansi-colors@4.1.1 │ ├── escape-string-regexp@4.0.0 │ ├─┬ eslint-scope@7.1.0 │ │ ├─┬ esrecurse@4.3.0 │ │ │ └── estraverse@5.3.0 │ │ └── estraverse@5.3.0 │ ├─┬ eslint-utils@3.0.0 │ │ └── eslint-visitor-keys@2.1.0 │ ├── eslint-visitor-keys@3.1.0 │ ├─┬ espree@9.2.0 │ │ ├── acorn@8.6.0 deduped │ │ ├── acorn-jsx@5.3.2 │ │ └── eslint-visitor-keys@3.1.0 deduped │ ├─┬ esquery@1.4.0 │ │ └── estraverse@5.3.0 │ ├── esutils@2.0.3 │ ├── fast-deep-equal@3.1.3 deduped │ ├─┬ file-entry-cache@6.0.1 │ │ └─┬ flat-cache@3.0.4 │ │ ├── flatted@3.2.4 │ │ └─┬ rimraf@3.0.2 │ │ └── glob@7.2.0 deduped │ ├── functional-red-black-tree@1.0.1 deduped │ ├─┬ glob-parent@6.0.2 │ │ └── is-glob@4.0.3 deduped │ ├─┬ globals@13.12.0 │ │ └── type-fest@0.20.2 │ ├── ignore@4.0.6 │ ├─┬ import-fresh@3.3.0 │ │ ├─┬ parent-module@1.0.1 │ │ │ └── callsites@3.1.0 │ │ └── resolve-from@4.0.0 │ ├── imurmurhash@0.1.4 │ ├─┬ is-glob@4.0.3 │ │ └── is-extglob@2.1.1 │ ├─┬ js-yaml@4.1.0 │ │ └── argparse@2.0.1 │ ├── json-stable-stringify-without-jsonify@1.0.1 │ ├─┬ levn@0.4.1 │ │ ├── prelude-ls@1.2.1 │ │ └─┬ type-check@0.4.0 │ │ └── prelude-ls@1.2.1 deduped │ ├── lodash.merge@4.6.2 │ ├─┬ minimatch@3.0.4 │ │ └─┬ brace-expansion@1.1.11 │ │ ├── balanced-match@1.0.2 │ │ └── concat-map@0.0.1 │ ├── natural-compare@1.4.0 │ ├─┬ optionator@0.9.1 │ │ ├── deep-is@0.1.4 │ │ ├── fast-levenshtein@2.0.6 │ │ ├── levn@0.4.1 deduped │ │ ├── prelude-ls@1.2.1 deduped │ │ ├── type-check@0.4.0 deduped │ │ └── word-wrap@1.2.3 │ ├── progress@2.0.3 │ ├── regexpp@3.2.0 deduped │ ├─┬ semver@7.3.5 │ │ └── lru-cache@6.0.0 deduped │ ├─┬ strip-ansi@6.0.1 │ │ └── ansi-regex@5.0.1 │ ├── strip-json-comments@3.1.1 │ ├── text-table@0.2.0 │ └── v8-compile-cache@2.3.0 ├─┬ eslint-import-resolver-typescript@2.5.0 │ ├── debug@4.3.3 deduped │ ├─┬ glob@7.2.0 │ │ ├── fs.realpath@1.0.0 │ │ ├─┬ inflight@1.0.6 │ │ │ ├── once@1.4.0 deduped │ │ │ └── wrappy@1.0.2 │ │ ├── inherits@2.0.4 │ │ ├── minimatch@3.0.4 deduped │ │ ├─┬ once@1.4.0 │ │ │ └── wrappy@1.0.2 deduped │ │ └── path-is-absolute@1.0.1 │ ├── is-glob@4.0.3 deduped │ ├─┬ resolve@1.20.0 │ │ ├── is-core-module@2.8.0 deduped │ │ └── path-parse@1.0.7 │ └── tsconfig-paths@3.12.0 deduped ├─┬ eslint-plugin-import@2.25.3 │ ├─┬ array-includes@3.1.4 │ │ ├─┬ call-bind@1.0.2 │ │ │ ├── function-bind@1.1.1 deduped │ │ │ └── get-intrinsic@1.1.1 deduped │ │ ├─┬ define-properties@1.1.3 │ │ │ └── object-keys@1.1.1 │ │ ├─┬ es-abstract@1.19.1 │ │ │ ├── call-bind@1.0.2 deduped │ │ │ ├─┬ es-to-primitive@1.2.1 │ │ │ │ ├── is-callable@1.2.4 deduped │ │ │ │ ├─┬ is-date-object@1.0.5 │ │ │ │ │ └── has-tostringtag@1.0.0 deduped │ │ │ │ └─┬ is-symbol@1.0.4 │ │ │ │ └── has-symbols@1.0.2 deduped │ │ │ ├── function-bind@1.1.1 deduped │ │ │ ├── get-intrinsic@1.1.1 deduped │ │ │ ├─┬ get-symbol-description@1.0.0 │ │ │ │ ├── call-bind@1.0.2 deduped │ │ │ │ └── get-intrinsic@1.1.1 deduped │ │ │ ├── has@1.0.3 deduped │ │ │ ├── has-symbols@1.0.2 │ │ │ ├─┬ internal-slot@1.0.3 │ │ │ │ ├── get-intrinsic@1.1.1 deduped │ │ │ │ ├── has@1.0.3 deduped │ │ │ │ └─┬ side-channel@1.0.4 │ │ │ │ ├── call-bind@1.0.2 deduped │ │ │ │ ├── get-intrinsic@1.1.1 deduped │ │ │ │ └── object-inspect@1.12.0 deduped │ │ │ ├── is-callable@1.2.4 │ │ │ ├── is-negative-zero@2.0.2 │ │ │ ├─┬ is-regex@1.1.4 │ │ │ │ ├── call-bind@1.0.2 deduped │ │ │ │ └── has-tostringtag@1.0.0 deduped │ │ │ ├── is-shared-array-buffer@1.0.1 │ │ │ ├── is-string@1.0.7 deduped │ │ │ ├─┬ is-weakref@1.0.2 │ │ │ │ └── call-bind@1.0.2 deduped │ │ │ ├── object-inspect@1.12.0 │ │ │ ├── object-keys@1.1.1 deduped │ │ │ ├─┬ object.assign@4.1.2 │ │ │ │ ├── call-bind@1.0.2 deduped │ │ │ │ ├── define-properties@1.1.3 deduped │ │ │ │ ├── has-symbols@1.0.2 deduped │ │ │ │ └── object-keys@1.1.1 deduped │ │ │ ├─┬ string.prototype.trimend@1.0.4 │ │ │ │ ├── call-bind@1.0.2 deduped │ │ │ │ └── define-properties@1.1.3 deduped │ │ │ ├─┬ string.prototype.trimstart@1.0.4 │ │ │ │ ├── call-bind@1.0.2 deduped │ │ │ │ └── define-properties@1.1.3 deduped │ │ │ └─┬ unbox-primitive@1.0.1 │ │ │ ├── function-bind@1.1.1 deduped │ │ │ ├── has-bigints@1.0.1 │ │ │ ├── has-symbols@1.0.2 deduped │ │ │ └─┬ which-boxed-primitive@1.0.2 │ │ │ ├─┬ is-bigint@1.0.4 │ │ │ │ └── has-bigints@1.0.1 deduped │ │ │ ├─┬ is-boolean-object@1.1.2 │ │ │ │ ├── call-bind@1.0.2 deduped │ │ │ │ └── has-tostringtag@1.0.0 deduped │ │ │ ├─┬ is-number-object@1.0.6 │ │ │ │ └── has-tostringtag@1.0.0 deduped │ │ │ ├── is-string@1.0.7 deduped │ │ │ └── is-symbol@1.0.4 deduped │ │ ├─┬ get-intrinsic@1.1.1 │ │ │ ├── function-bind@1.1.1 deduped │ │ │ ├── has@1.0.3 deduped │ │ │ └── has-symbols@1.0.2 deduped │ │ └─┬ is-string@1.0.7 │ │ └─┬ has-tostringtag@1.0.0 │ │ └── has-symbols@1.0.2 deduped │ ├─┬ array.prototype.flat@1.2.5 │ │ ├── call-bind@1.0.2 deduped │ │ ├── define-properties@1.1.3 deduped │ │ └── es-abstract@1.19.1 deduped │ ├─┬ debug@2.6.9 │ │ └── ms@2.0.0 │ ├─┬ doctrine@2.1.0 │ │ └── esutils@2.0.3 deduped │ ├─┬ eslint-import-resolver-node@0.3.6 │ │ ├─┬ debug@3.2.7 │ │ │ └── ms@2.1.2 deduped │ │ └── resolve@1.20.0 deduped │ ├─┬ eslint-module-utils@2.7.1 │ │ ├─┬ debug@3.2.7 │ │ │ └── ms@2.1.2 deduped │ │ ├─┬ find-up@2.1.0 │ │ │ └─┬ locate-path@2.0.0 │ │ │ ├─┬ p-locate@2.0.0 │ │ │ │ └─┬ p-limit@1.3.0 │ │ │ │ └── p-try@1.0.0 │ │ │ └── path-exists@3.0.0 │ │ └─┬ pkg-dir@2.0.0 │ │ └── find-up@2.1.0 deduped │ ├─┬ has@1.0.3 │ │ └── function-bind@1.1.1 │ ├─┬ is-core-module@2.8.0 │ │ └── has@1.0.3 deduped │ ├── is-glob@4.0.3 deduped │ ├── minimatch@3.0.4 deduped │ ├─┬ object.values@1.1.5 │ │ ├── call-bind@1.0.2 deduped │ │ ├── define-properties@1.1.3 deduped │ │ └── es-abstract@1.19.1 deduped │ ├── resolve@1.20.0 deduped │ └── tsconfig-paths@3.12.0 deduped ├─┬ eslint-plugin-jsdoc@37.4.0 │ ├─┬ @es-joy/jsdoccomment@0.13.0 │ │ ├── comment-parser@1.3.0 deduped │ │ ├── esquery@1.4.0 deduped │ │ └── jsdoc-type-pratt-parser@2.0.0 deduped │ ├── comment-parser@1.3.0 │ ├── debug@4.3.3 deduped │ ├── escape-string-regexp@4.0.0 deduped │ ├── esquery@1.4.0 deduped │ ├── jsdoc-type-pratt-parser@2.0.0 │ ├── regextras@0.8.0 │ ├─┬ semver@7.3.5 │ │ └── lru-cache@6.0.0 deduped │ └─┬ spdx-expression-parse@3.0.1 │ ├── spdx-exceptions@2.3.0 │ └── spdx-license-ids@3.0.11 ├── eslint-plugin-prefer-arrow@1.2.3 ├── eslint-plugin-promise@6.0.0 ├─┬ eslint-plugin-typescript-sort-keys@2.1.0 │ ├── @typescript-eslint/experimental-utils@5.8.0 deduped │ ├── json-schema@0.4.0 │ └── natural-compare-lite@1.4.0 ├─┬ eslint-plugin-unicorn@39.0.0 │ ├── @babel/helper-validator-identifier@7.15.7 │ ├── ci-info@3.3.0 │ ├─┬ clean-regexp@1.0.0 │ │ └── escape-string-regexp@1.0.5 │ ├─┬ eslint-template-visitor@2.3.2 │ │ ├─┬ @babel/core@7.16.5 │ │ │ ├── @babel/code-frame@7.16.0 deduped │ │ │ ├─┬ @babel/generator@7.16.5 │ │ │ │ ├── @babel/types@7.16.0 deduped │ │ │ │ ├── jsesc@2.5.2 │ │ │ │ └── source-map@0.5.7 deduped │ │ │ ├─┬ @babel/helper-compilation-targets@7.16.3 │ │ │ │ ├── @babel/compat-data@7.16.4 │ │ │ │ ├── @babel/helper-validator-option@7.14.5 │ │ │ │ ├─┬ browserslist@4.19.1 │ │ │ │ │ ├── caniuse-lite@1.0.30001292 │ │ │ │ │ ├── electron-to-chromium@1.4.27 │ │ │ │ │ ├── escalade@3.1.1 deduped │ │ │ │ │ ├── node-releases@2.0.1 │ │ │ │ │ └── picocolors@1.0.0 │ │ │ │ └── semver@6.3.0 deduped │ │ │ ├─┬ @babel/helper-module-transforms@7.16.5 │ │ │ │ ├─┬ @babel/helper-environment-visitor@7.16.5 │ │ │ │ │ └── @babel/types@7.16.0 deduped │ │ │ │ ├─┬ @babel/helper-module-imports@7.16.0 │ │ │ │ │ └── @babel/types@7.16.0 deduped │ │ │ │ ├─┬ @babel/helper-simple-access@7.16.0 │ │ │ │ │ └── @babel/types@7.16.0 deduped │ │ │ │ ├─┬ @babel/helper-split-export-declaration@7.16.0 │ │ │ │ │ └── @babel/types@7.16.0 deduped │ │ │ │ ├── @babel/helper-validator-identifier@7.15.7 deduped │ │ │ │ ├── @babel/template@7.16.0 deduped │ │ │ │ ├── @babel/traverse@7.16.5 deduped │ │ │ │ └── @babel/types@7.16.0 deduped │ │ │ ├─┬ @babel/helpers@7.16.5 │ │ │ │ ├── @babel/template@7.16.0 deduped │ │ │ │ ├── @babel/traverse@7.16.5 deduped │ │ │ │ └── @babel/types@7.16.0 deduped │ │ │ ├── @babel/parser@7.16.6 │ │ │ ├─┬ @babel/template@7.16.0 │ │ │ │ ├── @babel/code-frame@7.16.0 deduped │ │ │ │ ├── @babel/parser@7.16.6 deduped │ │ │ │ └── @babel/types@7.16.0 deduped │ │ │ ├─┬ @babel/traverse@7.16.5 │ │ │ │ ├── @babel/code-frame@7.16.0 deduped │ │ │ │ ├── @babel/generator@7.16.5 deduped │ │ │ │ ├── @babel/helper-environment-visitor@7.16.5 deduped │ │ │ │ ├─┬ @babel/helper-function-name@7.16.0 │ │ │ │ │ ├─┬ @babel/helper-get-function-arity@7.16.0 │ │ │ │ │ │ └── @babel/types@7.16.0 deduped │ │ │ │ │ ├── @babel/template@7.16.0 deduped │ │ │ │ │ └── @babel/types@7.16.0 deduped │ │ │ │ ├─┬ @babel/helper-hoist-variables@7.16.0 │ │ │ │ │ └── @babel/types@7.16.0 deduped │ │ │ │ ├── @babel/helper-split-export-declaration@7.16.0 deduped │ │ │ │ ├── @babel/parser@7.16.6 deduped │ │ │ │ ├── @babel/types@7.16.0 deduped │ │ │ │ ├── debug@4.3.3 deduped │ │ │ │ └── globals@11.12.0 │ │ │ ├─┬ @babel/types@7.16.0 │ │ │ │ ├── @babel/helper-validator-identifier@7.15.7 deduped │ │ │ │ └── to-fast-properties@2.0.0 │ │ │ ├─┬ convert-source-map@1.8.0 │ │ │ │ └── safe-buffer@5.1.2 │ │ │ ├── debug@4.3.3 deduped │ │ │ ├── gensync@1.0.0-beta.2 │ │ │ ├─┬ json5@2.2.0 │ │ │ │ └── minimist@1.2.5 deduped │ │ │ ├── semver@6.3.0 │ │ │ └── source-map@0.5.7 │ │ ├─┬ @babel/eslint-parser@7.16.5 │ │ │ ├── eslint-scope@5.1.1 deduped │ │ │ ├── eslint-visitor-keys@2.1.0 │ │ │ └── semver@6.3.0 deduped │ │ ├── eslint-visitor-keys@2.1.0 │ │ ├── esquery@1.4.0 deduped │ │ └── multimap@1.1.0 │ ├── eslint-utils@3.0.0 deduped │ ├── esquery@1.4.0 deduped │ ├── indent-string@4.0.0 │ ├─┬ is-builtin-module@3.1.0 │ │ └── builtin-modules@3.2.0 │ ├── lodash@4.17.21 deduped │ ├── pluralize@8.0.0 │ ├─┬ read-pkg-up@7.0.1 │ │ ├─┬ find-up@4.1.0 │ │ │ ├─┬ locate-path@5.0.0 │ │ │ │ └─┬ p-locate@4.1.0 │ │ │ │ └─┬ p-limit@2.3.0 │ │ │ │ └── p-try@2.2.0 │ │ │ └── path-exists@4.0.0 │ │ ├─┬ read-pkg@5.2.0 │ │ │ ├── @types/normalize-package-data@2.4.1 │ │ │ ├─┬ normalize-package-data@2.5.0 │ │ │ │ ├── hosted-git-info@2.8.9 │ │ │ │ ├── resolve@1.20.0 deduped │ │ │ │ ├── semver@5.7.1 │ │ │ │ └─┬ validate-npm-package-license@3.0.4 │ │ │ │ ├─┬ spdx-correct@3.1.1 │ │ │ │ │ ├── spdx-expression-parse@3.0.1 deduped │ │ │ │ │ └── spdx-license-ids@3.0.11 deduped │ │ │ │ └── spdx-expression-parse@3.0.1 deduped │ │ │ ├─┬ parse-json@5.2.0 │ │ │ │ ├── @babel/code-frame@7.16.0 deduped │ │ │ │ ├─┬ error-ex@1.3.2 │ │ │ │ │ └── is-arrayish@0.2.1 │ │ │ │ ├── json-parse-even-better-errors@2.3.1 │ │ │ │ └── lines-and-columns@1.2.4 │ │ │ └── type-fest@0.6.0 │ │ └── type-fest@0.8.1 │ ├── regexp-tree@0.1.24 │ ├─┬ safe-regex@2.1.1 │ │ └── regexp-tree@0.1.24 deduped │ ├─┬ semver@7.3.5 │ │ └── lru-cache@6.0.0 deduped │ └─┬ strip-indent@3.0.0 │ └── min-indent@1.0.1 ├─┬ firebase@9.6.1 │ ├─┬ @firebase/analytics@0.7.4 │ │ ├─┬ @firebase/component@0.5.9 │ │ │ ├── @firebase/util@1.4.2 deduped │ │ │ └── tslib@2.3.1 deduped │ │ ├── @firebase/installations@0.5.4 deduped │ │ ├─┬ @firebase/logger@0.3.2 │ │ │ └── tslib@2.3.1 deduped │ │ ├── @firebase/util@1.4.2 deduped │ │ └── tslib@2.3.1 │ ├─┬ @firebase/analytics-compat@0.1.5 │ │ ├── @firebase/analytics@0.7.4 deduped │ │ ├── @firebase/analytics-types@0.7.0 │ │ ├── @firebase/component@0.5.9 deduped │ │ ├── @firebase/util@1.4.2 deduped │ │ └── tslib@2.3.1 deduped │ ├─┬ @firebase/app@0.7.11 │ │ ├── @firebase/component@0.5.9 deduped │ │ ├── @firebase/logger@0.3.2 deduped │ │ ├── @firebase/util@1.4.2 deduped │ │ └── tslib@2.3.1 deduped │ ├─┬ @firebase/app-check@0.5.2 │ │ ├── @firebase/component@0.5.9 deduped │ │ ├── @firebase/logger@0.3.2 deduped │ │ ├── @firebase/util@1.4.2 deduped │ │ └── tslib@2.3.1 deduped │ ├─┬ @firebase/app-check-compat@0.2.2 │ │ ├── @firebase/app-check@0.5.2 deduped │ │ ├── @firebase/component@0.5.9 deduped │ │ ├── @firebase/logger@0.3.2 deduped │ │ ├── @firebase/util@1.4.2 deduped │ │ └── tslib@2.3.1 deduped │ ├─┬ @firebase/app-compat@0.1.12 │ │ ├── @firebase/app@0.7.11 deduped │ │ ├── @firebase/component@0.5.9 deduped │ │ ├── @firebase/logger@0.3.2 deduped │ │ ├── @firebase/util@1.4.2 deduped │ │ └── tslib@2.3.1 deduped │ ├── @firebase/app-types@0.7.0 │ ├─┬ @firebase/auth@0.19.4 │ │ ├── @firebase/component@0.5.9 deduped │ │ ├── @firebase/logger@0.3.2 deduped │ │ ├── @firebase/util@1.4.2 deduped │ │ ├─┬ node-fetch@2.6.5 │ │ │ └── whatwg-url@5.0.0 deduped │ │ ├─┬ selenium-webdriver@4.0.0-rc-1 │ │ │ ├─┬ jszip@3.7.1 │ │ │ │ ├─┬ lie@3.3.0 │ │ │ │ │ └── immediate@3.0.6 │ │ │ │ ├── pako@1.0.11 │ │ │ │ ├─┬ readable-stream@2.3.7 │ │ │ │ │ ├── core-util-is@1.0.3 │ │ │ │ │ ├── inherits@2.0.4 deduped │ │ │ │ │ ├── isarray@1.0.0 │ │ │ │ │ ├── process-nextick-args@2.0.1 │ │ │ │ │ ├── safe-buffer@5.1.2 │ │ │ │ │ ├─┬ string_decoder@1.1.1 │ │ │ │ │ │ └── safe-buffer@5.1.2 deduped │ │ │ │ │ └── util-deprecate@1.0.2 deduped │ │ │ │ └── set-immediate-shim@1.0.1 │ │ │ ├── rimraf@3.0.2 deduped │ │ │ ├─┬ tmp@0.2.1 │ │ │ │ └── rimraf@3.0.2 deduped │ │ │ └── ws@8.4.0 │ │ └── tslib@2.3.1 deduped │ ├─┬ @firebase/auth-compat@0.2.4 │ │ ├── @firebase/auth@0.19.4 deduped │ │ ├── @firebase/auth-types@0.11.0 │ │ ├── @firebase/component@0.5.9 deduped │ │ ├── @firebase/util@1.4.2 deduped │ │ ├─┬ node-fetch@2.6.5 │ │ │ └── whatwg-url@5.0.0 deduped │ │ ├── selenium-webdriver@4.0.0-rc-1 deduped │ │ └── tslib@2.3.1 deduped │ ├─┬ @firebase/database@0.12.4 │ │ ├── @firebase/auth-interop-types@0.1.6 │ │ ├── @firebase/component@0.5.9 deduped │ │ ├── @firebase/logger@0.3.2 deduped │ │ ├── @firebase/util@1.4.2 deduped │ │ ├─┬ faye-websocket@0.11.4 │ │ │ └─┬ websocket-driver@0.7.4 │ │ │ ├── http-parser-js@0.5.5 │ │ │ ├── safe-buffer@5.2.1 deduped │ │ │ └── websocket-extensions@0.1.4 │ │ └── tslib@2.3.1 deduped │ ├─┬ @firebase/database-compat@0.1.4 │ │ ├── @firebase/component@0.5.9 deduped │ │ ├── @firebase/database@0.12.4 deduped │ │ ├─┬ @firebase/database-types@0.9.3 │ │ │ ├── @firebase/app-types@0.7.0 deduped │ │ │ └── @firebase/util@1.4.2 deduped │ │ ├── @firebase/logger@0.3.2 deduped │ │ ├── @firebase/util@1.4.2 deduped │ │ └── tslib@2.3.1 deduped │ ├─┬ @firebase/firestore@3.4.1 │ │ ├── @firebase/component@0.5.9 deduped │ │ ├── @firebase/logger@0.3.2 deduped │ │ ├── @firebase/util@1.4.2 deduped │ │ ├── @firebase/webchannel-wrapper@0.6.1 │ │ ├── @grpc/grpc-js@1.4.5 deduped │ │ ├── @grpc/proto-loader@0.6.7 deduped │ │ ├─┬ node-fetch@2.6.5 │ │ │ └── whatwg-url@5.0.0 deduped │ │ └── tslib@2.3.1 deduped │ ├─┬ @firebase/firestore-compat@0.1.10 │ │ ├── @firebase/component@0.5.9 deduped │ │ ├── @firebase/firestore@3.4.1 deduped │ │ ├── @firebase/firestore-types@2.5.0 │ │ ├── @firebase/util@1.4.2 deduped │ │ └── tslib@2.3.1 deduped │ ├─┬ @firebase/functions@0.7.6 │ │ ├── @firebase/app-check-interop-types@0.1.0 │ │ ├── @firebase/auth-interop-types@0.1.6 deduped │ │ ├── @firebase/component@0.5.9 deduped │ │ ├── @firebase/messaging-interop-types@0.1.0 │ │ ├── @firebase/util@1.4.2 deduped │ │ ├─┬ node-fetch@2.6.5 │ │ │ └── whatwg-url@5.0.0 deduped │ │ └── tslib@2.3.1 deduped │ ├─┬ @firebase/functions-compat@0.1.7 │ │ ├── @firebase/component@0.5.9 deduped │ │ ├── @firebase/functions@0.7.6 deduped │ │ ├── @firebase/functions-types@0.5.0 │ │ ├── @firebase/util@1.4.2 deduped │ │ └── tslib@2.3.1 deduped │ ├─┬ @firebase/installations@0.5.4 │ │ ├── @firebase/component@0.5.9 deduped │ │ ├── @firebase/util@1.4.2 deduped │ │ ├── idb@3.0.2 │ │ └── tslib@2.3.1 deduped │ ├─┬ @firebase/messaging@0.9.4 │ │ ├── @firebase/component@0.5.9 deduped │ │ ├── @firebase/installations@0.5.4 deduped │ │ ├── @firebase/messaging-interop-types@0.1.0 deduped │ │ ├── @firebase/util@1.4.2 deduped │ │ ├── idb@3.0.2 deduped │ │ └── tslib@2.3.1 deduped │ ├─┬ @firebase/messaging-compat@0.1.4 │ │ ├── @firebase/component@0.5.9 deduped │ │ ├── @firebase/messaging@0.9.4 deduped │ │ ├── @firebase/util@1.4.2 deduped │ │ └── tslib@2.3.1 deduped │ ├─┬ @firebase/performance@0.5.4 │ │ ├── @firebase/component@0.5.9 deduped │ │ ├── @firebase/installations@0.5.4 deduped │ │ ├── @firebase/logger@0.3.2 deduped │ │ ├── @firebase/util@1.4.2 deduped │ │ └── tslib@2.3.1 deduped │ ├─┬ @firebase/performance-compat@0.1.4 │ │ ├── @firebase/component@0.5.9 deduped │ │ ├── @firebase/logger@0.3.2 deduped │ │ ├── @firebase/performance@0.5.4 deduped │ │ ├── @firebase/performance-types@0.1.0 │ │ ├── @firebase/util@1.4.2 deduped │ │ └── tslib@2.3.1 deduped │ ├─┬ @firebase/polyfill@0.3.36 │ │ ├── core-js@3.6.5 │ │ ├── promise-polyfill@8.1.3 │ │ └── whatwg-fetch@2.0.4 │ ├─┬ @firebase/remote-config@0.3.3 │ │ ├── @firebase/component@0.5.9 deduped │ │ ├── @firebase/installations@0.5.4 deduped │ │ ├── @firebase/logger@0.3.2 deduped │ │ ├── @firebase/util@1.4.2 deduped │ │ └── tslib@2.3.1 deduped │ ├─┬ @firebase/remote-config-compat@0.1.4 │ │ ├── @firebase/component@0.5.9 deduped │ │ ├── @firebase/logger@0.3.2 deduped │ │ ├── @firebase/remote-config@0.3.3 deduped │ │ ├── @firebase/remote-config-types@0.2.0 │ │ ├── @firebase/util@1.4.2 deduped │ │ └── tslib@2.3.1 deduped │ ├─┬ @firebase/storage@0.9.0 │ │ ├── @firebase/component@0.5.9 deduped │ │ ├── @firebase/util@1.4.2 deduped │ │ ├─┬ node-fetch@2.6.5 │ │ │ └── whatwg-url@5.0.0 deduped │ │ └── tslib@2.3.1 deduped │ ├─┬ @firebase/storage-compat@0.1.8 │ │ ├── @firebase/component@0.5.9 deduped │ │ ├── @firebase/storage@0.9.0 deduped │ │ ├── @firebase/storage-types@0.6.0 │ │ ├── @firebase/util@1.4.2 deduped │ │ └── tslib@2.3.1 deduped │ └─┬ @firebase/util@1.4.2 │ └── tslib@2.3.1 deduped ├─┬ firebase-admin@10.0.1 │ ├── @firebase/database-compat@0.1.4 deduped │ ├─┬ @firebase/database-types@0.7.3 │ │ └── @firebase/app-types@0.6.3 │ ├─┬ @google-cloud/firestore@4.15.1 │ │ ├── fast-deep-equal@3.1.3 deduped │ │ ├── functional-red-black-tree@1.0.1 deduped │ │ ├── google-gax@2.28.1 deduped │ │ └── protobufjs@6.11.2 deduped │ ├─┬ @google-cloud/storage@5.16.1 │ │ ├─┬ @google-cloud/common@3.8.1 │ │ │ ├── @google-cloud/projectify@2.1.1 │ │ │ ├── @google-cloud/promisify@2.0.4 deduped │ │ │ ├── arrify@2.0.1 deduped │ │ │ ├── duplexify@4.1.2 deduped │ │ │ ├── ent@2.2.0 │ │ │ ├── extend@3.0.2 deduped │ │ │ ├── google-auth-library@7.11.0 deduped │ │ │ ├── retry-request@4.2.2 deduped │ │ │ └─┬ teeny-request@7.1.3 │ │ │ ├─┬ http-proxy-agent@5.0.0 │ │ │ │ ├── @tootallnate/once@2.0.0 │ │ │ │ ├── agent-base@6.0.2 deduped │ │ │ │ └── debug@4.3.3 deduped │ │ │ ├── https-proxy-agent@5.0.0 deduped │ │ │ ├── node-fetch@2.6.6 deduped │ │ │ ├── stream-events@1.0.5 deduped │ │ │ └── uuid@8.3.2 │ │ ├─┬ @google-cloud/paginator@3.0.6 │ │ │ ├── arrify@2.0.1 deduped │ │ │ └── extend@3.0.2 deduped │ │ ├── @google-cloud/promisify@2.0.4 │ │ ├── arrify@2.0.1 │ │ ├─┬ async-retry@1.3.3 │ │ │ └── retry@0.13.1 │ │ ├─┬ compressible@2.0.18 │ │ │ └── mime-db@1.51.0 deduped │ │ ├── date-and-time@2.0.1 │ │ ├── duplexify@4.1.2 deduped │ │ ├── extend@3.0.2 │ │ ├─┬ gcs-resumable-upload@3.6.0 │ │ │ ├── abort-controller@3.0.0 deduped │ │ │ ├── async-retry@1.3.3 deduped │ │ │ ├─┬ configstore@5.0.1 │ │ │ │ ├─┬ dot-prop@5.3.0 │ │ │ │ │ └── is-obj@2.0.0 │ │ │ │ ├── graceful-fs@4.2.8 │ │ │ │ ├─┬ make-dir@3.1.0 │ │ │ │ │ └── semver@6.3.0 deduped │ │ │ │ ├─┬ unique-string@2.0.0 │ │ │ │ │ └── crypto-random-string@2.0.0 │ │ │ │ ├─┬ write-file-atomic@3.0.3 │ │ │ │ │ ├── imurmurhash@0.1.4 deduped │ │ │ │ │ ├── is-typedarray@1.0.0 │ │ │ │ │ ├── signal-exit@3.0.6 │ │ │ │ │ └─┬ typedarray-to-buffer@3.1.5 │ │ │ │ │ └── is-typedarray@1.0.0 deduped │ │ │ │ └── xdg-basedir@4.0.0 deduped │ │ │ ├── extend@3.0.2 deduped │ │ │ ├── gaxios@4.3.2 deduped │ │ │ ├── google-auth-library@7.11.0 deduped │ │ │ ├── pumpify@2.0.1 deduped │ │ │ └── stream-events@1.0.5 deduped │ │ ├── get-stream@6.0.1 │ │ ├── hash-stream-validation@0.2.4 │ │ ├── mime@3.0.0 │ │ ├── mime-types@2.1.34 deduped │ │ ├─┬ p-limit@3.1.0 │ │ │ └── yocto-queue@0.1.0 │ │ ├─┬ pumpify@2.0.1 │ │ │ ├── duplexify@4.1.2 deduped │ │ │ ├── inherits@2.0.4 deduped │ │ │ └─┬ pump@3.0.0 │ │ │ ├── end-of-stream@1.4.4 deduped │ │ │ └── once@1.4.0 deduped │ │ ├── snakeize@0.1.0 │ │ ├─┬ stream-events@1.0.5 │ │ │ └── stubs@3.0.0 │ │ └── xdg-basedir@4.0.0 │ ├── @types/node@14.18.2 deduped │ ├─┬ dicer@0.3.1 │ │ └── streamsearch@1.1.0 │ ├─┬ jsonwebtoken@8.5.1 │ │ ├─┬ jws@3.2.2 │ │ │ ├─┬ jwa@1.4.1 │ │ │ │ ├── buffer-equal-constant-time@1.0.1 │ │ │ │ ├── ecdsa-sig-formatter@1.0.11 deduped │ │ │ │ └── safe-buffer@5.2.1 deduped │ │ │ └── safe-buffer@5.2.1 deduped │ │ ├── lodash.includes@4.3.0 │ │ ├── lodash.isboolean@3.0.3 │ │ ├── lodash.isinteger@4.0.4 │ │ ├── lodash.isnumber@3.0.3 │ │ ├── lodash.isplainobject@4.0.6 │ │ ├── lodash.isstring@4.0.1 │ │ ├── lodash.once@4.1.1 │ │ ├── ms@2.1.2 deduped │ │ └── semver@5.7.1 │ ├─┬ jwks-rsa@2.0.5 │ │ ├─┬ @types/express-jwt@0.0.42 │ │ │ ├─┬ @types/express@4.17.13 │ │ │ │ ├── @types/body-parser@1.19.2 deduped │ │ │ │ ├── @types/express-serve-static-core@4.17.26 deduped │ │ │ │ ├── @types/qs@6.9.7 deduped │ │ │ │ └── @types/serve-static@1.13.10 deduped │ │ │ └─┬ @types/express-unless@0.5.2 │ │ │ └── @types/express@4.17.13 deduped │ │ ├── debug@4.3.3 deduped │ │ ├─┬ jose@2.0.5 │ │ │ └── @panva/asn1.js@1.0.0 │ │ ├── limiter@1.1.5 │ │ └─┬ lru-memoizer@2.1.4 │ │ ├── lodash.clonedeep@4.5.0 │ │ └─┬ lru-cache@4.0.2 │ │ ├── pseudomap@1.0.2 │ │ └── yallist@2.1.2 │ └── node-forge@0.10.0 ├─┬ firebase-functions@3.16.0 │ ├── @types/cors@2.8.12 │ ├─┬ @types/express@4.17.3 │ │ ├─┬ @types/body-parser@1.19.2 │ │ │ ├─┬ @types/connect@3.4.35 │ │ │ │ └── @types/node@14.18.2 deduped │ │ │ └── @types/node@14.18.2 deduped │ │ ├─┬ @types/express-serve-static-core@4.17.26 │ │ │ ├── @types/node@14.18.2 deduped │ │ │ ├── @types/qs@6.9.7 │ │ │ └── @types/range-parser@1.2.4 │ │ └─┬ @types/serve-static@1.13.10 │ │ ├── @types/mime@1.3.2 │ │ └── @types/node@14.18.2 deduped │ ├─┬ cors@2.8.5 │ │ ├── object-assign@4.1.1 │ │ └── vary@1.1.2 │ ├─┬ express@4.17.2 │ │ ├─┬ accepts@1.3.7 │ │ │ ├── mime-types@2.1.34 deduped │ │ │ └── negotiator@0.6.2 │ │ ├── array-flatten@1.1.1 │ │ ├─┬ body-parser@1.19.1 │ │ │ ├── bytes@3.1.1 │ │ │ ├── content-type@1.0.4 deduped │ │ │ ├─┬ debug@2.6.9 │ │ │ │ └── ms@2.0.0 │ │ │ ├── depd@1.1.2 deduped │ │ │ ├─┬ http-errors@1.8.1 │ │ │ │ ├── depd@1.1.2 deduped │ │ │ │ ├── inherits@2.0.4 deduped │ │ │ │ ├── setprototypeof@1.2.0 deduped │ │ │ │ ├── statuses@1.5.0 deduped │ │ │ │ └── toidentifier@1.0.1 │ │ │ ├─┬ iconv-lite@0.4.24 │ │ │ │ └── safer-buffer@2.1.2 │ │ │ ├── on-finished@2.3.0 deduped │ │ │ ├── qs@6.9.6 deduped │ │ │ ├─┬ raw-body@2.4.2 │ │ │ │ ├── bytes@3.1.1 deduped │ │ │ │ ├── http-errors@1.8.1 deduped │ │ │ │ ├── iconv-lite@0.4.24 deduped │ │ │ │ └── unpipe@1.0.0 deduped │ │ │ └── type-is@1.6.18 deduped │ │ ├─┬ content-disposition@0.5.4 │ │ │ └── safe-buffer@5.2.1 deduped │ │ ├── content-type@1.0.4 │ │ ├── cookie@0.4.1 │ │ ├── cookie-signature@1.0.6 │ │ ├─┬ debug@2.6.9 │ │ │ └── ms@2.0.0 │ │ ├── depd@1.1.2 │ │ ├── encodeurl@1.0.2 │ │ ├── escape-html@1.0.3 │ │ ├── etag@1.8.1 │ │ ├─┬ finalhandler@1.1.2 │ │ │ ├─┬ debug@2.6.9 │ │ │ │ └── ms@2.0.0 │ │ │ ├── encodeurl@1.0.2 deduped │ │ │ ├── escape-html@1.0.3 deduped │ │ │ ├── on-finished@2.3.0 deduped │ │ │ ├── parseurl@1.3.3 deduped │ │ │ ├── statuses@1.5.0 deduped │ │ │ └── unpipe@1.0.0 │ │ ├── fresh@0.5.2 │ │ ├── merge-descriptors@1.0.1 │ │ ├── methods@1.1.2 │ │ ├─┬ on-finished@2.3.0 │ │ │ └── ee-first@1.1.1 │ │ ├── parseurl@1.3.3 │ │ ├── path-to-regexp@0.1.7 │ │ ├─┬ proxy-addr@2.0.7 │ │ │ ├── forwarded@0.2.0 │ │ │ └── ipaddr.js@1.9.1 │ │ ├── qs@6.9.6 │ │ ├── range-parser@1.2.1 │ │ ├── safe-buffer@5.2.1 │ │ ├─┬ send@0.17.2 │ │ │ ├─┬ debug@2.6.9 │ │ │ │ └── ms@2.0.0 │ │ │ ├── depd@1.1.2 deduped │ │ │ ├── destroy@1.0.4 │ │ │ ├── encodeurl@1.0.2 deduped │ │ │ ├── escape-html@1.0.3 deduped │ │ │ ├── etag@1.8.1 deduped │ │ │ ├── fresh@0.5.2 deduped │ │ │ ├── http-errors@1.8.1 deduped │ │ │ ├── mime@1.6.0 │ │ │ ├── ms@2.1.3 │ │ │ ├── on-finished@2.3.0 deduped │ │ │ ├── range-parser@1.2.1 deduped │ │ │ └── statuses@1.5.0 deduped │ │ ├─┬ serve-static@1.14.2 │ │ │ ├── encodeurl@1.0.2 deduped │ │ │ ├── escape-html@1.0.3 deduped │ │ │ ├── parseurl@1.3.3 deduped │ │ │ └── send@0.17.2 deduped │ │ ├── setprototypeof@1.2.0 │ │ ├── statuses@1.5.0 │ │ ├─┬ type-is@1.6.18 │ │ │ ├── media-typer@0.3.0 │ │ │ └── mime-types@2.1.34 deduped │ │ ├── utils-merge@1.0.1 │ │ └── vary@1.1.2 deduped │ └── lodash@4.17.21 deduped ├─┬ firebase-functions-test@0.3.3 │ ├── @types/lodash@4.14.178 │ └── lodash@4.17.21 deduped ├─┬ jasmine@3.10.0 │ ├── glob@7.2.0 deduped │ └── jasmine-core@3.10.1 ├─┬ node-fetch@2.6.6 │ └─┬ whatwg-url@5.0.0 │ ├── tr46@0.0.3 │ └── webidl-conversions@3.0.1 ├─┬ ts-node@10.4.0 │ ├─┬ @cspotcode/source-map-support@0.7.0 │ │ └── @cspotcode/source-map-consumer@0.8.0 │ ├── @tsconfig/node10@1.0.8 │ ├── @tsconfig/node12@1.0.9 │ ├── @tsconfig/node14@1.0.1 │ ├── @tsconfig/node16@1.0.2 │ ├── acorn@8.6.0 │ ├── acorn-walk@8.2.0 │ ├── arg@4.1.3 │ ├── create-require@1.1.1 │ ├── diff@4.0.2 │ ├── make-error@1.3.6 │ └── yn@3.1.1 ├─┬ tsconfig-paths@3.12.0 │ ├── @types/json5@0.0.29 │ ├─┬ json5@1.0.1 │ │ └── minimist@1.2.5 deduped │ ├── minimist@1.2.5 │ └── strip-bom@3.0.0 ├─┬ tslint@6.1.3 │ ├─┬ @babel/code-frame@7.16.0 │ │ └─┬ @babel/highlight@7.16.0 │ │ ├── @babel/helper-validator-identifier@7.15.7 deduped │ │ ├─┬ chalk@2.4.2 │ │ │ ├─┬ ansi-styles@3.2.1 │ │ │ │ └─┬ color-convert@1.9.3 │ │ │ │ └── color-name@1.1.3 │ │ │ ├── escape-string-regexp@1.0.5 │ │ │ └─┬ supports-color@5.5.0 │ │ │ └── has-flag@3.0.0 │ │ └── js-tokens@4.0.0 │ ├── builtin-modules@1.1.1 │ ├─┬ chalk@2.4.2 │ │ ├─┬ ansi-styles@3.2.1 │ │ │ └─┬ color-convert@1.9.3 │ │ │ └── color-name@1.1.3 │ │ ├── escape-string-regexp@1.0.5 │ │ └─┬ supports-color@5.5.0 │ │ └── has-flag@3.0.0 │ ├── commander@2.20.3 │ ├── diff@4.0.2 deduped │ ├── glob@7.2.0 deduped │ ├─┬ js-yaml@3.14.1 │ │ ├─┬ argparse@1.0.10 │ │ │ └── sprintf-js@1.0.3 │ │ └── esprima@4.0.1 │ ├── minimatch@3.0.4 deduped │ ├─┬ mkdirp@0.5.5 │ │ └── minimist@1.2.5 deduped │ ├── resolve@1.20.0 deduped │ ├── semver@5.7.1 │ ├── tslib@1.14.1 │ └─┬ tsutils@2.29.0 │ └── tslib@1.14.1 deduped └── typescript@4.5.4 ```
schmidt-sebastian commented 2 years ago

You have both "@google-cloud/firestore@4.15.1" and "@google-cloud/firestore@5.0.1". Until firebase-admin pulls in 5.x, you should depend on "@google-cloud/firestore@4.15.1".

rgant commented 2 years ago

That is listed as an optional dependency in firebase-admin's package.json. I wouldn't have expected that to cause issues. Seems like something in npm should warn me about these conflicts.

Thanks!

devinrhode2 commented 1 year ago

@schmidt-sebastian is there any code from here that's still relelvant: https://gist.github.com/JamieCurnow/650ea759c277757ae5665ea52400713b also https://javascript.plainenglish.io/using-firestore-with-more-typescript-8058b6a88674

devinrhode2 commented 1 year ago

Sorry for spamming, jamie has a v9 article here: https://plainenglish.io/blog/using-firestore-with-typescript-in-the-v9-sdk-cf36851bb099

ljrodriguez1 commented 1 year ago

I wanted to know why is this closed, I am using "firebase-admin": "^11.11.0" And i still find this issue when using dot notation with withconverter

0x80 commented 12 months ago

@ljrodriguez1 I think it's because UpdateData is now included / exported from firebase-admin since it upgraded its @google-cloud/firestore dependency.

import type { UpdateData } from "firebase-admin/firestore";

So you can use that to type your data as described in this article by @JamieCurnow

PS: Tantiantially related, does anyone here know if/when the firestore API from firebase-admin / @google-cloud/firestore will transition to the same "modular" API as the web SDK >=v9?

ljrodriguez1 commented 12 months ago

@0x80 But that UpdateData does not support dot notation, and differs from the UpdateData implementation from the article.

because of that when doing db.collection("example").doc("exampleId").update(updates) it throws a typescript error if i use the UpdateData from the article that supports dot notation

0x80 commented 12 months ago

@ljrodriguez1 Ah ok that's good to know! I didn't fully understand your problem then.

Maybe @schmidt-sebastian or @thebrianchen can chime in...?